Plugin Directory

Changeset 3364111


Ignore:
Timestamp:
09/18/2025 04:36:12 PM (5 months ago)
Author:
notificationmaster
Message:

version 1.6.5

Location:
notification-master
Files:
808 added
11 edited

Legend:

Unmodified
Added
Removed
  • notification-master/trunk/dist/index.asset.php

    r3334303 r3364111  
    1 <?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => '5abff50929fd9690d1f3');
     1<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => '84cd8e0f26301394c9f4');
  • notification-master/trunk/dist/index.js

    r3334303 r3364111  
    99__html:(0,l.sprintf)((0,l.__)('If you face any issues with key generation or receiving notifications, please contact us via our <a href="%s" target="_blank" rel="noopener noreferrer">Contact Form</a>.',"notification-master"),`${w}/contact/`)}}),(0,a.jsx)("li",{dangerouslySetInnerHTML:{
    1010/* translators: %s is the documentation url */
    11 __html:(0,l.sprintf)((0,l.__)('For detailed instructions, please refer to the <a href="%s" target="_blank" rel="noopener noreferrer">documentation</a>.',"notification-master"),`${w}/docs/web-push/`)}})]})]})}),(0,a.jsxs)("div",{className:oi()("notification-master__settings--item"),style:{borderBottom:"none",paddingBottom:0},children:[(0,a.jsxs)("div",{className:"notification-master__settings--item--title",style:{flex:1},children:[(0,a.jsx)(J_.Title,{level:5,children:(0,l.__)("Web Push Public Key","notification-master")}),(0,a.jsx)(J_.Text,{children:(0,l.__)("This key is used to identify your web push service. use your own key or generate a new one.","notification-master")})]}),(0,a.jsx)("div",{className:"notification-master__settings--item--switch",style:{flex:1},children:(0,a.jsx)(gk,{value:e.webpush_public_key,onChange:e=>{t("webpush_public_key",e.target.value)}})})]}),(0,a.jsxs)("div",{className:oi()("notification-master__settings--item"),style:{borderBottom:"none",paddingBottom:0},children:[(0,a.jsxs)("div",{className:"notification-master__settings--item--title",style:{flex:1},children:[(0,a.jsx)(J_.Title,{level:5,children:(0,l.__)("Web Push Private Key","notification-master")}),(0,a.jsx)(J_.Text,{children:(0,l.__)("This key is used to authenticate your web push service. use your own key or generate a new one.","notification-master")})]}),(0,a.jsx)("div",{className:"notification-master__settings--item--switch",style:{flex:1},children:(0,a.jsx)(gk,{value:e.webpush_private_key,onChange:e=>{t("webpush_private_key",e.target.value)}})})]}),(0,a.jsxs)("div",{className:oi()("notification-master__settings--item"),children:[(0,a.jsx)(gu,{type:"primary",onClick:async()=>{if(!o){r(!0);try{const e=await fetch(v,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"ntfm_generate_keys",nonce:b,autoSave:p?"yes":"no"})}),o=await e.json();o.success?(n({type:"success",message:(0,l.__)("Keys generated successfully","notification-master")}),p&&window.location.reload(),t("webpush_public_key",o.data.public_key),t("webpush_private_key",o.data.private_key)):n({type:"error",message:o.data.message})}catch(e){n({type:"error",message:e?.message||(0,l.__)("An error occurred","notification-master")})}finally{r(!1)}}},loading:o,children:(0,l.__)("Generate Keys","notification-master")}),(0,a.jsxs)(Tp,{gap:5,children:[(0,a.jsx)(PP,{checked:p,onChange:e=>{h(e)}}),(0,a.jsx)(J_.Text,{children:(0,l.__)("Automatically save the keys after generating.","notification-master")})]})]}),(0,a.jsx)("div",{className:oi()("notification-master__settings--item"),style:{padding:20},children:(0,a.jsxs)("div",{style:{flex:1},children:[(0,a.jsx)("div",{style:{marginBottom:10},children:(0,a.jsx)(J_.Title,{level:5,style:{marginTop:0},children:(0,l.__)("Welcome Notification","notification-master")})}),(0,a.jsxs)(Tp,{gap:20,vertical:!0,children:[(0,a.jsxs)(Tp,{vertical:!0,gap:10,align:"start",children:[(0,a.jsx)(J_.Text,{strong:!0,children:(0,l.__)("Enable Welcome Notification","notification-master")}),(0,a.jsx)(PP,{checked:e.webpush_welcome_notification??!0,onChange:e=>{t("webpush_welcome_notification",e)}})]}),(e.webpush_welcome_notification??!0)&&(0,a.jsx)(nV,{defaultActiveKey:"welcome-notification",items:[{key:"welcome-notification",label:(0,l.__)("Welcome Notification","notification-master"),children:(0,a.jsxs)(Tp,{vertical:!0,gap:20,children:[(0,a.jsxs)(Tp,{vertical:!0,gap:10,align:"start",children:[(0,a.jsx)(J_.Text,{strong:!0,children:(0,l.__)("Title","notification-master")}),(0,a.jsx)(gk,{value:e.webpush_welcome_notification_title,onChange:e=>{t("webpush_welcome_notification_title",e.target.value)}})]}),(0,a.jsxs)(Tp,{vertical:!0,gap:10,align:"start",children:[(0,a.jsx)(J_.Text,{strong:!0,children:(0,l.__)("Message","notification-master")}),(0,a.jsx)(gk.TextArea,{value:e.webpush_welcome_notification_message,onChange:e=>{t("webpush_welcome_notification_message",e.target.value)}})]})]})}]})]})]})}),(0,a.jsx)("div",{className:oi()("notification-master__settings--item"),style:{padding:20},children:(0,a.jsxs)("div",{style:{flex:1},children:[(0,a.jsx)("div",{style:{marginBottom:10},children:(0,a.jsx)(J_.Title,{level:5,style:{marginTop:0},children:(0,l.__)("Subscribe Buttons","notification-master")})}),(0,a.jsxs)(Tp,{gap:20,wrap:"wrap",children:[(0,a.jsx)(zV,{title:(0,l.__)("Normal Button","notification-master"),style:{flex:1},children:(0,a.jsxs)(Tp,{vertical:!0,gap:20,wrap:"wrap",children:[(0,a.jsxs)("div",{children:[(0,a.jsxs)(J_.Text,{children:[(0,l.__)("Use this shortcode to display the subscribe button:","notification-master")," "]}),(0,a.jsx)(J_.Text,{code:!0,children:y})]}),(0,a.jsx)(zV,{children:(0,a.jsx)(Tp,{justify:"center",align:"center",children:(0,a.jsx)("button",{className:oi()("ntfm-subscribe-btn",{subscribed:i},qK`
     11__html:(0,l.sprintf)((0,l.__)('For detailed instructions, please refer to the <a href="%s" target="_blank" rel="noopener noreferrer">documentation</a>.',"notification-master"),`${w}/docs/web-push/`)}})]})]})}),(0,a.jsxs)("div",{className:oi()("notification-master__settings--item"),style:{borderBottom:"none",paddingBottom:0},children:[(0,a.jsxs)("div",{className:"notification-master__settings--item--title",style:{flex:1},children:[(0,a.jsx)(J_.Title,{level:5,children:(0,l.__)("Web Push Public Key","notification-master")}),(0,a.jsx)(J_.Text,{children:(0,l.__)("This key is used to identify your web push service. use your own key or generate a new one.","notification-master")})]}),(0,a.jsx)("div",{className:"notification-master__settings--item--switch",style:{flex:1},children:(0,a.jsx)(gk,{value:e.webpush_public_key,onChange:e=>{t("webpush_public_key",e.target.value)}})})]}),(0,a.jsxs)("div",{className:oi()("notification-master__settings--item"),style:{borderBottom:"none",paddingBottom:0},children:[(0,a.jsxs)("div",{className:"notification-master__settings--item--title",style:{flex:1},children:[(0,a.jsx)(J_.Title,{level:5,children:(0,l.__)("Web Push Private Key","notification-master")}),(0,a.jsx)(J_.Text,{children:(0,l.__)("This key is used to authenticate your web push service. use your own key or generate a new one.","notification-master")})]}),(0,a.jsx)("div",{className:"notification-master__settings--item--switch",style:{flex:1},children:(0,a.jsx)(gk,{value:e.webpush_private_key,onChange:e=>{t("webpush_private_key",e.target.value)}})})]}),(0,a.jsxs)("div",{className:oi()("notification-master__settings--item"),children:[(0,a.jsx)(gu,{type:"primary",onClick:async()=>{if(!o){r(!0);try{const e=await fetch(v,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"ntfm_generate_keys",nonce:b,autoSave:p?"yes":"no"})}),o=await e.json();o.success?(n({type:"success",message:(0,l.__)("Keys generated successfully","notification-master")}),p&&window.location.reload(),t("webpush_public_key",o.data.public_key),t("webpush_private_key",o.data.private_key)):n({type:"error",message:o.data.message})}catch(e){n({type:"error",message:e?.message||(0,l.__)("An error occurred","notification-master")})}finally{r(!1)}}},loading:o,children:(0,l.__)("Generate Keys","notification-master")}),(0,a.jsxs)(Tp,{gap:5,children:[(0,a.jsx)(PP,{checked:p,onChange:e=>{h(e)}}),(0,a.jsx)(J_.Text,{children:(0,l.__)("Automatically save the keys after generating.","notification-master")})]})]}),(0,a.jsx)("div",{className:oi()("notification-master__settings--item"),style:{padding:20},children:(0,a.jsxs)("div",{style:{flex:1},children:[(0,a.jsx)("div",{style:{marginBottom:10},children:(0,a.jsx)(J_.Title,{level:5,style:{marginTop:0},children:(0,l.__)("Welcome Notification","notification-master")})}),(0,a.jsxs)(Tp,{gap:20,vertical:!0,children:[(0,a.jsxs)(Tp,{vertical:!0,gap:10,align:"start",children:[(0,a.jsx)(J_.Text,{strong:!0,children:(0,l.__)("Enable Welcome Notification","notification-master")}),(0,a.jsx)(PP,{checked:e.webpush_welcome_notification??!0,onChange:e=>{t("webpush_welcome_notification",e)}})]}),(e.webpush_welcome_notification??!0)&&(0,a.jsx)(nV,{defaultActiveKey:"welcome-notification",items:[{key:"welcome-notification",label:(0,l.__)("Welcome Notification","notification-master"),children:(0,a.jsxs)(Tp,{vertical:!0,gap:20,children:[(0,a.jsxs)(Tp,{vertical:!0,gap:10,align:"start",children:[(0,a.jsx)(J_.Text,{strong:!0,children:(0,l.__)("Title","notification-master")}),(0,a.jsx)(gk,{value:e.webpush_welcome_notification_title,onChange:e=>{t("webpush_welcome_notification_title",e.target.value)}})]}),(0,a.jsxs)(Tp,{vertical:!0,gap:10,align:"start",children:[(0,a.jsx)(J_.Text,{strong:!0,children:(0,l.__)("Message","notification-master")}),(0,a.jsx)(gk.TextArea,{value:e.webpush_welcome_notification_message,onChange:e=>{t("webpush_welcome_notification_message",e.target.value)}})]})]})}]})]})]})}),(0,a.jsxs)("div",{className:oi()("notification-master__settings--item"),style:{borderBottom:"none",paddingBottom:0},children:[(0,a.jsxs)("div",{className:"notification-master__settings--item--title",children:[(0,a.jsx)(J_.Title,{level:5,children:(0,l.__)("Auto-Delete Failed Subscriptions","notification-master")}),(0,a.jsx)(J_.Text,{children:(0,l.__)("Automatically remove subscriptions that fail repeatedly to keep your database clean and improve delivery performance.","notification-master")})]}),(0,a.jsx)("div",{className:"notification-master__settings--item--switch",children:(0,a.jsx)(PP,{title:(0,l.__)("Enable Auto-Delete","notification-master"),checkedChildren:(0,l.__)("On","notification-master"),unCheckedChildren:(0,l.__)("Off","notification-master"),checked:e.webpush_enable_auto_delete_failed_subscriptions??!1,onChange:e=>{t("webpush_enable_auto_delete_failed_subscriptions",e)}})})]}),(e.webpush_enable_auto_delete_failed_subscriptions??!1)&&(0,a.jsx)("div",{className:"notification-master__settings--item notification-master__settings--item--block",children:(0,a.jsxs)(Tp,{vertical:!0,gap:20,children:[(0,a.jsx)(Tp,{gap:20,wrap:"wrap",children:(0,a.jsxs)(Tp,{vertical:!0,gap:10,style:{flex:1,minWidth:"300px"},children:[(0,a.jsx)(J_.Text,{strong:!0,children:(0,l.__)("Failure Threshold","notification-master")}),(0,a.jsx)(J_.Text,{children:(0,l.__)("Number of consecutive failed notification attempts before automatically deleting the subscription. Recommended: 3-5 failures.","notification-master")}),(0,a.jsx)(gk,{type:"number",min:1,max:50,value:e.webpush_auto_delete_failure_threshold??5,onChange:e=>{const n=parseInt(e.target.value)||5;t("webpush_auto_delete_failure_threshold",Math.min(Math.max(n,1),50))},placeholder:(0,l.__)("Enter number of failures (1-50)","notification-master")})]})}),(0,a.jsx)(Up,{message:(0,l.__)("How it works:","notification-master"),description:(0,a.jsxs)("div",{children:[(0,a.jsx)("p",{style:{marginBottom:"8px"},children:(0,l.__)("• When a notification successfully reaches a subscriber, their failure count resets to 0","notification-master")}),(0,a.jsx)("p",{style:{marginBottom:"8px"},children:(0,l.__)("• When a notification fails (invalid endpoint, expired subscription, etc.), the failure count increases by 1","notification-master")}),(0,a.jsx)("p",{style:{marginBottom:"8px"},children:(0,l.__)("• When the failure count reaches your threshold, the subscription is automatically deleted","notification-master")}),(0,a.jsx)("p",{style:{marginBottom:"0"},children:(0,l.__)("• This helps maintain a clean subscriber list and improves overall delivery rates","notification-master")})]}),type:"info",showIcon:!0})]})}),(0,a.jsx)("div",{className:oi()("notification-master__settings--item"),style:{padding:20},children:(0,a.jsxs)("div",{style:{flex:1},children:[(0,a.jsx)("div",{style:{marginBottom:10},children:(0,a.jsx)(J_.Title,{level:5,style:{marginTop:0},children:(0,l.__)("Subscribe Buttons","notification-master")})}),(0,a.jsxs)(Tp,{gap:20,wrap:"wrap",children:[(0,a.jsx)(zV,{title:(0,l.__)("Normal Button","notification-master"),style:{flex:1},children:(0,a.jsxs)(Tp,{vertical:!0,gap:20,wrap:"wrap",children:[(0,a.jsxs)("div",{children:[(0,a.jsxs)(J_.Text,{children:[(0,l.__)("Use this shortcode to display the subscribe button:","notification-master")," "]}),(0,a.jsx)(J_.Text,{code:!0,children:y})]}),(0,a.jsx)(zV,{children:(0,a.jsx)(Tp,{justify:"center",align:"center",children:(0,a.jsx)("button",{className:oi()("ntfm-subscribe-btn",{subscribed:i},qK`
    1212                                    color: ${e.normal_button_color};
    1313                                    background-color: ${e.normal_button_background_color};
  • notification-master/trunk/includes/class-plugin.php

    r3311895 r3364111  
    9191        // Register tables.
    9292        add_action( 'admin_init', array( $this, 'register_tables' ) );
     93
     94        // Schedule cleanup of failed subscriptions.
     95        add_action( 'ntfm_cleanup_failed_subscriptions', array( $this, 'cleanup_failed_subscriptions' ) );
     96        add_action( 'init', array( $this, 'schedule_failed_subscriptions_cleanup' ) );
    9397    }
    9498
     
    120124        wp_clear_scheduled_hook( 'ntfm_notifications_delete_logs' );
    121125        wp_clear_scheduled_hook( 'ntfm_delete_logs' );
     126        wp_clear_scheduled_hook( 'ntfm_cleanup_failed_subscriptions' );
    122127
    123128        // Flush rewrite rules.
     
    153158        // Load notification logger class.
    154159        $this->notification_logger = Notification_Logger::get_instance();
     160
     161        // Schedule cleanup of failed subscriptions.
     162        $this->schedule_failed_subscriptions_cleanup();
    155163    }
    156164
     
    190198            $subscriptions_table->add_status_column();
    191199        }
     200
     201        // Add failure tracking columns for auto-delete functionality.
     202        if ( version_compare( get_option( 'notification_master_version', '1.0.0' ), '1.6.5', '<' ) ) {
     203            $subscriptions_table->add_failure_tracking_columns();
     204            update_option( 'notification_master_version', NOTIFICATION_MASTER_VERSION );
     205        }
     206    }
     207
     208    /**
     209     * Schedule cleanup of failed subscriptions.
     210     *
     211     * @since 1.5.0
     212     */
     213    public function schedule_failed_subscriptions_cleanup() {
     214        if ( ! wp_next_scheduled( 'ntfm_cleanup_failed_subscriptions' ) ) {
     215            wp_schedule_event( time(), 'daily', 'ntfm_cleanup_failed_subscriptions' );
     216        }
     217    }
     218
     219    /**
     220     * Cleanup old failed subscriptions.
     221     *
     222     * @since 1.5.0
     223     */
     224    public function cleanup_failed_subscriptions() {
     225        // Log that cleanup is running for testing.
     226        $this->logger->info(
     227            'webpush',
     228            __( 'Running cleanup task for failed subscriptions...', 'notification-master' )
     229        );
     230
     231        // Only cleanup if auto-delete is enabled.
     232        $auto_delete_enabled = Settings::get_setting( 'webpush_enable_auto_delete_failed_subscriptions', false );
     233        if ( ! $auto_delete_enabled ) {
     234            $this->logger->info(
     235                'webpush',
     236                __( 'Auto-delete is disabled. Skipping cleanup.', 'notification-master' )
     237            );
     238            return;
     239        }
     240
     241        // Use shorter time for testing (1 day instead of 30 days).
     242        $deleted_count = DB\Models\Subscription_Model::cleanup_old_failed_subscriptions( 1 );
     243
     244        if ( $deleted_count > 0 ) {
     245            $this->logger->info(
     246                'webpush',
     247                sprintf(
     248                    /* translators: %d: number of deleted subscriptions */
     249                    __( 'Cleaned up %d old failed subscriptions.', 'notification-master' ),
     250                    $deleted_count
     251                )
     252            );
     253        } else {
     254            $this->logger->info(
     255                'webpush',
     256                __( 'No old failed subscriptions found to clean up.', 'notification-master' )
     257            );
     258        }
    192259    }
    193260}
  • notification-master/trunk/includes/db/models/class-subscription-model.php

    r3204184 r3364111  
    2424     * @var string
    2525     */
    26     public static $table_name = 'ntfm_web_push_subscriptions';
     26    public static $table_name = 'ntfm_subscriptions';
    2727
    2828    /**
     
    6868            'content_encoding' => $data['content_encoding'],
    6969            'expiration_time'  => $data['expiration_time'],
     70            'failure_count'    => 0,
     71            'last_failure_at'  => null,
    7072            'created_at'       => current_time( 'mysql' ),
    7173            'updated_at'       => current_time( 'mysql' ),
     
    421423        return $_SERVER['HTTP_USER_AGENT'];
    422424    }
     425
     426    /**
     427     * Record notification failure for a subscription.
     428     *
     429     * @since 1.5.0
     430     *
     431     * @param string $endpoint Endpoint.
     432     *
     433     * @return bool
     434     */
     435    public static function record_failure( $endpoint ) {
     436        global $wpdb;
     437
     438        $subscription = self::get_by_endpoint( $endpoint );
     439        if ( ! $subscription ) {
     440            return false;
     441        }
     442
     443        $failure_count = ( $subscription->failure_count ?? 0 ) + 1;
     444
     445        $updated = $wpdb->update(
     446            "{$wpdb->prefix}ntfm_subscriptions",
     447            array(
     448                'failure_count'   => $failure_count,
     449                'last_failure_at' => current_time( 'mysql' ),
     450                'updated_at'      => current_time( 'mysql' ),
     451            ),
     452            array( 'endpoint' => $endpoint ),
     453            array( '%d', '%s', '%s' ),
     454            array( '%s' )
     455        ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- No caching needed.
     456
     457        // Check if subscription should be auto-deleted.
     458        $max_failures        = \Notification_Master\Settings::get_setting( 'webpush_auto_delete_failure_threshold', 5 );
     459        $auto_delete_enabled = \Notification_Master\Settings::get_setting( 'webpush_enable_auto_delete_failed_subscriptions', false );
     460
     461        if ( $auto_delete_enabled && $failure_count >= $max_failures ) {
     462            self::delete_by_endpoint( $endpoint );
     463
     464            // Log the auto-deletion.
     465            if ( function_exists( 'Notification_Master' ) ) {
     466                \Notification_Master\Notification_Master()->logger->info(
     467                    'webpush',
     468                    sprintf(
     469                        /* translators: %1$s: endpoint, %2$d: failure count */
     470                        __( 'Auto-deleted subscription %1$s after %2$d failed attempts.', 'notification-master' ),
     471                        $endpoint,
     472                        $failure_count
     473                    )
     474                );
     475            }
     476        }
     477
     478        return $updated !== false;
     479    }
     480
     481    /**
     482     * Record notification success for a subscription (resets failure count).
     483     *
     484     * @since 1.5.0
     485     *
     486     * @param string $endpoint Endpoint.
     487     *
     488     * @return bool
     489     */
     490    public static function record_success( $endpoint ) {
     491        global $wpdb;
     492
     493        $updated = $wpdb->update(
     494            "{$wpdb->prefix}ntfm_subscriptions",
     495            array(
     496                'failure_count'   => 0,
     497                'last_failure_at' => null,
     498                'updated_at'      => current_time( 'mysql' ),
     499            ),
     500            array( 'endpoint' => $endpoint ),
     501            array( '%d', '%s', '%s' ),
     502            array( '%s' )
     503        ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- No caching needed.
     504
     505        return $updated !== false;
     506    }
     507
     508    /**
     509     * Delete subscription by endpoint.
     510     *
     511     * @since 1.5.0
     512     *
     513     * @param string $endpoint Endpoint.
     514     *
     515     * @return bool
     516     */
     517    public static function delete_by_endpoint( $endpoint ) {
     518        global $wpdb;
     519
     520        $deleted = $wpdb->delete(
     521            "{$wpdb->prefix}ntfm_subscriptions",
     522            array( 'endpoint' => $endpoint ),
     523            array( '%s' )
     524        ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- No caching needed.
     525
     526        return $deleted !== false;
     527    }
     528
     529    /**
     530     * Get subscriptions with high failure counts for monitoring.
     531     *
     532     * @since 1.5.0
     533     *
     534     * @param int $threshold Failure count threshold.
     535     *
     536     * @return array
     537     */
     538    public static function get_high_failure_subscriptions( $threshold = 3 ) {
     539        global $wpdb;
     540
     541        $results = $wpdb->get_results(
     542            $wpdb->prepare(
     543                "SELECT * FROM {$wpdb->prefix}ntfm_subscriptions WHERE failure_count >= %d AND status = 'subscribed' ORDER BY failure_count DESC, last_failure_at DESC",
     544                $threshold
     545            )
     546        ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- No caching needed.
     547
     548        return $results;
     549    }
     550
     551    /**
     552     * Clean up old failed subscriptions.
     553     *
     554     * @since 1.5.0
     555     *
     556     * @param int $days Number of days to keep failed subscriptions.
     557     *
     558     * @return int Number of deleted subscriptions.
     559     */
     560    public static function cleanup_old_failed_subscriptions( $days = 30 ) {
     561        global $wpdb;
     562
     563        $date_threshold = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );
     564        $max_failures   = \Notification_Master\Settings::get_setting( 'webpush_auto_delete_failure_threshold', 5 );
     565
     566        $deleted = $wpdb->query(
     567            $wpdb->prepare(
     568                "DELETE FROM {$wpdb->prefix}ntfm_subscriptions WHERE failure_count >= %d AND last_failure_at <= %s",
     569                $max_failures,
     570                $date_threshold
     571            )
     572        ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- No caching needed.
     573
     574        return $deleted;
     575    }
    423576}
  • notification-master/trunk/includes/db/tables/class-subscriptions-table.php

    r3164453 r3364111  
    5757            'p256dh',
    5858            'status',
     59            'failure_count',
     60            'last_failure_at',
    5961            'created_at',
    6062            'updated_at',
     
    8688            p256dh VARCHAR(255) NOT NULL,
    8789            `status` VARCHAR(255) NULL DEFAULT 'subscribed',
     90            failure_count INT(11) NULL DEFAULT 0,
     91            last_failure_at TIMESTAMP NULL DEFAULT NULL,
    8892            expiration_time TIMESTAMP NULL DEFAULT NULL,
    8993            content_encoding VARCHAR(255) NULL DEFAULT NULL,
     
    9195            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    9296            PRIMARY KEY (id),
    93             KEY `endpoint` (`endpoint`)
     97            KEY `endpoint` (`endpoint`),
     98            KEY `status` (`status`),
     99            KEY `failure_count` (`failure_count`)
    94100        ) $charset_collate;";
    95101
     
    115121        $wpdb->query( "ALTER TABLE {$wpdb->prefix}{$this->table_name} ADD status VARCHAR(255) NULL DEFAULT 'subscribed' AFTER p256dh" );
    116122    }
     123
     124    /**
     125     * Add failure tracking columns.
     126     *
     127     * @since 1.5.0
     128     *
     129     * @return void
     130     */
     131    public function add_failure_tracking_columns() {
     132        global $wpdb;
     133
     134        // Check if failure_count column exists.
     135        $failure_count_exists = $wpdb->get_results( "SHOW COLUMNS FROM {$wpdb->prefix}{$this->table_name} LIKE 'failure_count'" );
     136        if ( empty( $failure_count_exists ) ) {
     137            $wpdb->query( "ALTER TABLE {$wpdb->prefix}{$this->table_name} ADD failure_count INT(11) NULL DEFAULT 0 AFTER status" );
     138        }
     139
     140        // Check if last_failure_at column exists.
     141        $last_failure_at_exists = $wpdb->get_results( "SHOW COLUMNS FROM {$wpdb->prefix}{$this->table_name} LIKE 'last_failure_at'" );
     142        if ( empty( $last_failure_at_exists ) ) {
     143            $wpdb->query( "ALTER TABLE {$wpdb->prefix}{$this->table_name} ADD last_failure_at TIMESTAMP NULL DEFAULT NULL AFTER failure_count" );
     144        }
     145
     146        // Add indexes if they don't exist.
     147        $indexes     = $wpdb->get_results( "SHOW INDEX FROM {$wpdb->prefix}{$this->table_name}" );
     148        $index_names = wp_list_pluck( $indexes, 'Key_name' );
     149
     150        if ( ! in_array( 'status', $index_names, true ) ) {
     151            $wpdb->query( "ALTER TABLE {$wpdb->prefix}{$this->table_name} ADD KEY `status` (`status`)" );
     152        }
     153
     154        if ( ! in_array( 'failure_count', $index_names, true ) ) {
     155            $wpdb->query( "ALTER TABLE {$wpdb->prefix}{$this->table_name} ADD KEY `failure_count` (`failure_count`)" );
     156        }
     157    }
    117158}
  • notification-master/trunk/includes/integrations/class-webpush-integration.php

    r3204210 r3364111  
    169169     */
    170170    function get_valid_user_ids( $values ) {
     171        if ( is_string( $values ) ) {
     172            $values = explode( ',', $values );
     173            $values = array_map( 'trim', $values );
     174            if ( empty( $values ) ) {
     175                return array();
     176            }
     177        }
     178
    171179        $ids = array();
    172180
     
    296304
    297305            if ( $report->isSuccess() ) {
     306                // Record success and reset failure count.
     307                Subscription_Model::record_success( $endpoint );
     308
    298309                Notification_Master()->notification_logger->success(
    299310                    $this->slug,
     
    306317                );
    307318            } else {
     319                // Record failure and potentially auto-delete subscription.
     320                Subscription_Model::record_failure( $endpoint );
     321
    308322                Notification_Master()->notification_logger->error(
    309323                    $this->slug,
  • notification-master/trunk/includes/rest-api/controllers/v1/class-rest-settings-controller.php

    r3241165 r3364111  
    7676            'type'       => 'object',
    7777            'properties' => array(
    78                 'enable_background_processing'    => array(
     78                'enable_background_processing'          => array(
    7979                    'description' => __( 'Enable background processing.', 'notification-master' ),
    8080                    'type'        => 'boolean',
    8181                ),
    82                 'post_status_change_trigger'      => array(
     82                'post_status_change_trigger'            => array(
    8383                    'type'        => 'boolean',
    8484                    'description' => __( 'Post status change trigger.', 'notification-master' ),
    8585                ),
    86                 'post_types'                      => array(
     86                'post_types'                            => array(
    8787                    'type'        => 'array',
    8888                    'description' => __( 'Post types.', 'notification-master' ),
     
    9191                    ),
    9292                ),
    93                 'taxonomy_term_change_trigger'    => array(
     93                'taxonomy_term_change_trigger'          => array(
    9494                    'type'        => 'boolean',
    9595                    'description' => __( 'Taxonomy term change trigger.', 'notification-master' ),
    9696                ),
    97                 'taxonomies'                      => array(
     97                'taxonomies'                            => array(
    9898                    'type'        => 'array',
    9999                    'description' => __( 'Taxonomies.', 'notification-master' ),
     
    102102                    ),
    103103                ),
    104                 'comment_change_trigger'          => array(
     104                'comment_change_trigger'                => array(
    105105                    'type'        => 'boolean',
    106106                    'description' => __( 'Comment change trigger.', 'notification-master' ),
    107107                ),
    108                 'comment_types'                   => array(
     108                'comment_types'                         => array(
    109109                    'type'        => 'array',
    110110                    'description' => __( 'Comment types.', 'notification-master' ),
     
    113113                    ),
    114114                ),
    115                 'user_change_trigger'             => array(
     115                'user_change_trigger'                   => array(
    116116                    'type'        => 'boolean',
    117117                    'description' => __( 'User change trigger.', 'notification-master' ),
    118118                ),
    119                 'theme_change_trigger'            => array(
     119                'theme_change_trigger'                  => array(
    120120                    'type'        => 'boolean',
    121121                    'description' => __( 'Theme change trigger.', 'notification-master' ),
    122122                ),
    123                 'plugin_change_trigger'           => array(
     123                'plugin_change_trigger'                 => array(
    124124                    'type'        => 'boolean',
    125125                    'description' => __( 'Plugin change trigger.', 'notification-master' ),
    126126                ),
    127                 'media_change_trigger'            => array(
     127                'media_change_trigger'                  => array(
    128128                    'type'        => 'boolean',
    129129                    'description' => __( 'Media change trigger.', 'notification-master' ),
    130130                ),
    131                 'privacy_trigger'                 => array(
     131                'privacy_trigger'                       => array(
    132132                    'type'        => 'boolean',
    133133                    'description' => __( 'Privacy trigger.', 'notification-master' ),
    134134                ),
    135                 'woocommerce_change_trigger'      => array(
     135                'woocommerce_change_trigger'            => array(
    136136                    'type'        => 'boolean',
    137137                    'description' => __( 'WooCommerce change trigger.', 'notification-master' ),
    138138                ),
    139                 'delete_logs_every'               => array(
     139                'delete_logs_every'                     => array(
    140140                    'type'        => 'integer',
    141141                    'description' => __( 'Delete logs every.', 'notification-master' ),
    142142                ),
    143                 'notifications_delete_logs_every' => array(
     143                'notifications_delete_logs_every'       => array(
    144144                    'type'        => 'integer',
    145145                    'description' => __( 'Notifications delete logs every.', 'notification-master' ),
    146146                ),
    147                 'webpush_public_key'              => array(
     147                'webpush_public_key'                    => array(
    148148                    'type'        => 'string',
    149149                    'description' => __( 'Web Push Public Key.', 'notification-master' ),
    150150                ),
    151                 'webpush_private_key'             => array(
     151                'webpush_private_key'                   => array(
    152152                    'type'        => 'string',
    153153                    'description' => __( 'Web Push Private Key.', 'notification-master' ),
    154154                ),
    155                 'webpush_action_type'             => array(
     155                'webpush_action_type'                   => array(
    156156                    'type'        => 'string',
    157157                    'description' => __( 'Web Push Action Type.', 'notification-master' ),
     158                ),
     159                'webpush_enable_auto_delete_failed_subscriptions' => array(
     160                    'type'        => 'boolean',
     161                    'description' => __( 'Enable automatic deletion of failed subscriptions.', 'notification-master' ),
     162                ),
     163                'webpush_auto_delete_failure_threshold' => array(
     164                    'type'        => 'integer',
     165                    'description' => __( 'Number of consecutive failures before auto-deleting subscription.', 'notification-master' ),
     166                    'minimum'     => 1,
     167                    'maximum'     => 50,
     168                ),
     169                'auto_delete_after_failed_attempts'     => array(
     170                    'type'        => 'integer',
     171                    'description' => __( 'Auto delete subscription after failed attempts.', 'notification-master' ),
    158172                ),
    159173            ),
     
    176190                'webpush_private_key'                      => '',
    177191                'webpush_auto_prompt'                      => false,
     192                'webpush_enable_auto_delete_failed_subscriptions' => false,
     193                'webpush_auto_delete_failure_threshold'    => 5,
    178194                'woocommerce_change_trigger'               => true,
    179195                'normal_button_text'                       => __( 'Subscribe to Notifications!', 'notification-master' ),
     
    188204                'normal_button_extra_class'                => '',
    189205                'normal_button_id'                         => '',
    190                 'enable_floating_button'                   => false,
     206                'enable_floating_button'                   => true,
    191207                'enable_floating_button_animation'         => true,
    192208                'enable_floating_button_tooltip'           => true,
  • notification-master/trunk/includes/webpush/class-loader.php

    r3241165 r3364111  
    324324     */
    325325    public function get_floating_button_settings() {
    326         $enabled                = Settings::get_setting( 'enable_floating_button', false );
     326        $enabled                = Settings::get_setting( 'enable_floating_button', true );
    327327        $animation              = Settings::get_setting( 'enable_floating_button_animation', true );
    328328        $tooltip                = Settings::get_setting( 'enable_floating_button_tooltip', true );
  • notification-master/trunk/languages/notification-master.pot

    r3334303 r3364111  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Notification Master 1.6.3\n"
     5"Project-Id-Version: Notification Master 1.6.5\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/notification-master\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2025-07-25T18:41:47+00:00\n"
     12"POT-Creation-Date: 2025-09-18T16:25:08+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.10.0\n"
     
    187187msgstr ""
    188188
     189#: includes/class-plugin.php:228
     190msgid "Running cleanup task for failed subscriptions..."
     191msgstr ""
     192
     193#: includes/class-plugin.php:236
     194msgid "Auto-delete is disabled. Skipping cleanup."
     195msgstr ""
     196
     197#. translators: %d: number of deleted subscriptions
     198#: includes/class-plugin.php:249
     199msgid "Cleaned up %d old failed subscriptions."
     200msgstr ""
     201
     202#: includes/class-plugin.php:256
     203msgid "No old failed subscriptions found to clean up."
     204msgstr ""
     205
    189206#: includes/class-utils.php:98
    190207#: includes/class-utils.php:126
     
    223240#: dist/index.js:1
    224241msgid "Privacy"
     242msgstr ""
     243
     244#. translators: %1$s: endpoint, %2$d: failure count
     245#: includes/db/models/class-subscription-model.php:470
     246msgid "Auto-deleted subscription %1$s after %2$d failed attempts."
    225247msgstr ""
    226248
     
    417439
    418440#: includes/integrations/class-webhook-integration.php:142
    419 #: includes/integrations/class-webpush-integration.php:303
     441#: includes/integrations/class-webpush-integration.php:314
    420442msgid "Notification sent successfully."
    421443msgstr ""
     
    15921614msgstr ""
    15931615
    1594 #: includes/rest-api/controllers/v1/class-rest-settings-controller.php:179
     1616#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:161
     1617msgid "Enable automatic deletion of failed subscriptions."
     1618msgstr ""
     1619
     1620#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:165
     1621msgid "Number of consecutive failures before auto-deleting subscription."
     1622msgstr ""
     1623
     1624#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:171
     1625msgid "Auto delete subscription after failed attempts."
     1626msgstr ""
     1627
     1628#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:195
    15951629#: includes/webpush/class-loader.php:285
    15961630msgid "Subscribe to Notifications!"
    15971631msgstr ""
    15981632
    1599 #: includes/rest-api/controllers/v1/class-rest-settings-controller.php:187
    1600 #: includes/rest-api/controllers/v1/class-rest-settings-controller.php:194
     1633#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:203
     1634#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:210
    16011635#: includes/webpush/class-loader.php:293
    16021636#: includes/webpush/class-loader.php:330
     
    16051639msgstr ""
    16061640
    1607 #: includes/rest-api/controllers/v1/class-rest-settings-controller.php:193
     1641#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:209
    16081642#: includes/webpush/class-loader.php:329
    16091643msgid "Subscribe!"
    16101644msgstr ""
    16111645
    1612 #: includes/rest-api/controllers/v1/class-rest-settings-controller.php:228
     1646#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:244
    16131647#: includes/webpush/class-loader.php:100
    16141648msgid "Welcome!"
    16151649msgstr ""
    16161650
    1617 #: includes/rest-api/controllers/v1/class-rest-settings-controller.php:229
     1651#: includes/rest-api/controllers/v1/class-rest-settings-controller.php:245
    16181652#: includes/webpush/class-loader.php:101
    16191653msgid "Thanks for subscribing to our notifications!"
     
    21512185
    21522186#: dist/index.js:1
     2187#: dist/index.js:11
    21532188#: dist/index.js:38
    21542189msgid "On"
     
    21562191
    21572192#: dist/index.js:1
     2193#: dist/index.js:11
    21582194#: dist/index.js:38
    21592195msgid "Off"
     
    23502386
    23512387#: dist/index.js:11
     2388msgid "Auto-Delete Failed Subscriptions"
     2389msgstr ""
     2390
     2391#: dist/index.js:11
     2392msgid "Automatically remove subscriptions that fail repeatedly to keep your database clean and improve delivery performance."
     2393msgstr ""
     2394
     2395#: dist/index.js:11
     2396msgid "Enable Auto-Delete"
     2397msgstr ""
     2398
     2399#: dist/index.js:11
     2400msgid "Failure Threshold"
     2401msgstr ""
     2402
     2403#: dist/index.js:11
     2404msgid "Number of consecutive failed notification attempts before automatically deleting the subscription. Recommended: 3-5 failures."
     2405msgstr ""
     2406
     2407#: dist/index.js:11
     2408msgid "Enter number of failures (1-50)"
     2409msgstr ""
     2410
     2411#: dist/index.js:11
     2412msgid "How it works:"
     2413msgstr ""
     2414
     2415#: dist/index.js:11
     2416msgid "• When a notification successfully reaches a subscriber, their failure count resets to 0"
     2417msgstr ""
     2418
     2419#: dist/index.js:11
     2420msgid "• When a notification fails (invalid endpoint, expired subscription, etc.), the failure count increases by 1"
     2421msgstr ""
     2422
     2423#: dist/index.js:11
     2424msgid "• When the failure count reaches your threshold, the subscription is automatically deleted"
     2425msgstr ""
     2426
     2427#: dist/index.js:11
     2428msgid "• This helps maintain a clean subscriber list and improves overall delivery rates"
     2429msgstr ""
     2430
     2431#: dist/index.js:11
    23522432msgid "Subscribe Buttons"
    23532433msgstr ""
  • notification-master/trunk/notifications-master.php

    r3334308 r3364111  
    55 * Description: Enhance user engagement. Trigger notifications for events, support multiple channels like email and Discord, and personalize with dynamic merge tags. Easy setup and customization.
    66 *
    7  * Version: 1.6.4
     7 * Version: 1.6.5
    88 *
    99 * Author: Notification Master
     
    2626
    2727// Define notification-master constants.
    28 define( 'NOTIFICATION_MASTER_VERSION', '1.6.4' );
     28define( 'NOTIFICATION_MASTER_VERSION', '1.6.5' );
    2929define( 'NOTIFICATION_MASTER_FILE', __FILE__ );
    3030define( 'NOTIFICATION_MASTER_DIR', plugin_dir_path( __FILE__ ) );
  • notification-master/trunk/readme.txt

    r3334308 r3364111  
    1 === Notification Master - All-in-One WordPress Notifications ===
     1=== Notification Master - Real-Time WordPress Notifications With Email, SMS, WhatsApp & More ===
    22Contributors: notificationmaster
    33Donate link: https://notification-master.com
    44Tags: web push, email, notifications, sms, whatsapp
    5 Stable tag: 1.6.4
     5Stable tag: 1.6.5
    66Requires at least: 4.9
    77Tested up to: 6.8
     
    183183== Changelog ==
    184184
     185= 1.6.5 =
     186* Added: Smart auto-delete system for failed web push subscriptions to maintain clean subscriber lists and improve delivery rates.
     187* Added: Configurable failure threshold setting (1-50 attempts) before automatic subscription removal.
     188* Added: Success tracking that resets failure count when notifications are delivered successfully.
     189* Added: Automatic cleanup of old failed subscriptions with daily maintenance task.
     190* Enhanced: Web push notification reliability with intelligent subscription management.
     191* Enhanced: Database performance with new indexes for subscription status and failure tracking.
     192
    185193= 1.6.4 =
    186194* Fixed: Small bug in readme.txt file.
     
    307315
    308316== Upgrade Notice ==
    309 The latest version of Notification Master (1.6.3) includes fully responsive design optimizations for all settings pages, ensuring better mobile usability and an improved user experience across all devices.
     317Notification Master 1.6.5 introduces an intelligent auto-delete system for failed web push subscriptions. This major enhancement automatically maintains clean subscriber lists by removing inactive subscriptions after configurable failure thresholds, significantly improving notification delivery rates and database performance. The system intelligently resets failure counts when notifications succeed, ensuring only truly inactive subscriptions are removed.
Note: See TracChangeset for help on using the changeset viewer.