Repository: lauzis/mawiblah
Pull request #2: 1.0.10- checking settings if email should be sent
Author: login: lauzis
Created: 2025-04-09T20:09:11Z | Merged: 2025-04-10T19:59:44Z



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

- **New Features**
  - Introduced a configuration control for email notifications. Emails are now sent only when enabled in system settings, giving you improved control over outgoing communications.
  - Added a setting to skip actual email sending for testing and debugging purposes.
  - Added a feature to display settings on the test page.
- **Documentation**
  - Updated change log to reflect new features and improvements in version 1.0.10.
  - Added images illustrating the settings output and settings page.
- **Version Update**
  - Updated the version number to 1.0.10.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/README.md b/README.md
index dbbfcb3..4a6b5de 100644
--- a/README.md
+++ b/README.md
@@ -32,11 +32,19 @@ on all possible configurations and setups.
 
 ## Change log
 
+### --- 1.0.10 ---
+- Implemented setting to skip actual email sending for testing/debugging purposes.
+- Settings output in test page
+
+![Settings output in the test page](readme-assets/settings-output-in-test.jpg)
+
 ### --- 1.0.9 ---
 - Introduced a dedicated settings page in the admin interface, providing a centralized location for configuration.
 - Added options to control email intervals and enable debugging with IP restrictions.
 - Ability to toggle database logging via the settings page.
 - Testing/Shout-out to [coderabit.ai](https://coderabit.ai) for the help with the code. Will see how it goes, but for now it seems helpful.
+  
+![Settings page](readme-assets/settings.jpg)
 
 ### --- 1.0.8 ---
 - saving click time for stats, and so one could check what is most "active" time for opening emails
@@ -68,8 +76,8 @@ used  with some of the letters capitalized
 - added some minimal action logger, for debugging to see why flow of sending out campaign.
 
 ### --- initial MVP ---
-
 Minimal functionality only to achieve my needs. Maybe will make it more universal at later point in time.
+![Mvp version](readme-assets/mvp.jpg)
 
 ## Known issues
 - Texts in unsubscribe scenario is not translated
diff --git a/assets/css/mawiblah.css b/assets/css/mawiblah.css
index 81832d5..527b36e 100644
--- a/assets/css/mawiblah.css
+++ b/assets/css/mawiblah.css
@@ -336,3 +336,10 @@ form.google-analytics-events{
 .mawiblah-section-last {
     border-bottom-width:1px;
 }
+.mawiblah-settings-table th {
+    padding:20px;
+    width: 25%;
+}
+.mawiblah-settings-table td {
+    width: 25%;
+}
diff --git a/classes/Renderer.php b/classes/Renderer.php
index d58a82d..b271b10 100644
--- a/classes/Renderer.php
+++ b/classes/Renderer.php
@@ -80,7 +80,8 @@ public static function campaigns(array|null $debug = null)
                         'notATester' => [],
                         'alreadySent' => [],
                         'notUniqueEmail' => [],
-                        'skippingToPlaceWeLeftOf' => []
+                        'skippingToPlaceWeLeftOf' => [],
+                        'emailSendingIsDisabled' => [],
                     ];
 
                     $iteration = get_post_meta($campaign->id, 'iteration', true);
@@ -126,7 +127,7 @@ public static function campaigns(array|null $debug = null)
                                             'subscriber' => $subscriber,
                                             'uniqueEmails' => $uniqueEmails,
                                             'testMode' => $testMode,
-                                            'skippingReasons' => $skippingReasons
+                                            'skippingReasons' => $skippingReasons,
                                         ]
                                     );
                                     wp_redirect(Helpers::generatePluginUrl(['action' => 'list']));
@@ -192,7 +193,16 @@ public static function campaigns(array|null $debug = null)
                                     'testMode' => $testMode,
                                     'skippingReasons' => $skippingReasons
                                 ]);
-                                $emailSendingResult = wp_mail($email, $campaign->subject, $emailBody);
+
+                                $emailSendingResult = false;
+                                if (Settings::sendEmails()) {
+                                    $emailSendingResult = wp_mail($email, $campaign->subject, $emailBody);
+                                } else {
+                                    $skippingReasons['emailSendingIsDisabled'][] = $email;
+                                    $emailsSkipped++;
+                                    continue;
+                                }
+
                                 if ($emailSendingResult) {
                                     $emailsSent++;
                                     Subscribers::sentEmail($subscriber->id, $campaign->id);
diff --git a/classes/Settings.php b/classes/Settings.php
index 8c77dc8..3db0bbe 100755
--- a/classes/Settings.php
+++ b/classes/Settings.php
@@ -376,4 +376,15 @@ public static function generate_pot_file()
             self::add_message("Pot file generated. You will find it here $potFile");
         }
     }
+
+    public static function sendEmails():bool
+    {
+        $value = get_option("mawiblah-dont-send-email");
+
+        if ($value ==='dont-send-emails') {
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/mawiblah.php b/mawiblah.php
index a963e1e..51063c5 100644
--- a/mawiblah.php
+++ b/mawiblah.php
@@ -10,7 +10,7 @@
  */
 
 if (!defined('MAWIBLAH_VERSION')) {
-    define('MAWIBLAH_VERSION', '1.0.9.' . time());
+    define('MAWIBLAH_VERSION', '1.0.10.' . time());
 }
 
 define('MAWIBLAH_PLUGIN_NAME', 'Mawiblah');
diff --git a/readme-assets/mvp.jpg b/readme-assets/mvp.jpg
new file mode 100644
index 0000000..425f6ee
Binary files /dev/null and b/readme-assets/mvp.jpg differ
diff --git a/readme-assets/settings-output-in-test.jpg b/readme-assets/settings-output-in-test.jpg
new file mode 100644
index 0000000..13b916b
Binary files /dev/null and b/readme-assets/settings-output-in-test.jpg differ
diff --git a/readme-assets/settings.jpg b/readme-assets/settings.jpg
new file mode 100644
index 0000000..ee43fec
Binary files /dev/null and b/readme-assets/settings.jpg differ
diff --git a/templates/tests.php b/templates/tests.php
index eb60a45..ee06f0a 100644
--- a/templates/tests.php
+++ b/templates/tests.php
@@ -1,9 +1,52 @@
 <?php
-    use Mawiblah\Tests;
+
+use Mawiblah\Tests;
+use Mawiblah\Settings;
+
 ?>
 <div class="wrap mawiblah">
-
     <h1>Self Tests</h1>
+
+    <h2> Settings </h2>
+    <?php $settings = Settings::get_sections(); ?>
+    <?php foreach ($settings as $value) : ?>
+        <h3><?= $value['title']; ?></h3>
+        <p><?= $value['description']; ?></p>
+        <?php $fields = $value['fields']; ?>
+        <table class="mawiblah-settings-table">
+            <thead>
+            <tr>
+                <th>Field</th>
+                <th>Id</th>
+                <th>Value</th>
+                <th>Options</th>
+            </tr>
+            </thead>
+            <tbody>
+            <?php foreach ($fields as $field) : ?>
+                <tr>
+                    <td><?= esc_html($field['title']); ?></td>
+                    <td><?= esc_html($field['id']); ?></td>
+                    <td><?= esc_html($field['value']); ?></td>
+                    <td>
+                        <?php if (isset($field['options'])) : ?>
+                            <ul>
+                                <?php foreach ($field['options'] as $option) : ?>
+                                    <li><?= esc_html($option['title']); ?>: <?= esc_html($option['value']); ?></li>
+                                <?php endforeach; ?>
+                            </ul>
+                        <?php else : ?>
+                            No options
+                        <?php endif; ?>
+                    </td>
+                </tr>
+            <?php endforeach; ?>
+            </tbody>
+        </table>
+        <br/>
+    <?php endforeach; ?>
+
+    <h2> Actions </h2>
     <?php Tests::tests(); ?>
 </div>
 


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 a8c3d3a0bcf6cd0d354fc1c37a5215808b605c15 and 056ec3febecaf3abfbb3b6e50ed7d5a4bffcc313.

</details>

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

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

</details>

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

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

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

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

* `Settings` (6-389)
* `sendEmails` (380-388)

</details>

</blockquote></details>

</details>

</details>

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

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

`380-388`: **New method to control email sending looks good**

This new static method correctly implements a settings check to determine whether emails should be sent based on the "mawiblah-dont-send-email" option value, returning a boolean result.

</details>

</blockquote></details>

</details>

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

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

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

`195-198`: _⚠️ Potential issue_

**Fix potential undefined variable issue**

The code now conditionally sends emails based on the settings, which is good. However, if `Settings::sendEmails()` returns false, `$emailSendingResult` won't be defined before it's used on line 200.




Apply this fix to initialize the variable:

```diff
+$emailSendingResult = false;
 if (Settings::sendEmails()) {
     $emailSendingResult = wp_mail($email, $campaign->subject, $emailBody);
 }
```

</blockquote></details>

</blockquote></details>

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

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

`380-388`: **Good implementation of email sending control feature**

The new `sendEmails()` method provides a clean way to toggle email functionality based on a WordPress option. This helps control when emails are sent from the application.



I would suggest adding a brief comment to document the purpose of this option:

```diff
 public static function sendEmails():bool
 {
+    // Check if email sending is disabled via settings
     $value = get_option("mawiblah-dont-send-email");
     if ($value ==='dont-send-emails') {
         return false;
     }

     return true;
 }
```

</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 a8c3d3a0bcf6cd0d354fc1c37a5215808b605c15 and 056ec3febecaf3abfbb3b6e50ed7d5a4bffcc313.

</details>

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

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

</details>

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

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

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

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

* `Settings` (6-389)
* `sendEmails` (380-388)

</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-09T20:11:21Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

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

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

`35-36`: **Enhance Change Log Clarity**

The newly added change log entry for version 1.0.10 clearly documents the update to skip actual email sending for testing/debugging. Consider providing additional context—such as referencing the specific settings option or method—to improve clarity for users reviewing the change history.

</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 056ec3febecaf3abfbb3b6e50ed7d5a4bffcc313 and 6c0628dffa32e05ad0a987ed256b3bb848760448.

</details>

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

* `README.md` (1 hunks)
* `classes/Renderer.php` (3 hunks)
* `classes/Settings.php` (1 hunks)
* `mawiblah.php` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* mawiblah.php

</details>

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

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

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T04:22:22Z
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 6c0628dffa32e05ad0a987ed256b3bb848760448 and 4cadad613dd1e178e8b15504fb00cc23b90f57b2.

</details>

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

* `README.md` (1 hunks)
* `templates/tests.php` (1 hunks)

</details>

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

* README.md

</details>

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

<details>
<summary>templates/tests.php (2)</summary>

`3-3`: **Appropriate use statement added.**

The addition of `use Mawiblah\Settings;` properly imports the Settings class which is used in the new code below.

---

`19-19`: **Good UI organization.**

Adding a separate heading for "Actions" improves the page structure and clearly separates the settings display from the actions/tests section.

</details>

</blockquote></details>

</details>

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

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

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

`339-345`: **Review Comment: Settings Table CSS Additions**

The new rules for `.mawiblah-settings-table` ensure a consistent layout by applying a 20px padding to header cells and a fixed 25% width for both `<th>` and `<td>` elements. This is a good start for styling the settings table. Please verify that a fixed 25% width fits the intended design across various screen sizes. If the table has more or fewer than four columns, you might need to adjust the width or add responsive design rules.

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

`39-40`: **Review Comment: Settings Output Screenshot**

The inclusion of the screenshot for "Settings output in the test page" is a valuable visual aid. For better accessibility, consider adding alternative text (e.g., `alt="Screenshot showing settings output in test page"`) to the markdown image.

---

`47-48`: **Review Comment: Settings Page Screenshot**

The screenshot labeled "Settings page" reinforces the UI update and adds useful context in the documentation. As with the previous image, including an alt text for accessibility purposes would be a good enhancement.

</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 4cadad613dd1e178e8b15504fb00cc23b90f57b2 and addf6917dd4168e6b6be989c37d067c82817d29f.

</details>

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

* `readme-assets/mvp.jpg` is excluded by `!**/*.jpg`
* `readme-assets/settings-output-in-test.jpg` is excluded by `!**/*.jpg`
* `readme-assets/settings.jpg` is excluded by `!**/*.jpg`

</details>

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

* `README.md` (2 hunks)
* `assets/css/mawiblah.css` (1 hunks)
* `templates/tests.php` (1 hunks)

</details>

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

* templates/tests.php

</details>

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

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

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

[typographical] ~45-~45: Consider adding a comma here.
Context: ...ith the code. Will see how it goes, but for now it seems helpful.    ![Settings page](r...

(FOR_NOW_COMMA)

</details>

</details>

</details>

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

<details>
<summary>README.md (2)</summary>

`35-38`: **Review Comment: Changelog Entry for Version 1.0.10**

The updated changelog clearly documents the introduction of a setting to skip actual email sending for testing/debugging, as well as the addition of settings output on the test page. This provides clear context for users reviewing the version history.

---

`80-81`: **Review Comment: MVP Version Screenshot**

The "Mvp version" screenshot effectively illustrates the initial design state. It complements the changelog by visually documenting how the plugin looked in its minimal functionality phase.

</details>

</blockquote></details>

</details>

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

Review comments:
diff_hunk: @@ -192,7 +192,11 @@ public static function campaigns(array|null $debug = null)
                                     'testMode' => $testMode,
                                     'skippingReasons' => $skippingReasons
                                 ]);
-                                $emailSendingResult = wp_mail($email, $campaign->subject, $emailBody);
+
+                                if (Settings::sendEmails()) {
+                                    $emailSendingResult = wp_mail($email, $campaign->subject, $emailBody);
+                                }
path: classes/Renderer.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix potential undefined variable issue**

The code now conditionally sends emails based on the settings, which is good. However, if `Settings::sendEmails()` returns false, `$emailSendingResult` won't be defined before it's used on line 200.


Apply this fix to initialize the variable:

```diff
+$emailSendingResult = false;
 if (Settings::sendEmails()) {
     $emailSendingResult = wp_mail($email, $campaign->subject, $emailBody);
 }
```


<!-- 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
                                $emailSendingResult = false;
                                if (Settings::sendEmails()) {
                                    $emailSendingResult = wp_mail($email, $campaign->subject, $emailBody);
                                }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 6c0628d
created_at: 2025-04-09T20:10:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,9 +1,22 @@
 <?php
     use Mawiblah\Tests;
+    use Mawiblah\Settings;
 ?>
 <div class="wrap mawiblah">
-
     <h1>Self Tests</h1>
+
+    <h2> Settings </h2>
+    <?php $settings = Settings::get_sections(); ?>
+    <?php foreach ($settings as $value) : ?>
+        <h3><?= $value['title']; ?>[<?= $value['id']; ?>]</h3>
+        <?php $fields = $value['fields']; ?>
+        <?php foreach ($fields as $field) : ?>
+            <h4><?= $field['title']; ?> [<?= $field['id']; ?>]</h4>
+            <p>[<?= $field['id']; ?>]=[<?= $field['value']; ?>]</p>
+        <?php endforeach; ?>
+    <?php endforeach; ?>
+
path: templates/tests.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add escaping to prevent potential XSS vulnerabilities.**

The newly added Settings section displays settings values directly without proper escaping, which could lead to XSS vulnerabilities if these values contain malicious content. Additionally, there's no error handling if `Settings::get_sections()` returns null or an empty array.


Consider applying these changes:

```diff
    <h2> Settings </h2>
-   <?php $settings = Settings::get_sections(); ?>
-   <?php foreach ($settings as $value) : ?>
+   <?php 
+   $settings = Settings::get_sections(); 
+   if (is_array($settings) && !empty($settings)) :
+   ?>
+   <?php foreach ($settings as $value) : ?>
        <h3><?= esc_html($value['title']); ?>[<?= esc_html($value['id']); ?>]</h3>
        <?php $fields = $value['fields']; ?>
        <?php foreach ($fields as $field) : ?>
-           <h4><?= $field['title']; ?> [<?= $field['id']; ?>]</h4>
-           <p>[<?= $field['id']; ?>]=[<?= $field['value']; ?>]</p>
+           <h4><?= esc_html($field['title']); ?> [<?= esc_html($field['id']); ?>]</h4>
+           <p>[<?= esc_html($field['id']); ?>]=[<?= esc_html($field['value']); ?>]</p>
        <?php endforeach; ?>
    <?php endforeach; ?>
+   <?php else: ?>
+       <p>No settings found.</p>
+   <?php endif; ?>
```


<!-- 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
    <h2> Settings </h2>
    <?php 
    $settings = Settings::get_sections(); 
    if (is_array($settings) && !empty($settings)) :
    ?>
    <?php foreach ($settings as $value) : ?>
        <h3><?= esc_html($value['title']); ?>[<?= esc_html($value['id']); ?>]</h3>
        <?php $fields = $value['fields']; ?>
        <?php foreach ($fields as $field) : ?>
            <h4><?= esc_html($field['title']); ?> [<?= esc_html($field['id']); ?>]</h4>
            <p>[<?= esc_html($field['id']); ?>]=[<?= esc_html($field['value']); ?>]</p>
        <?php endforeach; ?>
    <?php endforeach; ?>
    <?php else: ?>
        <p>No settings found.</p>
    <?php endif; ?>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-10T19:20:12Z
author_association: CONTRIBUTOR

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

## Walkthrough
The changes modify the email sending flow within the application. In the `Renderer` class, a conditional check now wraps the call to `wp_mail`, executing it only if allowed by the settings. A new static method `sendEmails()` is added to the `Settings` class, which checks an option retrieved via `get_option` to determine whether email sending should occur. The control flow now depends on the result of this settings check. Additionally, updates have been made to the versioning and change log in the `README.md` and `mawiblah.php` files.

## Changes
| File(s)                     | Change Summary                                                                                   |
| --------------------------- | ----------------------------------------------------------------------------------------------- |
| classes/Renderer.php        | Wrapped `wp_mail` call in an `if` statement that checks `Settings::sendEmails()` before sending.  |
| classes/Settings.php        | Added a new public static method `sendEmails()` that returns a boolean based on the `"mawiblah-dont-send-email"` option. |
| README.md                   | Added change log entry for version 1.0.10, detailing the implementation of a setting to skip email sending for testing/debugging. |
| mawiblah.php                | Updated version constant from `1.0.9` to `1.0.10`.                                             |
| templates/tests.php         | Introduced a new section for displaying settings, added a `use` statement for `Mawiblah\Settings`, and structured HTML accordingly. |
| assets/css/mawiblah.css     | Added new CSS rules for styling the settings table header and data cells.                      |

## Possibly related PRs
- lauzis/mawiblah#1: The changes in the main PR are related to the addition of the `sendEmails` method in the `Settings` class, which is directly utilized in the modified `campaigns` method of the `Renderer` class to control email sending logic.

> 🐇 In the meadow where emails play,  
> A new setting brightens the day.  
> With `sendEmails` now in sight,  
> Testing's easier, all feels right!  
> Version bumps, we hop along,  
> Debugging's sweet, and so is our song! 🎶

<!-- walkthrough_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 addf6917dd4168e6b6be989c37d067c82817d29f and acf73cf7c2a5225eaa6839ed822972b3ad329fd5.

</details>

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

* `templates/tests.php` (1 hunks)

</details>

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

* templates/tests.php

</details>

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


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

<!-- internal state 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=2):

- 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-09T20:09:44Z
body: @coderabbitai full review

author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-09T20:09:50Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Full review triggered.

</details>
author_association: CONTRIBUTOR
user: login: lauzis
created_at: 2025-04-10T07:30:25Z
body: @coderabbitai review
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-10T07:30:29Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Full review triggered.

</details>
author_association: CONTRIBUTOR