<?php

/**

 * Plugin Name: WPMU Storage Quota Monitor

 * Plugin URI:  https://example.com/

 * Description: Emails the network administrator when a site in a Multisite network approaches its upload storage quota.

 * Version:     1.0

 * Author:      Your Name

 * Author URI:  https://example.com/

 * License:     GPL2

 */


// Exit if accessed directly.

if ( ! defined( 'ABSPATH' ) ) {

    exit;

}


class WPMU_Storage_Quota_Monitor {


    private $threshold_percentage = 80; // Email when usage reaches 80% of the quota

    private $email_interval_hours = 24; // Only send email once every 24 hours per site

    private $option_name_last_notified = 'wpmu_sqm_last_notified';


    public function __construct() {

        if ( is_multisite() ) {

            add_action( 'admin_init', array( $this, 'schedule_quota_check' ) );

            add_action( 'wpmu_storage_quota_check', array( $this, 'check_all_site_quotas' ) );

            add_action( 'admin_menu', array( $this, 'add_settings_page' ) );

            add_action( 'admin_init', array( $this, 'register_settings' ) );

        }

    }


    /**

     * Schedules the daily quota check.

     */

    public function schedule_quota_check() {

        if ( ! wp_next_scheduled( 'wpmu_storage_quota_check' ) ) {

            wp_schedule_event( time(), 'daily', 'wpmu_storage_quota_check' );

        }

    }


    /**

     * Unschedule the event on plugin deactivation.

     */

    public static function unschedule_quota_check_on_deactivation() {

        wp_clear_scheduled_hook( 'wpmu_storage_quota_check' );

    }


    /**

     * Checks the upload quota for all sites in the network.

     */

    public function check_all_site_quotas() {

        // Get the threshold percentage from settings, fallback to default.

        $this->threshold_percentage = get_site_option( 'wpmu_sqm_threshold_percentage', 80 );

        $this->email_interval_hours = get_site_option( 'wpmu_sqm_email_interval_hours', 24 );


        $sites = get_sites( array( 'fields' => 'ids', 'limit' => -1 ) );

        foreach ( $sites as $site_id ) {

            $this->check_single_site_quota( $site_id );

        }

    }


    /**

     * Checks the upload quota for a single site and sends an email if near limit.

     *

     * @param int $site_id The ID of the site to check.

     */

    private function check_single_site_quota( $site_id ) {

        switch_to_blog( $site_id );


        $quota_bytes = get_option( 'blog_upload_space' ); // Quota in MB, convert to bytes.

        if ( empty( $quota_bytes ) || $quota_bytes == 0 ) {

            restore_current_blog();

            return; // No quota set for this site, or quota is unlimited.

        }

        $quota_bytes *= MB_IN_BYTES; // Convert MB to bytes.


        $used_bytes = get_option( 'blog_upload_space_used' ); // Used space in MB, convert to bytes.

        $used_bytes *= MB_IN_BYTES; // Convert MB to bytes.


        if ( $quota_bytes <= 0 ) { // Ensure quota is positive to avoid division by zero.

            restore_current_blog();

            return;

        }


        $percentage_used = ( $used_bytes / $quota_bytes ) * 100;


        if ( $percentage_used >= $this->threshold_percentage ) {

            $this->send_quota_notification_email( $site_id, $used_bytes, $quota_bytes, $percentage_used );

        }


        restore_current_blog();

    }


    /**

     * Sends an email notification if the site is near its quota and not recently notified.

     *

     * @param int    $site_id         The ID of the site.

     * @param int    $used_bytes      The amount of storage used in bytes.

     * @param int    $quota_bytes     The total quota in bytes.

     * @param float  $percentage_used The percentage of quota used.

     */

    private function send_quota_notification_email( $site_id, $used_bytes, $quota_bytes, $percentage_used ) {

        $last_notified = get_site_option( $this->option_name_last_notified . '_' . $site_id );

        $current_time = time();


        // Check if enough time has passed since the last notification for this site.

        if ( $last_notified && ( $current_time - $last_notified ) < ( $this->email_interval_hours * HOUR_IN_SECONDS ) ) {

            return; // Too soon to send another email for this site.

        }


        $network_admin_email = get_site_option( 'admin_email' ); // Get the network admin email.

        if ( ! $network_admin_email ) {

            return; // No network admin email set.

        }


        $site_name = get_blog_details( $site_id )->blogname;

        $site_url  = get_blog_details( $site_id )->siteurl;


        $subject = sprintf( __( 'Storage Quota Alert for %s', 'wpmu-storage-quota-monitor' ), $site_name );


        $message = sprintf(

            __( "Hello Network Administrator,\n\n" .

                "The site '%1\$s' (%2\$s) is nearing its upload storage quota.\n\n" .

                "Current Usage: %3\$s MB (%.2f%% of quota)\n" .

                "Total Quota: %4\$s MB\n\n" .

                "Please consider increasing the site's upload space or advising the site administrator to reduce file usage.\n\n" .

                "This is an automated notification.",

                'wpmu-storage-quota-monitor' ),

            $site_name,

            $site_url,

            round( $used_bytes / MB_IN_BYTES, 2 ),

            $percentage_used,

            round( $quota_bytes / MB_IN_BYTES, 2 )

        );


        $headers = array( 'Content-Type: text/plain; charset=UTF-8' );


        // Send the email.

        if ( wp_mail( $network_admin_email, $subject, $message, $headers ) ) {

            // Update the last notified timestamp for this site.

            update_site_option( $this->option_name_last_notified . '_' . $site_id, $current_time );

        }

    }


    /**

     * Adds the settings page to the Network Admin menu.

     */

    public function add_settings_page() {

        add_submenu_page(

            'settings.php', // Parent slug for Network Settings.

            __( 'Storage Quota Monitor Settings', 'wpmu-storage-quota-monitor' ),

            __( 'Quota Monitor', 'wpmu-storage-quota-monitor' ),

            'manage_network_options', // Capability required to access.

            'wpmu-storage-quota-monitor-settings',

            array( $this, 'settings_page_content' )

        );

    }


    /**

     * Registers plugin settings.

     */

    public function register_settings() {

        add_action( 'network_admin_edit_wpmu_storage_quota_monitor_settings', array( $this, 'save_settings' ) );


        add_settings_section(

            'wpmu_sqm_general_settings',

            __( 'General Settings', 'wpmu-storage-quota-monitor' ),

            null,

            'wpmu-storage-quota-monitor-settings'

        );


        add_settings_field(

            'wpmu_sqm_threshold_percentage',

            __( 'Notification Threshold (%)', 'wpmu-storage-quota-monitor' ),

            array( $this, 'threshold_percentage_callback' ),

            'wpmu-storage-quota-monitor-settings',

            'wpmu_sqm_general_settings'

        );


        add_settings_field(

            'wpmu_sqm_email_interval_hours',

            __( 'Email Notification Interval (hours)', 'wpmu-storage-quota-monitor' ),

            array( $this, 'email_interval_hours_callback' ),

            'wpmu-storage-quota-monitor-settings',

            'wpmu_sqm_general_settings'

        );

    }


    /**

     * Callback for the threshold percentage setting field.

     */

    public function threshold_percentage_callback() {

        $threshold = get_site_option( 'wpmu_sqm_threshold_percentage', 80 );

        echo '<input type="number" name="wpmu_sqm_threshold_percentage" min="1" max="100" value="' . esc_attr( $threshold ) . '"/>';

        echo '<p class="description">' . __( 'Send an email notification when a site\'s upload space usage reaches this percentage of its quota.', 'wpmu-storage-quota-monitor' ) . '</p>';

    }


    /**

     * Callback for the email interval hours setting field.

     */

    public function email_interval_hours_callback() {

        $interval = get_site_option( 'wpmu_sqm_email_interval_hours', 24 );

        echo '<input type="number" name="wpmu_sqm_email_interval_hours" min="1" value="' . esc_attr( $interval ) . '"/>';

        echo '<p class="description">' . __( 'How often (in hours) to send email notifications for the same site if it remains above the threshold. Set to 0 for every check.', 'wpmu-storage-quota-monitor' ) . '</p>';

    }


    /**

     * Saves the plugin settings.

     */

    public function save_settings() {

        if ( ! current_user_can( 'manage_network_options' ) ) {

            wp_die( __( 'You do not have sufficient permissions to access this page.', 'wpmu-storage-quota-monitor' ) );

        }


        check_admin_referer( 'wpmu-storage-quota-monitor-settings-options' ); // Verify nonce.


        $threshold = isset( $_POST['wpmu_sqm_threshold_percentage'] ) ? intval( $_POST['wpmu_sqm_threshold_percentage'] ) : 80;

        $interval = isset( $_POST['wpmu_sqm_email_interval_hours'] ) ? intval( $_POST['wpmu_sqm_email_interval_hours'] ) : 24;


        update_site_option( 'wpmu_sqm_threshold_percentage', max( 1, min( 100, $threshold ) ) );

        update_site_option( 'wpmu_sqm_email_interval_hours', max( 0, $interval ) );


        wp_redirect( add_query_arg( array( 'page' => 'wpmu-storage-quota-monitor-settings', 'updated' => 'true' ), network_admin_url( 'settings.php' ) ) );

        exit();

    }



    /**

     * Displays the settings page content.

     */

    public function settings_page_content() {

        ?>

        <div class="wrap">

            <h1><?php _e( 'WPMU Storage Quota Monitor Settings', 'wpmu-storage-quota-monitor' ); ?></h1>

            <form method="post" action="<?php echo esc_url( network_admin_url( 'edit.php?action=wpmu_storage_quota_monitor_settings' ) ); ?>">

                <?php

                settings_fields( 'wpmu-storage-quota-monitor-settings-options' ); // Nonce for the settings page.

                do_settings_sections( 'wpmu-storage-quota-monitor-settings' );

                submit_button();

                ?>

            </form>

        </div>

        <?php

    }

}


// Initialize the plugin.

$wpmu_storage_quota_monitor = new WPMU_Storage_Quota_Monitor();


// Register deactivation hook.

register_deactivation_hook( __FILE__, array( 'WPMU_Storage_Quota_Monitor', 'unschedule_quota_check_on_deactivation' ) );

Katie Keith – Computer Science

Katie Keith

Position: Assistant Professor
Pronouns: she/her
Office: TBL 302A
Phone: 413.597.2832
E-mail: [email protected]
Website

Education

  • Ph.D. University of Massachusetts Amherst, 2021
  • M.S. University of Massachusetts Amherst, 2020
  • B.A. Lewis & Clark College, 2015

Interests

  • Natural language processing
  • Computational social science
  • Machine learning
  • Causal inference
  • Data science

Biography

Prior to Williams, Katherine (Katie) Keith was a Postdoctoral Young Investigator with the Semantic Scholar team at the Allen Institute for Artificial Intelligence. She graduated with a PhD from the College of Information and Computer Sciences at the University of Massachusetts Amherst where she was advised by Brendan O’Connor. Her research interests are at the intersection of natural language processing, computational social science, and causal inference. In the past, she co-organized the First Workshop on Causal Inference and NLP, hosted the podcast Diaries of Social Data Research, and co-organized the first NLP+CSS 201 Online Tutorial Series. She is a recipient of a Bloomberg Data Science PhD fellowship.

Research

Katie’s research is in the domain of social data science, answering questions about human behavior through quantitative analysis of large-scale data. She’s interested in adapting methods from natural language processing to the needs of computational social scientists. Most recently, her work has examined the challenges of using text as high-dimensional causal confounders. She primarily publishes in conference proceedings of the Association for Computational Linguistics (ACL, EMNLP, and NAACL).