Skip to content

Commit 20d0f08

Browse files
uddhabhcursoragent
andcommitted
Release 1.4.0: transactional email layer and admin updates
- Add Mailer, EventRegistry, and HTML email wrapper template - Wire plugin bootstrap and settings for email-related behavior - Bump PARTNER_PROGRAM_VERSION and plugin header to 1.4.0 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9c0f1d9 commit 20d0f08

10 files changed

Lines changed: 1150 additions & 42 deletions

File tree

partner-program.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: Partner Program for WooCommerce
44
* Plugin URI: https://beenacle.com/partner-program
55
* Description: White-label, fully configurable affiliate / partner program for WooCommerce. Tiered commissions, coupon attribution, hold periods, manual payouts, compliance gating, and a private partner portal. By Beenacle.
6-
* Version: 1.3.0
6+
* Version: 1.4.0
77
* Requires at least: 6.2
88
* Requires PHP: 7.4
99
* Author: Beenacle
@@ -26,7 +26,7 @@
2626
return;
2727
}
2828

29-
define( 'PARTNER_PROGRAM_VERSION', '1.3.0' );
29+
define( 'PARTNER_PROGRAM_VERSION', '1.4.0' );
3030
define( 'PARTNER_PROGRAM_FILE', __FILE__ );
3131
define( 'PARTNER_PROGRAM_DIR', plugin_dir_path( __FILE__ ) );
3232
define( 'PARTNER_PROGRAM_URL', plugin_dir_url( __FILE__ ) );

src/Admin/AffiliatesScreen.php

Lines changed: 363 additions & 4 deletions
Large diffs are not rendered by default.

src/Admin/Settings.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace PartnerProgram\Admin;
1111

1212
use PartnerProgram\Domain\TierResolver;
13+
use PartnerProgram\Emails\EventRegistry;
1314
use PartnerProgram\Support\Capabilities;
1415
use PartnerProgram\Support\SettingsRepo;
1516

@@ -42,6 +43,7 @@ public static function render_page(): void {
4243
'application' => __( 'Application Form', 'partner-program' ),
4344
'compliance' => __( 'Compliance', 'partner-program' ),
4445
'exclusions' => __( 'Exclusions', 'partner-program' ),
46+
'emails' => __( 'Emails', 'partner-program' ),
4547
'logs' => __( 'Logs', 'partner-program' ),
4648
'iotools' => __( 'Import / Export', 'partner-program' ),
4749
];
@@ -95,6 +97,7 @@ public static function render_page(): void {
9597
case 'application': self::tab_application( $settings ); break;
9698
case 'compliance': self::tab_compliance( $settings ); break;
9799
case 'exclusions': self::tab_exclusions( $settings ); break;
100+
case 'emails': self::tab_emails( $settings ); break;
98101
case 'logs': self::tab_logs( $settings ); break;
99102
}
100103

@@ -242,6 +245,96 @@ private static function tab_tracking( SettingsRepo $s ): void {
242245
echo '</table>';
243246
}
244247

248+
private static function tab_emails( SettingsRepo $s ): void {
249+
echo '<p class="description">' . esc_html__( 'Customize transactional emails sent by the plugin. Leave subject or body blank to use the built-in default. Tokens like {program_name} are replaced at send time — see each event\'s help text for available tokens.', 'partner-program' ) . '</p>';
250+
251+
echo '<h2>' . esc_html__( 'Sender', 'partner-program' ) . '</h2>';
252+
echo '<table class="form-table">';
253+
self::field_text(
254+
'emails_from_name',
255+
__( 'From name', 'partner-program' ),
256+
(string) $s->get( 'emails.from_name', '' ),
257+
__( 'Defaults to the program name.', 'partner-program' )
258+
);
259+
self::field_text(
260+
'emails_from_email',
261+
__( 'From email', 'partner-program' ),
262+
(string) $s->get( 'emails.from_email', '' ),
263+
__( 'Defaults to the support email.', 'partner-program' ),
264+
'email'
265+
);
266+
printf(
267+
'<tr><th scope="row"><label for="emails_footer_text">%s</label></th><td><textarea id="emails_footer_text" name="emails_footer_text" rows="2" cols="60">%s</textarea><p class="description">%s</p></td></tr>',
268+
esc_html__( 'Footer text', 'partner-program' ),
269+
esc_textarea( (string) $s->get( 'emails.footer_text', '' ) ),
270+
esc_html__( 'Shown at the bottom of every email. Leave blank for the default.', 'partner-program' )
271+
);
272+
echo '</table>';
273+
274+
echo '<h2>' . esc_html__( 'Events', 'partner-program' ) . '</h2>';
275+
276+
foreach ( EventRegistry::all() as $key => $event ) {
277+
$config = (array) $s->get( 'emails.events.' . $key, [] );
278+
$enabled = array_key_exists( 'enabled', $config ) ? (bool) $config['enabled'] : (bool) $event['default_enabled'];
279+
$subject = (string) ( $config['subject'] ?? '' );
280+
$body = (string) ( $config['body'] ?? '' );
281+
282+
$tokens_html = '';
283+
foreach ( $event['tokens'] as $token => $token_desc ) {
284+
$tokens_html .= sprintf(
285+
'<li><code>%s</code> — %s</li>',
286+
esc_html( $token ),
287+
esc_html( $token_desc )
288+
);
289+
}
290+
291+
$audience_label = 'admin' === $event['audience']
292+
? __( 'Sent to admin', 'partner-program' )
293+
: __( 'Sent to partner', 'partner-program' );
294+
295+
?>
296+
<details class="pp-email-event" <?php echo $enabled ? 'open' : ''; ?> style="border:1px solid #c3c4c7;background:#fff;padding:8px 16px;margin:0 0 12px;">
297+
<summary style="cursor:pointer;font-weight:600;padding:6px 0;">
298+
<?php echo esc_html( $event['label'] ); ?>
299+
<span style="font-weight:400;color:#646970;margin-left:8px;">— <?php echo esc_html( $audience_label ); ?></span>
300+
</summary>
301+
<p class="description" style="margin-top:4px;"><?php echo esc_html( $event['description'] ); ?></p>
302+
<table class="form-table">
303+
<tr>
304+
<th scope="row"><?php esc_html_e( 'Enabled', 'partner-program' ); ?></th>
305+
<td>
306+
<label>
307+
<input type="checkbox" name="emails_events[<?php echo esc_attr( $key ); ?>][enabled]" value="1" <?php checked( $enabled ); ?> />
308+
<?php esc_html_e( 'Send this email', 'partner-program' ); ?>
309+
</label>
310+
</td>
311+
</tr>
312+
<tr>
313+
<th scope="row"><label for="emails_subject_<?php echo esc_attr( $key ); ?>"><?php esc_html_e( 'Subject', 'partner-program' ); ?></label></th>
314+
<td>
315+
<input type="text" id="emails_subject_<?php echo esc_attr( $key ); ?>" name="emails_events[<?php echo esc_attr( $key ); ?>][subject]" value="<?php echo esc_attr( $subject ); ?>" class="large-text" placeholder="<?php echo esc_attr( $event['subject'] ); ?>" />
316+
<p class="description"><?php esc_html_e( 'Leave blank to use the default shown as placeholder.', 'partner-program' ); ?></p>
317+
</td>
318+
</tr>
319+
<tr>
320+
<th scope="row"><label for="emails_body_<?php echo esc_attr( $key ); ?>"><?php esc_html_e( 'Body', 'partner-program' ); ?></label></th>
321+
<td>
322+
<textarea id="emails_body_<?php echo esc_attr( $key ); ?>" name="emails_events[<?php echo esc_attr( $key ); ?>][body]" rows="8" class="large-text code" placeholder="<?php echo esc_attr( $event['body'] ); ?>"><?php echo esc_textarea( $body ); ?></textarea>
323+
<p class="description"><?php esc_html_e( 'Plain text or basic HTML. Leave blank to use the default.', 'partner-program' ); ?></p>
324+
</td>
325+
</tr>
326+
<tr>
327+
<th scope="row"><?php esc_html_e( 'Available tokens', 'partner-program' ); ?></th>
328+
<td>
329+
<ul style="margin:0;padding-left:18px;font-size:13px;line-height:1.6;"><?php echo $tokens_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></ul>
330+
</td>
331+
</tr>
332+
</table>
333+
</details>
334+
<?php
335+
}
336+
}
337+
245338
private static function tab_logs( SettingsRepo $s ): void {
246339
echo '<table class="form-table">';
247340
self::field_text(
@@ -762,6 +855,27 @@ public function handle_save(): void {
762855
] );
763856
break;
764857

858+
case 'emails':
859+
$raw_events = isset( $_POST['emails_events'] ) && is_array( $_POST['emails_events'] )
860+
? wp_unslash( (array) $_POST['emails_events'] )
861+
: [];
862+
$clean_events = [];
863+
foreach ( EventRegistry::all() as $event_key => $event_def ) {
864+
$row = is_array( $raw_events[ $event_key ] ?? null ) ? $raw_events[ $event_key ] : [];
865+
$clean_events[ $event_key ] = [
866+
'enabled' => ! empty( $row['enabled'] ),
867+
'subject' => sanitize_text_field( (string) ( $row['subject'] ?? '' ) ),
868+
'body' => wp_kses_post( (string) ( $row['body'] ?? '' ) ),
869+
];
870+
}
871+
$repo->save_section( 'emails', [
872+
'from_name' => sanitize_text_field( wp_unslash( (string) ( $_POST['emails_from_name'] ?? '' ) ) ),
873+
'from_email' => sanitize_email( wp_unslash( (string) ( $_POST['emails_from_email'] ?? '' ) ) ),
874+
'footer_text' => sanitize_textarea_field( wp_unslash( (string) ( $_POST['emails_footer_text'] ?? '' ) ) ),
875+
'events' => $clean_events,
876+
] );
877+
break;
878+
765879
case 'logs':
766880
$repo->save_section( 'logs', [
767881
'retention_days' => max( 0, (int) ( $_POST['logs_retention_days'] ?? 90 ) ),

src/Application/ApplicationForm.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,6 @@ public function handle_submission(): void {
162162

163163
do_action( 'partner_program_application_submitted', $application_id, $data );
164164

165-
$this->notify_admin( $application_id, $data );
166-
167165
$this->redirect_back(
168166
__( 'Thanks for applying. We will review your application and email you when you are approved.', 'partner-program' ),
169167
'success'
@@ -207,19 +205,6 @@ static function ( callable $rename ) use ( $key, $allowed ) {
207205
return (int) $attachment_id;
208206
}
209207

210-
private function notify_admin( int $application_id, array $data ): void {
211-
$settings = new SettingsRepo();
212-
$to = (string) $settings->get( 'general.support_email', get_option( 'admin_email' ) );
213-
$program = (string) $settings->get( 'general.program_name', __( 'Partner Program', 'partner-program' ) );
214-
$subject = sprintf( '[%s] %s', $program, __( 'New partner application', 'partner-program' ) );
215-
$body = __( 'A new partner application has been submitted.', 'partner-program' ) . "\n\n";
216-
foreach ( $data as $k => $v ) {
217-
$body .= $k . ': ' . ( is_scalar( $v ) ? (string) $v : wp_json_encode( $v ) ) . "\n";
218-
}
219-
$body .= "\n" . admin_url( 'admin.php?page=partner-program-applications&id=' . $application_id );
220-
wp_mail( $to, $subject, $body );
221-
}
222-
223208
private function redirect_back( string $message, string $type = 'success' ): void {
224209
set_transient( 'pp_apply_flash_' . $this->flash_key(), [ 'message' => $message, 'type' => $type ], 60 );
225210
$ref = wp_get_referer();

src/Application/ApplicationReview.php

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use PartnerProgram\Domain\AgreementRepo;
1414
use PartnerProgram\Domain\ApplicationRepo;
1515
use PartnerProgram\Support\Capabilities;
16-
use PartnerProgram\Support\SettingsRepo;
1716

1817
defined( 'ABSPATH' ) || exit;
1918

@@ -52,6 +51,7 @@ public function handle_review(): void {
5251
'reviewed_at' => current_time( 'mysql', true ),
5352
]
5453
);
54+
do_action( 'partner_program_application_rejected', $application_id, $notes );
5555
}
5656

5757
$args = [ 'page' => 'partner-program-applications', 'id' => $application_id ];
@@ -146,26 +146,6 @@ private function approve( array $application, string $notes ) {
146146
);
147147

148148
do_action( 'partner_program_affiliate_approved', $affiliate_id );
149-
$this->send_welcome_email( (int) $user_id, $affiliate_id );
150149
return true;
151150
}
152-
153-
private function send_welcome_email( int $user_id, int $affiliate_id ): void {
154-
$user = get_user_by( 'id', $user_id );
155-
if ( ! $user ) {
156-
return;
157-
}
158-
$settings = new SettingsRepo();
159-
$program = (string) $settings->get( 'general.program_name', __( 'Partner Program', 'partner-program' ) );
160-
$portal_id = (int) get_option( 'partner_program_portal_page_id' );
161-
$portal_url = $portal_id ? get_permalink( $portal_id ) : home_url( '/partner-portal/' );
162-
163-
$subject = sprintf( __( 'You are approved for %s', 'partner-program' ), $program );
164-
$body = sprintf( __( 'Hi %s,', 'partner-program' ), $user->display_name ) . "\n\n";
165-
$body .= sprintf( __( 'Your application for the %s has been approved.', 'partner-program' ), $program ) . "\n";
166-
$body .= __( 'Log in to your partner portal to grab your referral link, coupon code, and marketing materials:', 'partner-program' ) . "\n";
167-
$body .= $portal_url . "\n\n";
168-
169-
wp_mail( $user->user_email, $subject, $body );
170-
}
171151
}

src/Core/Plugin.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PartnerProgram\Application\PrivateUploads;
1717
use PartnerProgram\Cli\Commands;
1818
use PartnerProgram\Compliance\AgreementManager;
19+
use PartnerProgram\Emails\Mailer;
1920
use PartnerProgram\Frontend\Portal;
2021
use PartnerProgram\Payouts\PayoutManager;
2122
use PartnerProgram\Rest\RestController;
@@ -117,6 +118,7 @@ private function boot_subsystems(): void {
117118
( new CommissionEngine() )->register();
118119
( new PayoutManager() )->register();
119120
( new AgreementManager() )->register();
121+
( new Mailer() )->register();
120122
( new RestController() )->register();
121123
}
122124
}

0 commit comments

Comments
 (0)