Repository: lauzis/mawiblah
Pull request #5: 1.0.11 - dont send email to someone that just got email already
Author: login: lauzis
Created: 2025-04-10T20:12:03Z | Merged: 2025-04-15T07:33:21Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Enhanced subscriber management by tracking engagement through recording the last interaction after emails are sent.
  - Integration of Gravity Forms functionality to update subscriber interaction on form submission.
  - Added a "Don't disturb threshold" feature to control the interval before contacting a subscriber again.
  - Added visual representation for "Don't disturb" settings in the plugin interface.

- **Bug Fixes**
  - Corrected typographical errors in method names and variable references.

- **Chores**
  - Updated plugin version to 1.0.11, reflecting improvements in subscriber interaction tracking and email handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/README.md b/README.md
index 4a6b5de..1f7b35f 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ Kind of steep increase as our projects budget  is about 100$ yearly at the momen
 
 So... "Fine... will do my own Mailchimp... with blackjack and hookers"
 
+![Mawiblah name](readme-assets/mawiblah.jpg)
+
 ## What it does
 - Sends out emails to the email list.
 - Email list is collected via Gravity Forms entries. But one could add the mailing list manually.
@@ -32,6 +34,12 @@ on all possible configurations and setups.
 
 ## Change log
 
+### --- 1.0.11 ---
+- Add meta field to the subscribers post type for last interaction, added update after email is sent
+- Added meta settings field to controll the time between emails to the same subscriber
+  
+![dont disturb settings](readme-assets/dont-disturb-threshold.png)
+
 ### --- 1.0.10 ---
 - Implemented setting to skip actual email sending for testing/debugging purposes.
 - Settings output in test page
diff --git a/assets/css/mawiblah.css b/assets/css/mawiblah.css
index 527b36e..feb303a 100644
--- a/assets/css/mawiblah.css
+++ b/assets/css/mawiblah.css
@@ -2,6 +2,10 @@ body {
     background-color: #e2e2e2; /* Different color for the body */
 }
 
+.mawiblah{
+    padding-inline-end:15px;
+}
+
 .create-campaign-form {
     background-color: #f1f1f1; /* Light gray background */
     padding: 20px;
@@ -291,6 +295,8 @@ form.google-analytics-events{
 .mawiblah-section {
     position:relative;
     width:100%;
+    margin-inline-end: 30px;
+    backgroud-color:red;
 }
 
 .mawiblah-section > h1 {
diff --git a/classes/GravityForms.php b/classes/GravityForms.php
index 0a624b7..0ae86b7 100644
--- a/classes/GravityForms.php
+++ b/classes/GravityForms.php
@@ -4,13 +4,31 @@
 
 class GravityForms
 {
+    public static function init()
+    {
+        add_action('gform_after_submission', function ($entry, $form) {
+            foreach ($form['fields'] as $field) {
+                if ($field->type === 'email') {
+                    $emailFieldId = $field->id;
+                    if (!empty($entry[$emailFieldId])) {
+                        $email = $entry[$emailFieldId];
+                        // Perform your logic with the email
+                        $subscriber = Subscribers::getSubscriber($email);
+                        if ($subscriber){
+                            Subscribers::updateLastInteraction($subscriber->id);
+                        }
+                    }
+                }
+            }
+        }, 10, 2);
+    }
     public static function getArrayOfGravityForms(): array
     {
         $GravityForms = \GFAPI::get_forms();
         return $GravityForms;
     }
 
-    public static function getGravityFormWitheEmailFieldIds()
+    public static function getGravityFormWithEmailFieldIds()
     {
         $forms = self::getArrayOfGravityForms();
         $ids = [];
@@ -34,7 +52,7 @@ public static function getGravityFormWitheEmailFieldIds()
     public static function findEmail($email)
     {
 
-        $formsWihtEmailField = self::getGravityFormWitheEmailFieldIds();
+        $formsWihtEmailField = self::getGravityFormWithEmailFieldIds();
         $emails = [];
 
         foreach ($formsWihtEmailField as $form) {
@@ -63,7 +81,7 @@ public static function findEmail($email)
 
     public static function getAllEmails(): array
     {
-        $formsWihtEmailField = self::getGravityFormWitheEmailFieldIds();
+        $formsWihtEmailField = self::getGravityFormWithEmailFieldIds();
         $emails = [];
 
         foreach ($formsWihtEmailField as $form) {
@@ -105,7 +123,7 @@ public static function getAllEmailsForForm($formId): array{
     }
 
 
-    public static function getFormName($formId){
+    public static function getFormName($formId) {
         $form = \GFAPI::get_form($formId);
         return $form['title'];
     }
diff --git a/classes/Renderer.php b/classes/Renderer.php
index b271b10..860dedb 100644
--- a/classes/Renderer.php
+++ b/classes/Renderer.php
@@ -46,7 +46,7 @@ public static function campaigns(array|null $debug = null)
                 if (isset($_GET['campaignId'])) {
                     $startTime = time();
                     $maxTime = ini_get('max_execution_time');
-                    $unsubedAudience = Subscribers::ubsubedAudience();
+                    $unsubedAudience = Subscribers::unsubedAudience();
 
                     $campaignId = $_GET['campaignId'];
 
@@ -82,6 +82,7 @@ public static function campaigns(array|null $debug = null)
                         'notUniqueEmail' => [],
                         'skippingToPlaceWeLeftOf' => [],
                         'emailSendingIsDisabled' => [],
+                        'donNotDisturb' => [],
                     ];
 
                     $iteration = get_post_meta($campaign->id, 'iteration', true);
@@ -118,6 +119,7 @@ public static function campaigns(array|null $debug = null)
                                 if (!$subscriber) {
                                     $subscriber = Subscribers::addSubscriber($email);
                                 }
+
                                 $currentTIme = time();
                                 if ($currentTIme - $startTime > $maxTime - 2) {
                                     Logs::addLog("Campaign stopped {$campaign->post_title} as we are running out of time", "Campaign {$campaign->post_title} stopped",
@@ -134,7 +136,6 @@ public static function campaigns(array|null $debug = null)
                                     die();
                                 }
 
-
                                 if (is_array($subscriber->audiences) && count($subscriber->audiences) > 0) {
                                     $found = false;
                                     foreach ($subscriber->audiences as $audienceId) {
@@ -180,6 +181,17 @@ public static function campaigns(array|null $debug = null)
                                     continue;
                                 }
 
+                                $lastInteraction = $subscriber->lastInteraction;
+                                $doNotDisturbThreshold = Settings::dontDisturbThreshold();
+                                // current tiem did not go over the reshold
+                                if (time() < strtotime($lastInteraction) + $doNotDisturbThreshold) {
+                                    $skippingReasons['donNotDisturb'][] = $email;
+
+                                    if (!$testMode && !$isTester){
+                                        continue;
+                                    }
+                                }
+
                                 Subscribers::sendingEmail($subscriber->id, $campaign->id);
 
                                 $emailBody = Campaigns::fillTemplate($template, $campaign, $subscriber);
diff --git a/classes/Settings.php b/classes/Settings.php
index 3db0bbe..c86ea94 100755
--- a/classes/Settings.php
+++ b/classes/Settings.php
@@ -379,12 +379,32 @@ public static function generate_pot_file()
 
     public static function sendEmails():bool
     {
-        $value = get_option("mawiblah-dont-send-email");
+        return self::getOption("mawiblah-dont-send-email");
+    }
+
+    public static function dontDisturbThreshold(){
+        return self::getOption('mawiblah-dont-disturb-threshold');
+    }
 
-        if ($value ==='dont-send-emails') {
-            return false;
+    public static function getOption($optionId)
+    {
+        // TODO: return default value if option not set
+        $value = get_option($optionId);
+
+        if (!$value) {
+            $sections = self::get_sections();
+            foreach ($sections as $section) {
+                foreach ($section['fields'] as $field) {
+                    if ($field['id'] === $optionId) {
+                        if ($field['type']!=='boolean'){
+                            return $field['default_value'];
+                        }
+                        break;
+                    }
+                }
+            }
         }
 
-        return true;
+        return $value;
     }
 }
diff --git a/classes/Subscribers.php b/classes/Subscribers.php
index af74c29..fedf8f6 100644
--- a/classes/Subscribers.php
+++ b/classes/Subscribers.php
@@ -8,7 +8,33 @@ class Subscribers
     // exampel http://gudlenieks.test/?utm_source=email&utm_medium=email&utm_campaign=monthly-email&mawiblahId=%7BmawiblahId%7D&unsubscribe=%7Bemail%7D
     public static function init()
     {
+        self::registerPostType();
+        add_action('add_meta_boxes', [self::class, 'addMetaBoxes']);
+    }
+
+    public static function addMetaBoxes()
+    {
+
+        add_meta_box(
+            'mawiblahMetadata', // Unique ID for the meta box
+            'Metadata',               // Title of the meta box
+            [self::class, 'renderMetaData'], // Callback function to render the content
+            self::postType(),         // Post type where the meta box will appear
+            'normal',                 // Context (normal, side, advanced)
+            'default'                 // Priority
+        );
+    }
 
+    public static function renderMetaData($post)
+    {
+        // Add your custom output here
+        $metadata = self::getMetaData($post->ID);
+        echo '<div>';
+        echo '<h3>Meta data</h3>';
+        foreach ($metadata as $key => $value) {
+            echo '<p><strong>' . esc_html($key) . ':</strong> ' . esc_html($value) . '</p>';
+        }
+        echo '</div>';
     }
 
     public static function postType()
@@ -63,7 +89,6 @@ public static function registerPostType()
 
         register_post_type(Subscribers::postType(), $args);
 
-
         // Register custom taxonomy
         $taxonomy_labels = [
             'name' => _x('Subscriber Audiences', 'taxonomy general name', 'mawiblah'),
@@ -89,9 +114,41 @@ public static function registerPostType()
         ];
 
         register_taxonomy(Subscribers::postType() . '_category', [Subscribers::postType()], $taxonomy_args);
-
     }
 
+    public static function getMetaKeys()
+    {
+        return [
+            'email' => [
+                'key' => 'email',
+                'default_value' => ''
+            ],
+            'subscriberId' => [
+                'key' => 'subscriberId',
+                'default_value' => ''
+            ],
+            'unsubToken' => [
+                'key' => 'unsubToken',
+                'default_value' => ''
+            ],
+            'unsubed' => [
+                'key' => 'unsubed',
+                'default_value' => ''
+            ],
+            'activity' => [
+                'key' => 'activity',
+                'default_value' => ''
+            ],
+            'activityTotal' => [
+                'key' => 'activityTotal',
+                'default_value' => ''
+            ],
+            'lastInteraction' => [
+                'key' => 'lastInteraction',
+                'default_value' => ''
+            ]
+        ];
+    }
 
     public static function appendMeta($post)
     {
@@ -103,17 +160,27 @@ public static function appendMeta($post)
         $post->unsubed = get_post_meta($post->id, 'unsubed', true);
         $post->activity = get_post_meta($post->id, 'activity', true) ?? 0;
         $post->activityTotal = get_post_meta($post->id, 'activityTotal', true) ?? 0;
+        $post->lastInteraction = get_post_meta($post->id, 'lastInteraction', true) ?? date("Y-m-d H:i:s", 0);
 
-        if (!$post->subscriberId){
+        if (!$post->subscriberId) {
             update_post_meta($post->id, 'subscriberId', md5($post->id));
         }
 
-
-        $post->audiences = get_the_terms( $post->ID, Subscribers::postType() . '_category' );
+        $post->audiences = get_the_terms($post->ID, Subscribers::postType() . '_category');
 
         return $post;
     }
 
+    public static function getMetaData($postId)
+    {
+        $metaKeys = self::getMetaKeys();
+        $metaData = [];
+        foreach ($metaKeys as $key => $value) {
+            $metaData[$key] = get_post_meta($postId, $key, true);
+        }
+        return $metaData;
+    }
+
     public static function getSubscriber($email): object|null
     {
         $subscriber = get_posts([
@@ -189,7 +256,6 @@ public static function unsub($email, $unsubToken, $feedback)
         }
     }
 
-
     public static function validateAudiences(array $audiences): bool
     {
         $gravityForms = GravityForms::getArrayOfGravityForms();
@@ -227,14 +293,14 @@ public static function getGFAudience($gravityFormId, $title = "", $description =
             foreach ($listOfTaxanomies as $taxanomy) {
                 $gfId = get_term_meta($taxanomy->term_id, 'gravityFormsId', true);
                 if ($gfId === $gravityFormId) {
-                    return (object) $taxanomy;
+                    return (object)$taxanomy;
                 }
             }
         }
 
         if (!empty($title)) {
 
-            return (object) self::createAudience($title, $description, $gravityFormId);
+            return (object)self::createAudience($title, $description, $gravityFormId);
         }
 
         return null;
@@ -264,18 +330,18 @@ public static function checkMailchimpUnsubedAudience($email): bool
             return false;
         }
 
-        $file = fopen($mailchimpUnsubFile,"r");
+        $file = fopen($mailchimpUnsubFile, "r");
 
-        while (($data = fgetcsv($file)) !== FALSE)
-        {
-           if ($data[0] === $email) {
-               return true;
-           }
+        while (($data = fgetcsv($file)) !== FALSE) {
+            if ($data[0] === $email) {
+                return true;
+            }
         }
         return false;
     }
 
-    public static function ubsubedAudience(){
+    public static function unsubedAudience()
+    {
         $term = get_term_by('name', 'Unsubed', Subscribers::postType() . '_category');
         if (!$term) {
             $term = wp_insert_term('Unsubed', Subscribers::postType() . '_category');
@@ -287,8 +353,9 @@ public static function isEmailSent(int $subscriberId, int $campaignId): bool
     {
         $sent = get_post_meta($subscriberId, 'sent_' . $campaignId, true);
 
-        return (bool) $sent;
+        return (bool)$sent;
     }
+
     public static function isTester($subscriber): bool
     {
         $testerFlag = get_post_meta($subscriber->id, 'tester', true);
@@ -297,15 +364,16 @@ public static function isTester($subscriber): bool
 
         $inTesterAudience = false;
 
-        foreach($audiences as $audience){
-            if($audience->term_id === $testerCategory->term_id){
+        foreach ($audiences as $audience) {
+            if ($audience->term_id === $testerCategory->term_id) {
                 $inTesterAudience = true;
             }
         }
-        return  $testerFlag || $inTesterAudience;
+        return $testerFlag || $inTesterAudience;
     }
 
-    public static function testerAudience(){
+    public static function testerAudience()
+    {
         $term = get_term_by('name', 'Testers', Subscribers::postType() . '_category');
         if (!$term) {
             $term = wp_insert_term('Testers', Subscribers::postType() . '_category');
@@ -313,22 +381,30 @@ public static function testerAudience(){
         return $term;
     }
 
-    public static function sendingEmail(int $subscriberId, int $campaignId):void
+    public static function updateLastInteraction(int $subscriberId, string|null $interactionDate = null): void
+    {
+        $interactionDate = $interactionDate ?? date("Y-m-d H:i:s");
+        update_post_meta($subscriberId, 'lastInteraction', $interactionDate);
+    }
+
+    public static function sendingEmail(int $subscriberId, int $campaignId): void
     {
         update_post_meta($subscriberId, 'sent_' . $campaignId, 'sending');
     }
 
-    public static function sentEmail(int $subscriberId, int $campaignId):void
+    public static function sentEmail(int $subscriberId, int $campaignId): void
     {
         update_post_meta($subscriberId, 'sent_' . $campaignId, 'sent');
+        self::updateLastInteraction($subscriberId);
     }
 
-    public static function sentEmailFailed(int $subscriberId, int $campaignId):void
+    public static function sentEmailFailed(int $subscriberId, int $campaignId): void
     {
         update_post_meta($subscriberId, 'sent_' . $campaignId, 'failed');
     }
 
-    public static function getSubscriberBySubscriberId ($subscriberId) {
+    public static function getSubscriberBySubscriberId($subscriberId)
+    {
 
         $postsByMeta = get_posts([
             'post_type' => self::postType(),
@@ -344,8 +420,8 @@ public static function getSubscriberBySubscriberId ($subscriberId) {
         return self::appendMeta($postsByMeta[0]);
     }
 
-    public static function linksClicked($subscriberId) {
-
+    public static function linksClicked($subscriberId)
+    {
         $subscriber = self::getSubscriberBySubscriberId($subscriberId);
         $subscriber->activityTotal++;
         update_post_meta($subscriber->id, 'activityTotal', $subscriber->activityTotal);
@@ -364,4 +440,9 @@ public static function linksClicked($subscriberId) {
         return $subscriber->activity;
     }
 
+    public static function dontDisturbThreshold()
+    {
+        return Settings::getOption('mawiblah-dont-disturb-threshold');
+    }
+
 }
diff --git a/classes/Unsubscribe.php b/classes/Unsubscribe.php
index 4344537..bbbafaf 100644
--- a/classes/Unsubscribe.php
+++ b/classes/Unsubscribe.php
@@ -85,7 +85,7 @@ public static function unsubscribeAprooved($subscriberId, $email, $unsubToken)
             } else {
                 if ($subscriber->unsubToken === $unsubToken) {
                     update_post_meta($subscriber->id, 'unsubed', true);
-                    $audience = Subscribers::ubsubedAudience();
+                    $audience = Subscribers::unsubedAudience();
                     Subscribers::addSubscriberToAudience($subscriber->id, $audience->term_id);
                     if (!empty($feedback)) {
                         add_post_meta($subscriber->id, 'unsubed_feedback', $feedback, false);
diff --git a/config/sections.json b/config/sections.json
index 33be220..293013d 100644
--- a/config/sections.json
+++ b/config/sections.json
@@ -1,4 +1,19 @@
 [
+  {
+    "id": "mawiblah-dont-disturn-threshold",
+    "title": "Dont disturb threshold",
+    "description": "The threshold/delta between emails to one subcriber. If the threshold is not reached, the email will not be sent. We dont want to bother subscribers that did recieve email aready lately.",
+    "fields": [
+      {
+        "id": "mawiblah-dont-disturb-threshold",
+        "title": "Time between the subscriber is contacred again (in seconds)",
+        "type": "text",
+        "value": "",
+        "placeholder": "2592000",
+        "default_value": "2592000"
+      }
+    ]
+  },
   {
     "id": "time-between-emails",
     "title": "Time between emails (in seconds)",
@@ -6,7 +21,7 @@
     "fields": [
       {
         "id": "mawiblah-time-between-emails",
-        "title": "Time between emails",
+        "title": "Time between emails (in seconds)",
         "type": "text",
         "value": "",
         "placeholder": "1",
diff --git a/mawiblah.php b/mawiblah.php
index 51063c5..9a88c12 100644
--- a/mawiblah.php
+++ b/mawiblah.php
@@ -10,7 +10,7 @@
  */
 
 if (!defined('MAWIBLAH_VERSION')) {
-    define('MAWIBLAH_VERSION', '1.0.10.' . time());
+    define('MAWIBLAH_VERSION', '1.0.11.' . time());
 }
 
 define('MAWIBLAH_PLUGIN_NAME', 'Mawiblah');
@@ -93,10 +93,11 @@ function mawiblah_init(): void
 
     \Mawiblah\ShortCodes::register();
     \Mawiblah\Unsubscribe::init();
-    \Mawiblah\Subscribers::registerPostType();
+    \Mawiblah\Subscribers::init();
     \Mawiblah\Campaigns::init();
     \Mawiblah\Visits::init();
     \Mawiblah\Logs::init();
+    \Mawiblah\GravityForms::init();
 }
 
 add_action('init', 'mawiblah_init');
diff --git a/readme-assets/dont-disturb-threshold.png b/readme-assets/dont-disturb-threshold.png
new file mode 100644
index 0000000..376534f
Binary files /dev/null and b/readme-assets/dont-disturb-threshold.png differ
diff --git a/readme-assets/mawiblah.jpg b/readme-assets/mawiblah.jpg
new file mode 100644
index 0000000..fef98b6
Binary files /dev/null and b/readme-assets/mawiblah.jpg differ


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between beb6847bb2d8a65de782fd9cb079829537b22451 and 42c8218fa1ac7b198bbacc4be2868a57821b35ba.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `classes/GravityForms.php` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>classes/GravityForms.php (1)</summary><blockquote>

<details>
<summary>classes/Subscribers.php (1)</summary>

* `updateLastInteraction` (318-322)

</details>

</blockquote></details>

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T20:20:03Z
user: login: lauzis
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-10T20:22:26Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T20:23:02Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>classes/Subscribers.php (1)</summary><blockquote>

`317-333`: _🛠️ Refactor suggestion_

**Missing functionality to check recent email recipients.**

While you've implemented storing the last interaction time, there's no method to check if a subscriber has recently received an email and should be skipped for new mailings.


Consider adding a method to check if a subscriber has received an email too recently:

```php
public static function hasRecentInteraction(int $subscriberId, int $minimumIntervalHours = 24): bool
{
    $subscriber = get_post($subscriberId);
    if (!$subscriber) {
        return false; // If subscriber doesn't exist, allow sending
    }
    
    $subscriber = self::appendMeta($subscriber);
    if (empty($subscriber->lastInteraction)) {
        return false; // No previous interaction recorded
    }
    
    $lastInteraction = new \DateTime($subscriber->lastInteraction);
    $now = new \DateTime();
    $interval = $now->diff($lastInteraction);
    $hoursSinceLastInteraction = $interval->h + ($interval->days * 24);
    
    return $hoursSinceLastInteraction < $minimumIntervalHours;
}
```

Then, before sending emails in your campaign logic, you could check:

```php
if (!Subscribers::hasRecentInteraction($subscriberId, $minimumIntervalHours)) {
    // Proceed with sending email
} else {
    // Log skipped subscriber due to recent interaction
    Logs::log("Skipped subscriber #$subscriberId due to recent interaction");
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>classes/GravityForms.php (1)</summary><blockquote>

`114-116`: **Implementation missing for `updateLastInteraction` method.**

This method has been added but currently contains no implementation. Based on the PR title "don't send email to someone that just got email already" and the related implementation in `classes/Subscribers.php`, this method should likely update interaction timestamps for subscribers coming from GravityForms.


Apply this fix to implement the method:

```diff
-    public static function updateLastInteraction(){
-
-    }
+    public static function updateLastInteraction(int $subscriberId, string | null $interactionDate = null): void
+    {
+        // Call the Subscribers class method to maintain consistency
+        Subscribers::updateLastInteraction($subscriberId, $interactionDate);
+    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>README.md (1)</summary><blockquote>

`35-37`: **Enhance changelog description for 1.0.11.**

The current changelog entry mentions adding metadata for last interaction, but it doesn't clearly explain the purpose of this feature - preventing duplicate emails to recent recipients.


Consider enhancing the changelog to make the purpose clearer:

```diff
 ### --- 1.0.11 ---
-Add meta field to the subscribers post type for last interaction, added update after email is sent
+- Add meta field to the subscribers' post type to track last interaction time
+- Implement update mechanism after email is sent to prevent sending duplicate emails to recent recipients
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~36-~36: It seems likely that a singular genitive (’s) apostrophe is missing.
Context: ... --- 1.0.11 --- - Add meta field to the subscribers post type for last interaction, added u...

(AI_HYDRA_LEO_APOSTROPHE_S_XS)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ea1878d0bdf2af4ed03e24f4dc53aef118ac4cca and 42c8218fa1ac7b198bbacc4be2868a57821b35ba.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `README.md` (1 hunks)
* `classes/GravityForms.php` (1 hunks)
* `classes/Subscribers.php` (3 hunks)
* `mawiblah.php` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>classes/GravityForms.php (1)</summary>

```
Learnt from: lauzis
PR: lauzis/mawiblah#5
File: classes/GravityForms.php:114-116
Timestamp: 2025-04-10T20:23:00.911Z
Learning: The `updateLastInteraction` method in the GravityForms class should loop through Gravity Forms entries, check the last interaction date, and only update subscriber records if the new interaction date is more recent than the stored date.
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>classes/GravityForms.php (1)</summary><blockquote>

<details>
<summary>classes/Subscribers.php (1)</summary>

* `updateLastInteraction` (318-322)

</details>

</blockquote></details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>README.md</summary>

[uncategorized] ~36-~36: It seems likely that a singular genitive (’s) apostrophe is missing.
Context: ... --- 1.0.11 --- - Add meta field to the subscribers post type for last interaction, added u...

(AI_HYDRA_LEO_APOSTROPHE_S_XS)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>mawiblah.php (1)</summary>

`13-13`: **LGTM! Version number updated to reflect the new feature.**

The version number has been correctly updated from 1.0.10 to 1.0.11, in line with the new functionality described in README.md.

</details>
<details>
<summary>classes/Subscribers.php (3)</summary>

`106-106`: **LGTM! Proper initialization of the new `lastInteraction` property.**

The implementation correctly retrieves the last interaction date from metadata or defaults to the Unix epoch timestamp if not set.

---

`318-322`: **LGTM! Well-implemented method for updating last interaction.**

The method properly handles the optional interaction date parameter and updates the subscriber's metadata.

---

`332-332`: **LGTM! Appropriate integration with the email sending process.**

The `sentEmail` method now correctly updates the last interaction timestamp when an email is sent, which is crucial for implementing the "don't send email to someone that just got email already" feature.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T20:24:30Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>classes/Subscribers.php (4)</summary><blockquote>

`28-38`: **Meta box rendering implementation looks good.**

The `renderMetaData` method properly retrieves and displays the subscriber metadata in a structured format. Consider enhancing this with more styling or organization for better readability in the admin UI.


```diff
 public static function renderMetaData($post)
 {
     // Add your custom output here
     $metadata = self::getMetaData($post->ID);
-    echo '<div>';
-    echo '<h3>Custom Output Section</h3>';
+    echo '<div class="mawiblah-metadata">';
+    echo '<h3>Subscriber Metadata</h3>';
+    echo '<table class="widefat">';
+    echo '<tbody>';
     foreach ($metadata as $key => $value) {
-        echo '<p><strong>' . esc_html($key) . ':</strong> ' . esc_html($value) . '</p>';
+        echo '<tr><th>' . esc_html(ucfirst($key)) . '</th><td>' . esc_html($value) . '</td></tr>';
     }
+    echo '</tbody>';
+    echo '</table>';
     echo '</div>';
 }
```

---

`343-350`: **Method name has a typo.**

The method is named `ubsubedAudience` (missing an 'n') rather than `unsubedAudience`. This should be corrected for consistency and clarity.

```diff
-public static function ubsubedAudience()
+public static function unsubedAudience()
```

---

`406-421`: **Improved method signature for getSubscriberBySubscriberId.**

The method signature has been updated for consistency with other methods, which is a good practice. However, it would be better to add a specific return type declaration to match other methods.

```diff
-public static function getSubscriberBySubscriberId($subscriberId)
+public static function getSubscriberBySubscriberId($subscriberId): object|null
```

---

`390-393`: **Consider updating lastInteraction in sendingEmail method.**

For consistency with the `sentEmail` method, consider also updating the last interaction timestamp in the `sendingEmail` method.

```diff
 public static function sendingEmail(int $subscriberId, int $campaignId): void
 {
     update_post_meta($subscriberId, 'sent_' . $campaignId, 'sending');
+    self::updateLastInteraction($subscriberId);
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 42c8218fa1ac7b198bbacc4be2868a57821b35ba and 8f3df4cfc10760d8d7c1e6be2cb615634181f5b7.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `classes/GravityForms.php` (4 hunks)
* `classes/Subscribers.php` (8 hunks)
* `mawiblah.php` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* classes/GravityForms.php
* mawiblah.php

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>classes/Subscribers.php (1)</summary><blockquote>

<details>
<summary>classes/Campaigns.php (2)</summary>

* `registerPostType` (113-159)
* `postType` (108-111)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>classes/Subscribers.php (8)</summary>

`11-13`: **Good addition of meta box functionality in the init method.**

Adding meta boxes for displaying subscriber metadata in the WordPress admin interface is a good enhancement to the subscriber management functionality.

---

`15-26`: **Well-implemented meta box registration.**

The implementation follows WordPress standards for registering meta boxes, using appropriate context and priority settings.

---

`119-151`: **Good structure for metadata definition.**

The `getMetaKeys` method provides a clear, structured way to define metadata keys with their default values. Adding the `lastInteraction` metadata is a good enhancement for tracking subscriber engagement.

---

`163-163`: **Good default for lastInteraction.**

Setting a default timestamp of "1970-01-01 00:00:00" for the `lastInteraction` field ensures that all subscribers have a valid timestamp, even if they haven't interacted yet.

---

`174-182`: **Well-implemented metadata retrieval method.**

The `getMetaData` method efficiently retrieves all metadata for a subscriber based on the keys defined in `getMetaKeys`.

---

`384-388`: **Good implementation of lastInteraction updating.**

The `updateLastInteraction` method provides a centralized way to update the interaction timestamp, with a sensible default to the current date and time.

---

`395-399`: **Good update to track email interactions.**

Updating the last interaction timestamp when an email is sent aligns perfectly with the PR objective of preventing duplicate emails to the same recipient within a short time frame.

---

`1-444`: <details>
<summary>❓ Verification inconclusive</summary>

**Verify that email frequency limits are implemented.**

The PR objective mentions preventing sending emails to recipients who recently received one, but I don't see any code that explicitly checks the `lastInteraction` timestamp before sending a new email. Make sure this functionality is implemented elsewhere in the codebase.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for code that might check lastInteraction before sending emails

echo "Searching for lastInteraction usage in email sending context..."
rg -A 10 "lastInteraction" --glob "*.php" | grep -v "update"

echo "Searching for potential email sending conditions..."
rg -A 10 "sentEmail" --glob "*.php" | grep -i "if"
```

Length of output: 1846

---

I'll run a new script to search for conditional logic that might compare the `lastInteraction` timestamp with the current time (e.g., using `time(`) to limit email frequency. This will help us determine if the frequency limiting functionality is implemented elsewhere. Please review the next script output.

---

🏁 Script executed:

```shell
#!/bin/bash
echo "Searching for potential frequency limiting conditions based on time difference from lastInteraction..."
rg -A 10 "time\(" --glob "*.php" | grep "lastInteraction"
```

Length of output: 272

---



</details>

**Action Required: Confirm/Implement Email Frequency Limiting**

After reviewing the code, it’s clear that the methods update the `lastInteraction` timestamp (e.g., via `updateLastInteraction` in `sentEmail()`), but there isn’t any conditional logic that checks this timestamp before sending subsequent emails. This raises a concern with respect to the PR objective—which is to prevent sending emails to recipients who have recently received one.

- The code in `classes/Subscribers.php` updates `lastInteraction` but does not gate or compare it against the current timestamp to enforce a frequency limit.
- Searches for conditional checks (e.g., with `time(` or `if` statements referencing `lastInteraction`) did not reveal any logic that would prevent rapid re-sending.

Please verify if the email frequency limiting functionality is implemented in another part of the codebase, and if not, consider adding a condition to compare the current time against `lastInteraction` to enforce the desired delay between emails.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T22:29:20Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>assets/css/mawiblah.css (1)</summary><blockquote>

`298-299`: **Fix typo in CSS property name**  
The property `backgroud-color` is misspelled and is causing a static analysis error. Change it to `background-color` to ensure correct styling is applied.  

Apply this diff:  
```diff
-    backgroud-color:red;
+    background-color:red;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 299-299: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

</details>

</details>

</blockquote></details>
<details>
<summary>classes/Settings.php (1)</summary><blockquote>

`390-411`: **Consider refining the 'boolean' fallback and completing the TODO.**  
The method partially addresses returning a default value if the option is not set, but if the field type is `'boolean'`, it simply breaks and returns whatever `get_option()` yielded. This could lead to unexpected behavior if `get_option()` returns a falsy value. Also, review the `TODO` comment to ensure a consistent default is returned for *all* field types.

</blockquote></details>
<details>
<summary>classes/Renderer.php (2)</summary><blockquote>

`85-85`: **Refine the key name for uniform readability.**  
The array key `'donNotDisturb'` appears slightly inconsistent. Consider using `'doNotDisturb'` or `'dontDisturb'` to maintain consistent naming style.

---

`189-197`: **Avoid using `die()` in production flow.**  
Calling `die()` here immediately terminates execution, which may not be desirable if other tasks still need to be completed. Consider removing or replacing it with a more graceful exit or logging mechanism.

</blockquote></details>
<details>
<summary>config/sections.json (1)</summary><blockquote>

`5-9`: **Correct typos in the description fields.**  
Words like “subcriber,” “recieve,” “aready,” and “contacred” have typos. Correcting them enhances professionalism and clarity.

</blockquote></details>
<details>
<summary>classes/Subscribers.php (3)</summary><blockquote>

`163-164`: **Revisit default date usage for `lastInteraction`.**  
Setting `lastInteraction` to the Unix epoch may be confusing if the subscriber truly has no prior interaction. Consider a nullable approach or leaving it unset until an actual timestamp is known.

---

`296-303`: **Casting WP_Term to `(object)` may be unnecessary.**  
Typically, WordPress returns `WP_Term` objects already. If further transformation is needed, ensure the final structure is documented or typed clearly to avoid confusion.

---

`443-446`: **Cast or validate the threshold setting.**  
Since `Settings::getOption('mawiblah-dont-disturb-threshold')` can return a string, consider casting to an integer if numeric math is expected.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8f3df4cfc10760d8d7c1e6be2cb615634181f5b7 and 1febe0cdc287d7f2dd25458aade30c76f26fc383.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `readme-assets/dont-disturb-threshold.png` is excluded by `!**/*.png`
* `readme-assets/mawiblah.jpg` is excluded by `!**/*.jpg`

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `README.md` (2 hunks)
* `assets/css/mawiblah.css` (2 hunks)
* `classes/Renderer.php` (3 hunks)
* `classes/Settings.php` (1 hunks)
* `classes/Subscribers.php` (9 hunks)
* `config/sections.json` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* README.md

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>assets/css/mawiblah.css</summary>

[error] 299-299: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>assets/css/mawiblah.css (1)</summary>

`5-7`: **New CSS rule for .mawiblah added**  
The new `.mawiblah` class with `padding-inline-end:15px;` clearly establishes a layout adjustment that could be useful for handling inline spacing in logical layouts. Ensure that your target browsers support CSS logical properties.

</details>
<details>
<summary>classes/Renderer.php (1)</summary>

`184-187`: **Verify that doNotDisturbThreshold is numeric.**  
Since `Settings::dontDisturbThreshold()` may return a string, casting it to an integer (or validating it) before arithmetic will help avoid runtime type issues. Also, if `$lastInteraction` is empty or invalid, `strtotime($lastInteraction)` could be `false`, so ensure that case is handled as well.

</details>
<details>
<summary>config/sections.json (1)</summary>

`24-24`: **Section title updated correctly.**  
The new title clarifies that the time is in seconds. No concerns.

</details>
<details>
<summary>classes/Subscribers.php (1)</summary>

`11-38`: **Meta box addition looks good.**  
Registering the post type and adding meta boxes for displaying subscriber metadata is done correctly. No issues found.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T06:05:52Z

Review comments:
diff_hunk: @@ -109,4 +109,9 @@ public static function getFormName($formId){
         $form = \GFAPI::get_form($formId);
         return $form['title'];
     }
+
+
+    public static function updateLastInteraction(){
+
+    }
path: classes/GravityForms.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Implementation missing for `updateLastInteraction` method.**

This method has been added but contains no implementation. Based on the PR title "don't send email to someone that just got email already" and the related implementation in `classes/Subscribers.php`, this method should likely update interaction timestamps for subscribers coming from GravityForms.


Consider implementing this method similar to the one in Subscribers.php:

```diff
-    public static function updateLastInteraction(){
-
-    }
+    public static function updateLastInteraction(int $subscriberId, string | null $interactionDate = null): void
+    {
+        // Call the Subscribers class method to maintain consistency
+        Subscribers::updateLastInteraction($subscriberId, $interactionDate);
+    }
```

This would ensure consistent behavior between both classes and fulfill the PR's objective of tracking the last time a subscriber received an email.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    public static function updateLastInteraction(int $subscriberId, string | null $interactionDate = null): void
    {
        // Call the Subscribers class method to maintain consistency
        Subscribers::updateLastInteraction($subscriberId, $interactionDate);
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-10T20:20:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -109,4 +109,9 @@ public static function getFormName($formId){
         $form = \GFAPI::get_form($formId);
         return $form['title'];
     }
+
+
+    public static function updateLastInteraction(){
+
+    }
path: classes/GravityForms.php
user: login: lauzis
body: @coderabbitai will need to loop trough gravity forms items to check the last sent email date and theupdate subscriber only if the new lastInteraction date is higher than it has stored

created_at: 2025-04-10T20:22:26Z
author_association: OWNER
diff_hunk: @@ -109,4 +109,9 @@ public static function getFormName($formId){
         $form = \GFAPI::get_form($formId);
         return $form['title'];
     }
+
+
+    public static function updateLastInteraction(){
+
+    }
path: classes/GravityForms.php
user: login: coderabbitai[bot]
body: @lauzis Thanks for the clarification! I'll implement the method to loop through Gravity Forms entries, compare dates, and only update if the new interaction date is more recent. Here's an implementation:

```php
public static function updateLastInteraction($formId = null, string $interactionDate = null): void
{
    // Use current date if none provided
    $interactionDate = $interactionDate ?? date('Y-m-d H:i:s');
    
    // Get emails from specific form or all forms if formId not specified
    $emails = $formId ? self::getAllEmailsForForm($formId) : self::getAllEmails();
    
    foreach ($emails as $email) {
        // Find subscriber ID by email
        $subscriberId = Subscribers::getSubscriberIdByEmail($email);
        
        if (!$subscriberId) {
            continue; // Skip if no subscriber found for this email
        }
        
        // Get current last interaction date
        $currentLastInteraction = Subscribers::getLastInteraction($subscriberId);
        
        // Only update if new date is more recent
        if (strtotime($interactionDate) > strtotime($currentLastInteraction)) {
            Subscribers::updateLastInteraction($subscriberId, $interactionDate);
        }
    }
}
```

This implementation assumes:
1. There's a method `getSubscriberIdByEmail` in the Subscribers class
2. There's a method `getLastInteraction` to retrieve the current timestamp

Let me know if you need adjustments or if these methods don't exist yet!
---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: lauzis
PR: lauzis/mawiblah#5
File: classes/GravityForms.php:114-116
Timestamp: 2025-04-10T20:23:00.737Z
Learning: The `updateLastInteraction` method in the GravityForms class should loop through Gravity Forms entries, check the last interaction date, and only update subscriber records if the new interaction date is more recent than the stored date.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-10T20:23:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -264,18 +330,18 @@ public static function checkMailchimpUnsubedAudience($email): bool
             return false;
         }
 
-        $file = fopen($mailchimpUnsubFile,"r");
+        $file = fopen($mailchimpUnsubFile, "r");
 
-        while (($data = fgetcsv($file)) !== FALSE)
-        {
-           if ($data[0] === $email) {
-               return true;
-           }
+        while (($data = fgetcsv($file)) !== FALSE) {
+            if ($data[0] === $email) {
+                return true;
+            }
         }
         return false;
     }
path: classes/Subscribers.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Close the file after reading CSV to avoid resource leaks.**  
A `fclose($file);` call is missing after the loop. Leaving open file handles can lead to unexpected behavior.  

```diff
 while (($data = fgetcsv($file)) !== FALSE) {
     if ($data[0] === $email) {
         return true;
     }
 }
+fclose($file);
 return false;
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-14T06:05:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-10T20:12:11Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This update introduces new functionality and versioning improvements for the plugin. A new changelog section for version 1.0.11 documents two key enhancements: adding a meta field to the subscribers post type for recording the last interaction and activating an update mechanism after an email is sent. Additionally, the Subscribers class now includes methods for managing subscriber metadata, while the version constant in the main plugin file has been incremented accordingly. The update also integrates Gravity Forms handling, adds a "don't disturb" threshold to control email frequency, and modifies the email sending logic to respect this threshold.

## Changes

| File(s)                        | Change Summary                                                                                                                                                                                                                             |
|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| README.md                     | Added a new changelog section for version 1.0.11 detailing the meta field for last interaction and an update trigger post-email sending, plus an image illustrating the "don't disturb" settings.                                         |
| classes/Subscribers.php       | Introduced several new methods for managing subscriber metadata, including `addMetaBoxes`, `renderMetaData`, `getMetaKeys`, `getMetaData`, and `updateLastInteraction`. Updated `sentEmail` to call `updateLastInteraction`.             |
| mawiblah.php                  | Updated the version constant from `1.0.10` to `1.0.11` with a dynamic timestamp. Replaced `registerPostType` with `init` for the Subscribers class and added `GravityForms::init()` to the initialization sequence.                       |
| classes/GravityForms.php      | Added a new public static method `init` for processing form submissions and renamed `getGravityFormWitheEmailFieldIds` to `getGravityFormWithEmailFieldIds` to fix a typo.                                                                |
| assets/css/mawiblah.css      | Added new CSS class `.mawiblah` with padding and updated `.mawiblah-section` with margin and corrected background color property.                                                                                                        |
| classes/Renderer.php          | Added a new skipping reason `'donNotDisturb'` in the email sending logic to conditionally skip sending emails if the subscriber was contacted recently, based on the "don't disturb" threshold setting.                                    |
| classes/Settings.php          | Simplified `sendEmails()` to directly return an option value. Added `dontDisturbThreshold()` method and a new generalized `getOption($optionId)` method that retrieves option values with fallback to default values from config.         |
| config/sections.json          | Added a new configuration section for "Dont disturb threshold" with a field for specifying the time interval before contacting a subscriber again. Updated the title of the existing "Time between emails" section to include units (seconds). |
| classes/Unsubscribe.php       | Fixed a typo in the `unsubscribeAprooved` method, correcting the call from `ubsubedAudience()` to `unsubedAudience()`.                                                                                                                  |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client as Client Code
    participant EmailSys as Email System
    participant Subs as Subscribers Class
    participant DB as Database

    Client->>EmailSys: Initiate email send
    EmailSys->>Subs: Call sentEmail(subscriberId, campaignId)
    Subs->>Subs: Update last interaction timestamp
    Subs->>DB: Save 'lastInteraction' meta data
```

```mermaid
sequenceDiagram
    participant Gravity as GravityForms Class
    participant Form as Gravity Form
    participant Subs as Subscribers Class
    participant DB as Database

    Form->>Gravity: Form submitted with fields
    Gravity->>Gravity: Check for email fields
    Gravity->>Subs: Retrieve subscriber by email
    Subs->>Subs: Update last interaction timestamp
    Subs->>DB: Save 'lastInteraction' meta data
```

```mermaid
sequenceDiagram
    participant Renderer as Renderer Class
    participant Settings as Settings Class
    participant Subs as Subscribers Class

    Renderer->>Subs: Get subscriber last interaction
    Renderer->>Settings: Get 'dontDisturbThreshold' option
    alt Current time < lastInteraction + threshold
        Renderer->>Renderer: Skip sending email (donNotDisturb)
    else
        Renderer->>Renderer: Proceed with sending email
    end
```

## Possibly related PRs

- **lauzis/mawiblah#2**: Initial implementation of `sendEmails()` setting check and email sending control logic in `Renderer` and `Settings` classes, extended by the current PR to add the "don't disturb" threshold and generalized option retrieval.

## Poem
> I'm a rabbit with a coding zest,  
> Hopping through changes, doing my best.  
> New meta fields meet email flight,  
> Tracking each moment, day and night.  
> Updated version makes my heart sing,  
> Leaping forward, new features in spring!  
> 🐇💻 Enjoy the hop of innovation!

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1febe0cdc287d7f2dd25458aade30c76f26fc383 and 9c17b8be823c89696873f8645d839550b4a2c241.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `classes/Renderer.php` (4 hunks)
* `classes/Settings.php` (1 hunks)
* `classes/Unsubscribe.php` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* classes/Renderer.php
* classes/Settings.php

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>classes/Unsubscribe.php (1)</summary><blockquote>

<details>
<summary>classes/Subscribers.php (2)</summary>

* `Subscribers` (5-448)
* `unsubedAudience` (343-350)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>classes/Unsubscribe.php (1)</summary>

`88-88`: **Good typo fix for method call.**

This change fixes a typo in the method call, correcting it from the non-existent `Subscribers::ubsubedAudience()` to the correct `Subscribers::unsubedAudience()`. The fix ensures proper functionality of the unsubscribe flow by correctly retrieving the "Unsubed" audience term to which unsubscribed users are added.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=lauzis/mawiblah&utm_content=5):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: lauzis
created_at: 2025-04-10T20:19:38Z
body: @coderabbitai full review
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-10T20:19:43Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Full review triggered.

</details>
author_association: CONTRIBUTOR