diff --git a/src/Controllers/ChimpleController.php b/src/Controllers/ChimpleController.php index 09c9b9f..46b5e8c 100644 --- a/src/Controllers/ChimpleController.php +++ b/src/Controllers/ChimpleController.php @@ -31,20 +31,11 @@ */ class ChimpleController extends PageController { - /** - * @var string - */ - private static $url_segment = 'mc-subscribe/v1'; + private static string $url_segment = 'mc-subscribe/v1'; - /** - * @var bool - */ - private static $hide_generic_form = true; + private static bool $hide_generic_form = true; - /** - * @var array - */ - private static $allowed_actions = [ + private static array $allowed_actions = [ 'SubscribeForm', 'XhrSubscribeForm' ]; @@ -66,14 +57,11 @@ public function index() public function pageTitle($complete = null) { - switch($complete) { - case 'y': - return _t(__CLASS__. '.DEFAULT_TITLE_SUCCESSFUL', 'Thanks for subscribing'); - case 'n': - return _t(__CLASS__. '.DEFAULT_TITLE_NOT_SUCCESSFUL', 'Sorry, there was an error'); - default: - return _t(__CLASS__. '.DEFAULT_TITLE', 'Subscribe'); - } + return match ($complete) { + 'y' => _t(self::class. '.DEFAULT_TITLE_SUCCESSFUL', 'Thanks for subscribing'), + 'n' => _t(self::class. '.DEFAULT_TITLE_NOT_SUCCESSFUL', 'Sorry, there was an error'), + default => _t(self::class. '.DEFAULT_TITLE', 'Subscribe'), + }; } public function Link($action = null) @@ -120,11 +108,8 @@ public function setFormNameSuffix(string $suffix = ''): self */ public function getFormNameSuffix(): string { - if($this->formNameSuffix) { - $suffix = "_{$this->formNameSuffix}"; - } else { - $suffix = ""; - } + $suffix = $this->formNameSuffix ? "_{$this->formNameSuffix}" : ""; + return $suffix; } @@ -133,11 +118,8 @@ public function getFormNameSuffix(): string */ public function getSubscriptionForm($useXhr = false): ?SubscribeForm { - if($useXhr) { - $form = $this->XhrSubscribeForm(); - } else { - $form = $this->SubscribeForm(); - } + $form = $useXhr ? $this->XhrSubscribeForm() : $this->SubscribeForm(); + return $form; } @@ -230,17 +212,16 @@ protected function configureForm(SubscribeForm $form): SubscribeForm */ protected function getFields(): FieldList { - $fields = FieldList::create( - $name = TextField::create('Name', _t(__CLASS__. '.NAME', 'Name')) - ->setAttribute('placeholder', _t(__CLASS__. '.YOUR_NAME', 'Your name')) - ->setAttribute('title', _t(__CLASS__. '.NAME', 'Name')) + return FieldList::create( + $name = TextField::create('Name', _t(self::class. '.NAME', 'Name')) + ->setAttribute('placeholder', _t(self::class. '.YOUR_NAME', 'Your name')) + ->setAttribute('title', _t(self::class. '.NAME', 'Name')) ->setAttribute('required', 'required'), - $email = EmailField::create('Email', _t(__CLASS__. '.EMAIL', 'Email')) - ->setAttribute('placeholder', _t(__CLASS__. '.EMAIL_ADDRESS', 'Email address')) - ->setAttribute('title', _t(__CLASS__. '.EMAIL', 'Email')) + $email = EmailField::create('Email', _t(self::class. '.EMAIL', 'Email')) + ->setAttribute('placeholder', _t(self::class. '.EMAIL_ADDRESS', 'Email address')) + ->setAttribute('title', _t(self::class. '.EMAIL', 'Email')) ->setAttribute('required', 'required') ); - return $fields; } /** @@ -248,14 +229,13 @@ protected function getFields(): FieldList */ protected function getActions(): FieldList { - $actions = FieldList::create( + return FieldList::create( FormAction::create( 'subscribe', - _t(__CLASS__ . '.SUBSCRIBE', 'Subscribe') + _t(self::class . '.SUBSCRIBE', 'Subscribe') )->setUseButtonTag(true) ->addExtraClass('signup') ); - return $actions; } /** @@ -271,14 +251,13 @@ protected function getValidator(): ?Validator * Returns the validation callback upon errors * A response is returned only upon errors in XHR submissions * See FormRequestHandler::getValidationErrorResponse(); - * @return callable */ protected function getCallbackForXhrValidation(): callable { return function (ValidationResult $result) { // Fail, using the first message returned from the validation result $messages = $result->getMessages(); - $message = (!empty($messages[0]['message']) ? $messages[0]['message'] : ''); + $message = (empty($messages[0]['message']) ? '' : $messages[0]['message']); return $this->xhrError(400, $message); }; } @@ -308,26 +287,26 @@ private function handleError($code, $error_message, Form $form = null) { if($this->request->isAjax()) { return $this->xhrError($code, $error_message); - } elseif($form) { + } elseif($form instanceof \SilverStripe\Forms\Form) { // set session error on the form $form->sessionError($error_message, ValidationResult::TYPE_ERROR); } - return; + return null; } /** * Handle successful submissions, based on the request type */ - private function handleSuccess($code, $message, Form $form = null) + private function handleSuccess(int $code, Form $form = null) { $success_message = Config::inst()->get(MailchimpConfig::class, 'success_message'); if($this->request->isAjax()) { - return $this->xhrSuccess($code, $message, $success_message); - } elseif($form) { + return $this->xhrSuccess($code, $success_message); + } elseif($form instanceof \SilverStripe\Forms\Form) { // set session message on the form $form->sessionMessage($success_message, ValidationResult::TYPE_GOOD); } - return; + return null; } /** @@ -342,7 +321,7 @@ public function subscribe($data = [], Form $form = null) $code = "";// MailchimpConfig.Code $list_id = ""; - if(!$form) { + if(!$form instanceof \SilverStripe\Forms\Form) { throw new RequestException("Forbidden", 403); } @@ -351,14 +330,14 @@ public function subscribe($data = [], Form $form = null) if(empty($data['code'])) { // fail with error $error_message = _t( - __CLASS__ . '.NO_CODE', + self::class . '.NO_CODE', "No code was provided" ); $error_code = 400;// default to invalid data } else { - $code = strip_tags(trim($data['code'] ?: '')); + $code = strip_tags(trim((string) ($data['code'] ?: ''))); $error_message = ""; $error_code = 400;// default to invalid data $mc_config = null; @@ -368,7 +347,7 @@ public function subscribe($data = [], Form $form = null) $enabled = MailchimpConfig::isEnabled(); if(!$enabled) { $error_message = _t( - __CLASS__ . '.SUBSCRIPTIONS_NOT_AVAILABLE', + self::class . '.SUBSCRIPTIONS_NOT_AVAILABLE', "Subscriptions are not available at the moment" ); } @@ -378,16 +357,17 @@ public function subscribe($data = [], Form $form = null) if (empty($data['Email'])) { // fail with error $error_message = _t( - __CLASS__ . '.NO_EMAIL_ADDRESS', + self::class . '.NO_EMAIL_ADDRESS', "No e-mail address was provided" ); } + if (!Email::is_valid_address($data['Email'])) { $error_message = _t( - __CLASS__ . '.INVALID_EMAIL_ADDRESS', + self::class . '.INVALID_EMAIL_ADDRESS', "Please provide a valid e-mail address, '{email}' is not valid", [ - 'email' => htmlspecialchars($data['Email']) + 'email' => htmlspecialchars((string) $data['Email']) ] ); } @@ -395,26 +375,27 @@ public function subscribe($data = [], Form $form = null) if (!$error_message) { // check code provided - if (!$code) { + if ($code === '' || $code === '0') { $error_message = _t( - __CLASS__ . ".GENERIC_ERROR_1", + self::class . ".GENERIC_ERROR_1", "Sorry, the sign-up could not be completed" ); } else { $mc_config = MailchimpConfig::getConfig('', '', $code); if (empty($mc_config->ID)) { $error_message = _t( - __CLASS__ . ".GENERIC_ERROR_2", + self::class . ".GENERIC_ERROR_2", "Sorry, the sign-up could not be completed" ); } + $list_id = $mc_config->getMailchimpListId(); } } if (!$list_id) { $error_message = _t( - __CLASS__ . ".GENERIC_ERROR_3", + self::class . ".GENERIC_ERROR_3", "Sorry, the sign-up could not be completed" ); } @@ -457,7 +438,7 @@ public function subscribe($data = [], Form $form = null) } // handle a successful subscription - $response = $this->handleSuccess(200, "OK", $form); + $response = $this->handleSuccess(200, $form); if($response && ($response instanceof HTTPResponse)) { // handle responses for e.g XHR return $response; @@ -473,7 +454,7 @@ public function subscribe($data = [], Form $form = null) } catch (RequestException $e) { $error_message = $e->getMessage(); $error_code = $e->getCode(); - } catch (\Exception $e) { + } catch (\Exception) { // general exceptin $error_message = Config::inst()->get(MailchimpConfig::class, 'error_message'); $error_code = 500; @@ -497,11 +478,10 @@ public function subscribe($data = [], Form $form = null) /** * Return error response for XHR - * @return HTTPResponse */ - private function xhrError($code = 500, $message = "", $description = "") + private function xhrError($code = 500, $message = ""): \SilverStripe\Control\HTTPResponse { - $response = new HTTPResponse(); + $response = \SilverStripe\Control\HTTPResponse::create(); $response->setStatusCode($code); $response->addHeader('Content-Type', 'application/json'); $response->addHeader('X-Submission-OK', '0'); @@ -511,11 +491,10 @@ private function xhrError($code = 500, $message = "", $description = "") /** * Return success response for XHR - * @return HTTPResponse */ - private function xhrSuccess($code = 200, $message = "", $description = "") + private function xhrSuccess(int $code = 200, $description = ""): \SilverStripe\Control\HTTPResponse { - $response = new HTTPResponse(); + $response = \SilverStripe\Control\HTTPResponse::create(); $response->setStatusCode($code); $response->addHeader('Content-Type', 'application/json'); $response->addHeader('X-Submission-OK', '1'); diff --git a/src/Controllers/MailchimpAdmin.php b/src/Controllers/MailchimpAdmin.php index 8d40dc6..3f65815 100644 --- a/src/Controllers/MailchimpAdmin.php +++ b/src/Controllers/MailchimpAdmin.php @@ -13,12 +13,12 @@ */ class MailchimpAdmin extends ModelAdmin { - private static $managed_models = [ + private static array $managed_models = [ MailchimpSubscriber::class, MailchimpConfig::class ]; - private static $menu_title = 'Mailchimp'; + private static string $menu_title = 'Mailchimp'; - private static $url_segment = 'mailchimp'; + private static string $url_segment = 'mailchimp'; } diff --git a/src/Extensions/DisableSecurityTokenExtension.php b/src/Extensions/DisableSecurityTokenExtension.php index 2321f1b..eb4bc68 100644 --- a/src/Extensions/DisableSecurityTokenExtension.php +++ b/src/Extensions/DisableSecurityTokenExtension.php @@ -14,6 +14,6 @@ class DisableSecurityTokenExtension extends Extension { public function updateChimpleSubscribeForm() { - $this->owner->disableSecurityToken(); + $this->getOwner()->disableSecurityToken(); } } diff --git a/src/Extensions/PageExtension.php b/src/Extensions/PageExtension.php index 562e465..fa7c50d 100644 --- a/src/Extensions/PageExtension.php +++ b/src/Extensions/PageExtension.php @@ -20,6 +20,7 @@ public function ChimpleGlobalSubscribeForm() if ($config) { return $config->SubscribeForm(); } + return null; } @@ -34,6 +35,7 @@ public function ChimpleSubscribeForm($config_code) if($config) { return $config->SubscribeForm(); } + return null; } } diff --git a/src/Extensions/SiteConfigExtension.php b/src/Extensions/SiteConfigExtension.php index d014b12..64b9d3b 100644 --- a/src/Extensions/SiteConfigExtension.php +++ b/src/Extensions/SiteConfigExtension.php @@ -11,14 +11,15 @@ class SiteConfigExtension extends DataExtension { - private static $db = [ + private static array $db = [ 'MailchimpEnabled' => 'Boolean' ]; - private static $has_one = [ + private static array $has_one = [ 'MailchimpConfig' => MailchimpConfig::class // global element for configuration ]; + #[\Override] public function updateCmsFields(FieldList $fields) { $fields->addFieldsToTab( @@ -26,25 +27,24 @@ public function updateCmsFields(FieldList $fields) [ CheckboxField::create( 'MailchimpEnabled', - _t(__CLASS__. '.MAILCHIMP_ENABLED', 'Mailchimp subscriptions enabled') + _t(self::class. '.MAILCHIMP_ENABLED', 'Mailchimp subscriptions enabled') ), DropdownField::create( 'MailchimpConfigID', - _t(__CLASS__. '.DEFAULT_MAILCHIMP_CONFIG', 'Default Mailchimp configuration'), + _t(self::class. '.DEFAULT_MAILCHIMP_CONFIG', 'Default Mailchimp configuration'), MailchimpConfig::get()->map('ID', 'TitleCode') )->setEmptyString('') ] ); } + #[\Override] public function onAfterWrite() { parent::onAfterWrite(); - if($this->owner->MailchimpConfigID) { - if($config = MailchimpConfig::get()->byId($this->owner->MailchimpConfigID)) { - $config->IsGlobal = 1; - $config->write(); - } + if ($this->getOwner()->MailchimpConfigID && ($config = MailchimpConfig::get()->byId($this->getOwner()->MailchimpConfigID))) { + $config->IsGlobal = 1; + $config->write(); } } diff --git a/src/Forms/XhrSubscribeForm.php b/src/Forms/XhrSubscribeForm.php index d0a718c..aabfdbe 100644 --- a/src/Forms/XhrSubscribeForm.php +++ b/src/Forms/XhrSubscribeForm.php @@ -17,9 +17,8 @@ class XhrSubscribeForm extends SubscribeForm { /** * Set to true if forms of this class will appear on a publicly cacheable page - * @var bool */ - private static $disable_security_token = false; + private static bool $disable_security_token = false; public function __construct( RequestHandler $controller = null, @@ -37,6 +36,7 @@ public function __construct( /** * @inheritdoc */ + #[\Override] protected function canBeCached() { $token = $this->getSecurityToken(); @@ -51,6 +51,7 @@ protected function canBeCached() * @inheritdoc * Add attributes */ + #[\Override] protected function getDefaultAttributes(): array { $attributes = parent::getDefaultAttributes(); diff --git a/src/Jobs/MailchimpCleanupJob.php b/src/Jobs/MailchimpCleanupJob.php index 61f79ea..ffa38b0 100644 --- a/src/Jobs/MailchimpCleanupJob.php +++ b/src/Jobs/MailchimpCleanupJob.php @@ -19,7 +19,7 @@ class MailchimpCleanupJob extends AbstractQueuedJob implements QueuedJob { use Configurable; - private static $run_in_minutes = 30; + private static int $run_in_minutes = 30; public function __construct($minutes_ago = 30, $limit = 0, $report_only = 0) { @@ -28,11 +28,12 @@ public function __construct($minutes_ago = 30, $limit = 0, $report_only = 0) $this->limit = $limit; } + #[\Override] public function getTitle() { return sprintf( _t( - __CLASS__ . '.TITLE', + self::class . '.TITLE', "Mailchimp cleanup job - minutes=%s limit=%s report_only=%s" ), $this->minutes_ago, @@ -41,24 +42,26 @@ public function getTitle() ); } + #[\Override] public function getJobType() { return QueuedJob::QUEUED; } + #[\Override] public function setup() { $this->totalSteps = 1; } + #[\Override] public function process() { $this->processSubscriptions(); $this->isComplete = true; - return; } - private function processSubscriptions() + private function processSubscriptions(): bool { $prune_datetime = new DateTime(); $minutes = $this->minutes_ago; @@ -87,15 +90,16 @@ private function processSubscriptions() $subscriber->delete(); $success_deletes++; } + $this->addMessage("Deleted {$success_deletes} subscribers with status " . MailchimpSubscriber::CHIMPLE_STATUS_SUCCESS); } - - - $this->currentStep = $this->totalSteps = $success_deletes; + $this->currentStep = $success_deletes; + $this->totalSteps = $success_deletes; // remove stale failed subscriptions older than 7 days $fail_datetime = new DateTime(); $fail_datetime->modify('-7 days'); + $failed = MailchimpSubscriber::get()->filter([ 'Status' => MailchimpSubscriber::CHIMPLE_STATUS_FAIL, 'Created:LessThan' => $fail_datetime->format('Y-m-d H:i:s') @@ -109,11 +113,11 @@ private function processSubscriptions() $failed_subscriber->delete(); $failed_deletes++; } + $this->addMessage("Deleted {$failed_deletes} subscribers with status " . MailchimpSubscriber::CHIMPLE_STATUS_FAIL); } - - - $this->currentStep = $this->totalSteps = ($success_deletes + $failed_deletes); + $this->currentStep = $success_deletes + $failed_deletes; + $this->totalSteps = $success_deletes + $failed_deletes; return true; } @@ -121,16 +125,18 @@ private function processSubscriptions() /** * Get next configured run time */ - private function getNextRunMinutes() + private function getNextRunMinutes(): int { $minutes = (int)$this->config()->get('run_in_minutes'); if ($minutes <= 2) { // min every 2 minutes $minutes = 2; } + return $minutes; } + #[\Override] public function afterComplete() { $run_datetime = new DateTime(); diff --git a/src/Jobs/MailchimpSubscribeJob.php b/src/Jobs/MailchimpSubscribeJob.php index 5c2a6a7..ca9c0b7 100644 --- a/src/Jobs/MailchimpSubscribeJob.php +++ b/src/Jobs/MailchimpSubscribeJob.php @@ -14,8 +14,9 @@ class MailchimpSubscribeJob extends AbstractQueuedJob implements QueuedJob { use Configurable; - private static $run_in_seconds = 60; - private static $default_limit = 100; + private static int $run_in_seconds = 60; + + private static int $default_limit = 100; public function __construct($limit = 100, $report_only = 0) { @@ -23,37 +24,40 @@ public function __construct($limit = 100, $report_only = 0) $this->limit = (int)$limit; } + #[\Override] public function getTitle() { $title = _t( - __CLASS__ . '.TITLE', + self::class . '.TITLE', "Batch subscribe emails to Mailchimp" ); $title .= " (limit:{$this->limit})"; - $title .= ($this->report_only ? " - report only" : ""); - return $title; + return $title . ($this->report_only ? " - report only" : ""); } + #[\Override] public function getJobType() { return QueuedJob::QUEUED; } + #[\Override] public function setup() { $this->totalSteps = 1; } - private function getTotalResults($results) + private function getTotalResults(array $results): int|float { $total = 0; - foreach ($results as $key => $count) { + foreach ($results as $count) { $total += $count; } + return $total; } - private function getTotalNonFailedResults($results) + private function getTotalNonFailedResults(array $results) { $copy = $results; unset($copy[ MailchimpSubscriber::CHIMPLE_STATUS_FAIL]); @@ -63,29 +67,35 @@ private function getTotalNonFailedResults($results) /** * Process the job */ + #[\Override] public function process() { $results = MailchimpSubscriber::batch_subscribe($this->limit, $this->report_only); if ($this->report_only) { $this->addMessage("Report only: " . json_encode($results)); - $this->totalSteps = $this->currentStep = $this->getTotalResults($results); + $this->totalSteps = $this->getTotalResults($results); + $this->currentStep = $this->totalSteps; } elseif (is_array($results)) { foreach ($results as $status => $count) { $message = $status . ":" . $count; $this->addMessage($message); } + $this->totalSteps = $this->getTotalResults($results);// total including failed $this->currentStep = $this->getTotalNonFailedResults($results); } else { $this->addMessage("Failed completely - check logs!"); - $this->totalSteps = $this->currentStep = 0; + $this->totalSteps = 0; + $this->currentStep = 0; } + $this->isComplete = true; } /** * Recreate the MailchimpSubscribeJob job for the next run */ + #[\Override] public function afterComplete() { $run_datetime = new DateTime(); @@ -94,6 +104,7 @@ public function afterComplete() // min every 30s $seconds = 30; } + $run_datetime->modify("+{$seconds} seconds"); singleton(QueuedJobService::class)->queueJob( new MailchimpSubscribeJob($this->limit, $this->report_only), diff --git a/src/Models/Elements/ElementChimpleSubscribe.php b/src/Models/Elements/ElementChimpleSubscribe.php index 17fbe4b..10744b5 100644 --- a/src/Models/Elements/ElementChimpleSubscribe.php +++ b/src/Models/Elements/ElementChimpleSubscribe.php @@ -24,45 +24,48 @@ */ class ElementChimpleSubscribe extends BaseElement { - private static $table_name = 'ElementChimpleSubscribe'; + private static string $table_name = 'ElementChimpleSubscribe'; - private static $singular_name = 'Mailchimp subscribe'; - private static $plural_name = 'Mailchimp subscribe'; + private static string $singular_name = 'Mailchimp subscribe'; - private static $icon = 'font-icon-up-circled'; + private static string $plural_name = 'Mailchimp subscribe'; - private static $db = [ + private static string $icon = 'font-icon-up-circled'; + + private static array $db = [ 'UseXHR' => 'Boolean',// whether to submit without redirect 'BeforeFormContent' => 'HTMLText', 'AfterFormContent' => 'HTMLText', 'ImageAlignment' => 'Varchar(32)', ]; - private static $defaults = [ + private static array $defaults = [ 'UseXHR' => 1 ]; /** * Has_one relationship - * @var array */ - private static $has_one = [ + private static array $has_one = [ 'MailchimpConfig' => MailchimpConfig::class, 'Image' => Image::class ]; - private static $owns = [ + private static array $owns = [ 'Image' ]; + #[\Override] public function getType() { - return _t(__CLASS__ . '.BlockType', 'Mailchimp Subscribe'); + return _t(self::class . '.BlockType', 'Mailchimp Subscribe'); } - private static $title = 'Mailchimp subscribe'; - private static $description = 'Provide a mailchimp subscription form'; + private static string $title = 'Mailchimp subscribe'; + + private static string $description = 'Provide a mailchimp subscription form'; + #[\Override] public function getCMSFields() { $fields = parent::getCMSFields(); @@ -74,7 +77,7 @@ public function getCMSFields() LiteralField::create( 'NotEnabled', '

' - . _t(__CLASS__ . 'NOT_ENABLED', 'Mailchimp is not enable in site configuration') + . _t(self::class . 'NOT_ENABLED', 'Mailchimp is not enable in site configuration') . '

' ) ); @@ -90,7 +93,7 @@ public function getCMSFields() DropdownField::create( 'MailchimpConfigID', _t( - __CLASS__ . '.SELECT_CONFIGURATION', + self::class . '.SELECT_CONFIGURATION', 'Select the list configuration to use for this subscription form' ), MailchimpConfig::get()->sort('Title ASC')->map('ID', 'TitleWithDetails') @@ -98,30 +101,30 @@ public function getCMSFields() CheckboxField::create( 'UseXHR', _t( - __CLASS__ . '.USE_XHR', + self::class . '.USE_XHR', 'Submit without redirecting' ), ), HTMLEditorField::create( 'BeforeFormContent', _t( - __CLASS__ . '.BEFORE_CONTENT', + self::class . '.BEFORE_CONTENT', 'Content to show before form' ) )->setRows(6), HTMLEditorField::create( 'AfterFormContent', _t( - __CLASS__ . '.AFTER_CONTENT', + self::class . '.AFTER_CONTENT', 'Content to show after form' ) )->setRows(6), UploadField::create( 'Image', - _t(__CLASS__ . '.IMAGE', 'Image') + _t(self::class . '.IMAGE', 'Image') )->setTitle( _t( - __CLASS__ . '.ADD_IMAGE_TO_CONTENT_BLOCK', + self::class . '.ADD_IMAGE_TO_CONTENT_BLOCK', 'Add an image' ) )->setFolderName('blocks/content/' . $this->owner->ID) @@ -129,14 +132,14 @@ public function getCMSFields() ->setIsMultiUpload(false), DropdownField::create( 'ImageAlignment', - _t(__CLASS__ . '.IMAGE_ALIGNMENT', 'Image alignment'), + _t(self::class . '.IMAGE_ALIGNMENT', 'Image alignment'), [ - 'left' => _t(__CLASS__ . '.LEFT', 'Left'), - 'right' => _t(__CLASS__ . '.RIGHT', 'Right') + 'left' => _t(self::class . '.LEFT', 'Left'), + 'right' => _t(self::class . '.RIGHT', 'Right') ] )->setEmptyString('Choose an option') ->setDescription( - _t(__CLASS__ . '.IMAGE_ALIGNMENT_DESCRIPTION', 'Use of this option is dependent on the theme') + _t(self::class . '.IMAGE_ALIGNMENT_DESCRIPTION', 'Use of this option is dependent on the theme') ) ] ); @@ -160,6 +163,7 @@ public function getSubscribeForm(): ?SubscribeForm $form = $config->SubscribeForm($this->UseXHR == 1); return $form; } + return null; } } diff --git a/src/Models/MailchimpConfig.php b/src/Models/MailchimpConfig.php index 2603339..27b8428 100644 --- a/src/Models/MailchimpConfig.php +++ b/src/Models/MailchimpConfig.php @@ -31,25 +31,28 @@ */ class MailchimpConfig extends DataObject implements TemplateGlobalProvider, PermissionProvider { - private static $list_id = "";// default list (audience) ID - private static $api_key = "";// API key provided by Mailchimp + private static string $list_id = ""; + // default list (audience) ID + private static string $api_key = "";// API key provided by Mailchimp - private static $success_message = "Thank you for subscribing. You will receive an email to confirm your subscription shortly."; - private static $error_message = "Sorry, we could not subscribe that email address at the current time. Please try again later."; + private static string $success_message = "Thank you for subscribing. You will receive an email to confirm your subscription shortly."; - private static $table_name = 'ChimpleConfig'; + private static string $error_message = "Sorry, we could not subscribe that email address at the current time. Please try again later."; - private static $singular_name = 'Mailchimp Configuration'; - private static $plural_name = 'Mailchimp Configurations'; + private static string $table_name = 'ChimpleConfig'; - private static $title = "Mailchimp Subscriber Form"; - private static $description = "Configuration for a Mailchimp subscribe form"; + private static string $singular_name = 'Mailchimp Configuration'; + + private static string $plural_name = 'Mailchimp Configurations'; + + private static string $title = "Mailchimp Subscriber Form"; + + private static string $description = "Configuration for a Mailchimp subscribe form"; /** * Database fields - * @var array */ - private static $db = [ + private static array $db = [ 'Title' => 'Varchar(255)', 'Code' => 'Varchar(255)',// auto created, used to identify config 'IsGlobal' => 'Boolean', @@ -70,9 +73,8 @@ class MailchimpConfig extends DataObject implements TemplateGlobalProvider, Perm /** * Defines summary fields commonly used in table columns * as a quick overview of the data for this dataobject - * @var array */ - private static $summary_fields = [ + private static array $summary_fields = [ 'Title' => 'Title', 'Code' => 'Code', 'IsGlobal.Nice' => 'Default', @@ -81,16 +83,15 @@ class MailchimpConfig extends DataObject implements TemplateGlobalProvider, Perm 'UseXHR.Nice' => 'Submit w/o redirect' ]; - private static $indexes = [ + private static array $indexes = [ 'MailchimpListId' => true, 'Code' => ['type' => 'unique'] ]; /** * Add default values to database - * @var array */ - private static $defaults = [ + private static array $defaults = [ 'UpdateExisting' => 1,// @deprecated 'SendWelcome' => 0,// @deprecated 'ReplaceInterests' => 0,// @deprecated @@ -99,12 +100,12 @@ class MailchimpConfig extends DataObject implements TemplateGlobalProvider, Perm 'UseXHR' => 1 ]; - public function TitleCode() + public function TitleCode(): string { return "{$this->Title} ({$this->Code})"; } - public static function isEnabled() + public static function isEnabled(): bool { $site_config = SiteConfig::current_site_config(); return $site_config->MailchimpEnabled == 1; @@ -122,38 +123,38 @@ public static function getApiKey() /** * Returns the data centre (dc) component based on the API key e.g us2 - * @return string */ public static function getDataCentre(): string { - $dc = ''; $key = self::getApiKey(); $parts = []; if($key) { - $parts = explode("-", $key); + $parts = explode("-", (string) $key); } - return !empty($parts[1]) ? $parts[1] : ''; + + return empty($parts[1]) ? '' : $parts[1]; } - public function TitleWithCode() + public function TitleWithCode(): string { return $this->Title . " - (code {$this->Code})"; } - public function TitleWithDetails() + public function TitleWithDetails(): string { $title = $this->Title; $list_id = $this->getMailchimpListId(); - $title .= " (list {$list_id})"; - return $title; + return $title . " (list {$list_id})"; } + #[\Override] public function onBeforeWrite() { parent::onBeforeWrite(); if (!$this->Code) { $this->Code = bin2hex(random_bytes(16)); } + $this->Code = Convert::raw2url($this->Code); if($this->IsGlobal == 1) { @@ -184,10 +185,11 @@ public function getMailchimpListId() if (!$list_id) { $list_id = self::getDefaultMailchimpListId(); } + return $list_id; } - public function HasMailchimpListId() + public function HasMailchimpListId(): bool { return $this->getMailchimpListId() != ''; } @@ -197,18 +199,22 @@ public static function getConfig($id = '', $list_id = '', $code = '') if ($id) { return MailchimpConfig::get()->byId($id); } + if ($list_id) { return MailchimpConfig::get()->filter('MailchimpListId', $list_id)->first(); } + if ($code) { return MailchimpConfig::get()->filter('Code', $code)->first(); } + return false; } /** * @inheritdoc */ + #[\Override] public function getCMSFields() { $fields = parent::getCMSFields(); @@ -229,7 +235,7 @@ public function getCMSFields() 'NoApiKey', '

' . _t( - __CLASS__ . '.NO_API_KEY', + self::class . '.NO_API_KEY', 'Warning: no API key was found in the system configuration - subscriptions cannot occur until this is set.' ) . '

' @@ -243,7 +249,7 @@ public function getCMSFields() TextField::create( 'ArchiveLink', _t( - __CLASS__ . '.ARCHIVE_URL', + self::class . '.ARCHIVE_URL', 'Newsletter archive URL' ) ) @@ -253,14 +259,14 @@ public function getCMSFields() $list_id = $this->getField('MailchimpListId'); $fields->dataFieldByName('MailchimpListId') ->setDescription( - !$list_id ? - sprintf( + $list_id ? + "" : sprintf( _t( - __CLASS__ . '.NO_LIST_ID', + self::class . '.NO_LIST_ID', "No list Id is set, the default list id '%s' is being used." ), $default_list_id - ) : "" + ) ); // this is set from SiteConfig @@ -271,7 +277,7 @@ public function getCMSFields() 'IsGlobalBanner', '

' . _t( - __CLASS__. '.CONFIG_IS_GLOBAL', + self::class. '.CONFIG_IS_GLOBAL', 'This configuration is the default for this website' ) . '

' @@ -285,7 +291,7 @@ public function getCMSFields() MultiValueTextField::create( 'Tags', _t( - __CLASS__ . '.TAGS_FOR_SUBSCRIPTIONS', + self::class . '.TAGS_FOR_SUBSCRIPTIONS', 'Tags assigned to subscribers' ) ) @@ -296,7 +302,7 @@ public function getCMSFields() CheckboxField::create( 'UseXHR', _t( - __CLASS__ . '.USE_XHR', + self::class . '.USE_XHR', 'Submit without redirecting' ) ), @@ -309,14 +315,14 @@ public function getCMSFields() HTMLEditorField::create( 'BeforeFormContent', _t( - __CLASS__ . '.BEFORE_CONTENT', + self::class . '.BEFORE_CONTENT', 'Content to show before form' ) )->setRows(6), HTMLEditorField::create( 'AfterFormContent', _t( - __CLASS__ . '.AFTER_CONTENT', + self::class . '.AFTER_CONTENT', 'Content to show after form' ) )->setRows(6) @@ -325,7 +331,7 @@ public function getCMSFields() if($heading = $fields->dataFieldByName('Heading')) { $heading->setDescription(_t( - __CLASS__ . '.HEADING_DESCRIPTON', + self::class . '.HEADING_DESCRIPTON', 'Displayed above the form' )); } @@ -346,13 +352,14 @@ public function MailchimpLink() /** * Ensure the subscription for the global footer is added */ + #[\Override] public function requireDefaultRecords() { $config = MailchimpConfig::get()->filter(['IsGlobal' => 1])->first(); if (empty($config->ID)) { $config = MailchimpConfig::create([ - 'Title' => _t(__CLASS__ . '.DEFAULT_CONFIG_TITLE', 'Default Configuration'), - 'Heading' => _t(__CLASS__ . '.DEFAULT_CONFIG_HEADER', 'Subscribe'), + 'Title' => _t(self::class . '.DEFAULT_CONFIG_TITLE', 'Default Configuration'), + 'Heading' => _t(self::class . '.DEFAULT_CONFIG_HEADER', 'Subscribe'), 'IsGlobal' => 1, 'MailchimpListId' => null ]); @@ -361,6 +368,7 @@ public function requireDefaultRecords() } else { $config_id = $config->ID; } + if ($config_id) { $site_config = SiteConfig::current_site_config(); if (!empty($site_config->ID) && empty($site_config->MailchimpConfigID)) { @@ -403,6 +411,7 @@ public function SubscribeForm($force_xhr = null): ?SubscribeForm if ($this->Heading) { $form->setLegend($this->Heading); } + $form->addExtraClass('form-subscribe'); return $form; } @@ -412,39 +421,43 @@ public function SubscribeForm($force_xhr = null): ?SubscribeForm /** * Return alerts for the form - * @return string */ - public function Alerts() + public function Alerts(): string { return '' . '' . '';// info added by JS } + #[\Override] public function canView($member = null) { return Permission::checkMember($member, 'MAILCHIMP_CONFIG_VIEW'); } + #[\Override] public function canCreate($member = null, $context = []) { return Permission::checkMember($member, 'MAILCHIMP_CONFIG_CREATE'); } + #[\Override] public function canEdit($member = null) { return Permission::checkMember($member, 'MAILCHIMP_CONFIG_EDIT'); } + #[\Override] public function canDelete($member = null) { return Permission::checkMember($member, 'MAILCHIMP_CONFIG_DELETE'); } + #[\Override] public function providePermissions() { return [ @@ -474,9 +487,10 @@ public function providePermissions() public function forTemplate($force_xhr = null) { $form = $this->SubscribeForm($force_xhr); - if($form) { + if($form instanceof \NSWDPC\Chimple\Forms\SubscribeForm) { return $this->customise(['Form' => $form])->renderWith(self::class); } + return null; } @@ -490,7 +504,7 @@ public function forTemplate($force_xhr = null) */ public static function get_chimple_subscribe_form(...$args) { - $code = isset($args[0]) ? $args[0] : ''; + $code = $args[0] ?? ''; if ($code) { $config = self::getConfig('', '', $code); if ($config) { @@ -505,9 +519,11 @@ public static function get_chimple_subscribe_form(...$args) $force_xhr = true; } } + return $config->forTemplate($force_xhr); } } + return null; } @@ -522,9 +538,11 @@ public static function get_chimple_global_subscribe_form() if ($config) { return $config->forTemplate(); } + return null; } + #[\Override] public static function get_template_global_variables() { return [ diff --git a/src/Models/MailchimpSubscriber.php b/src/Models/MailchimpSubscriber.php index 5b2cf0f..f925586 100644 --- a/src/Models/MailchimpSubscriber.php +++ b/src/Models/MailchimpSubscriber.php @@ -19,71 +19,69 @@ class MailchimpSubscriber extends DataObject implements PermissionProvider { public const CHIMPLE_STATUS_UNKNOWN = ''; + public const CHIMPLE_STATUS_NEW = 'NEW'; + public const CHIMPLE_STATUS_PROCESSING = 'PROCESSING'; + public const CHIMPLE_STATUS_BATCHED = 'BATCHED'; + public const CHIMPLE_STATUS_SUCCESS = 'SUCCESS'; + public const CHIMPLE_STATUS_FAIL = 'FAIL'; public const MAILCHIMP_SUBSCRIBER_PENDING = 'pending'; + public const MAILCHIMP_SUBSCRIBER_SUBSCRIBED = 'subscribed'; + public const MAILCHIMP_SUBSCRIBER_UNSUBSCRIBED = 'unsubscribed'; + public const MAILCHIMP_SUBSCRIBER_CLEANED = 'cleaned'; public const MAILCHIMP_TAG_INACTIVE = 'inactive'; + public const MAILCHIMP_TAG_ACTIVE = 'active'; public const MAILCHIMP_EMAIL_TYPE_HTML = 'html'; + public const MAILCHIMP_EMAIL_TYPE_TEXT = 'text'; - /** - * @var string - */ - private static $table_name = 'ChimpleSubscriber'; + private static string $table_name = 'ChimpleSubscriber'; /** * Singular name for CMS - * @var string */ - private static $singular_name = 'Mailchimp Subscriber'; + private static string $singular_name = 'Mailchimp Subscriber'; /** * Plural name for CMS - * @var string */ - private static $plural_name = 'Mailchimp Subscribers'; + private static string $plural_name = 'Mailchimp Subscribers'; /** * Default sort ordering - * @var array */ - private static $default_sort = ['Created' => 'DESC']; + private static array $default_sort = ['Created' => 'DESC']; /** * Default chr for obfuscation - * @var string */ - private static $obfuscation_chr = "•"; + private static string $obfuscation_chr = "•"; /** * Email type, defaults to 'html', other value is 'text' - * @var string */ - private static $email_type = self::MAILCHIMP_EMAIL_TYPE_HTML; + private static string $email_type = self::MAILCHIMP_EMAIL_TYPE_HTML; /** * Remove tags that do not exist in the tags list when a subscriber * attempts to update their subscription * This is a potentially destructive action as it will remove tags added to * a subscriber via other means - * @var bool */ - private static $remove_subscriber_tags = false; + private static bool $remove_subscriber_tags = false; - /** - * @var array - */ - private static $db = [ + private static array $db = [ 'Name' => 'Varchar(255)', 'Surname' => 'Varchar(255)', 'Email' => 'Varchar(255)', @@ -107,12 +105,12 @@ class MailchimpSubscriber extends DataObject implements PermissionProvider 'Tags' => 'MultiValueField' ]; - private static $sync_fields = [ + private static array $sync_fields = [ 'Name' => 'FNAME', 'Surname' => 'LNAME' ]; - private static $indexes = [ + private static array $indexes = [ 'Status' => true, 'Email' => true, 'Name' => true, @@ -123,9 +121,8 @@ class MailchimpSubscriber extends DataObject implements PermissionProvider /** * Add default values to database - * @var array */ - private static $defaults = [ + private static array $defaults = [ 'Status' => self::CHIMPLE_STATUS_NEW, 'UpdateExisting' => 1,// @deprecated 'SendWelcome' => 0,// @deprecated @@ -136,9 +133,8 @@ class MailchimpSubscriber extends DataObject implements PermissionProvider /** * Defines summary fields commonly used in table columns * as a quick overview of the data for this dataobject - * @var array */ - private static $summary_fields = [ + private static array $summary_fields = [ 'Created.Nice' => 'Created', 'Name' => 'Name', 'Surname' => 'Surname', @@ -151,9 +147,8 @@ class MailchimpSubscriber extends DataObject implements PermissionProvider /** * Defines a default list of filters for the search context - * @var array */ - private static $searchable_fields = [ + private static array $searchable_fields = [ 'Name' => 'PartialMatchFilter', 'Surname' => 'PartialMatchFilter', 'Email' => 'PartialMatchFilter', @@ -165,25 +160,26 @@ class MailchimpSubscriber extends DataObject implements PermissionProvider * @var MailchimpApiClient * The Mailchimp API client instance */ - protected static $mailchimp = null; + protected static $mailchimp; /** * List of current subscriber tags - * @var array|null */ - private $_cache_tags = null; + private ?array $_cache_tags = null; /** * Return whether subscription was success */ - public function getSuccessful() + public function getSuccessful(): bool { return $this->Status == self::CHIMPLE_STATUS_SUCCESS; } + /** * @inheritdoc */ + #[\Override] public function getCMSFields() { $fields = parent::getCMSFields(); @@ -204,7 +200,7 @@ public function getCMSFields() $fields->dataFieldByName('LastError') ->setDescription( _t( - __CLASS__. '.IF_ERROR_OCCURRED', + self::class. '.IF_ERROR_OCCURRED', "If a subscription error occurred, this will display the last error returned by Mailchimp and can help determine the cause of the error" ) ) @@ -214,13 +210,13 @@ public function getCMSFields() if($this->exists()) { $status_field = DropdownField::create( 'Status', - _t(__CLASS__ . '.STATUS', 'Status'), + _t(self::class . '.STATUS', 'Status'), [ self::CHIMPLE_STATUS_UNKNOWN => '', - self::CHIMPLE_STATUS_NEW => _t(__CLASS__ . '.STATUS_NEW', 'NEW (the subscriber has not yet been subscribed)'), - self::CHIMPLE_STATUS_PROCESSING => _t(__CLASS__ . '.STATUS_PROCESSING', 'PROCESSING (the subscriber is in the process of being subscribed)'), - self::CHIMPLE_STATUS_SUCCESS => _t(__CLASS__ . '.STATUS_SUCCESS', 'SUCCESS (the subscriber was subscribed)'), - self::CHIMPLE_STATUS_FAIL => _t(__CLASS__ . '.STATUS_FAIL', 'FAIL (the subscriber could not be subscribed - check the \'Last Error\' value)') + self::CHIMPLE_STATUS_NEW => _t(self::class . '.STATUS_NEW', 'NEW (the subscriber has not yet been subscribed)'), + self::CHIMPLE_STATUS_PROCESSING => _t(self::class . '.STATUS_PROCESSING', 'PROCESSING (the subscriber is in the process of being subscribed)'), + self::CHIMPLE_STATUS_SUCCESS => _t(self::class . '.STATUS_SUCCESS', 'SUCCESS (the subscriber was subscribed)'), + self::CHIMPLE_STATUS_FAIL => _t(self::class . '.STATUS_FAIL', "FAIL (the subscriber could not be subscribed - check the 'Last Error' value)") ] ); @@ -234,27 +230,27 @@ public function getCMSFields() // stuck processing - can reset to new $status_field->setDescription( _t( - __CLASS__ . '.PROCESSING_RESET_NEW_STATUS', + self::class . '.PROCESSING_RESET_NEW_STATUS', "If this attempt is stuck, reset to 'New' for another attempt" ) ); // can retain failed or set to new for retry $status_field->setSource([ - self::CHIMPLE_STATUS_NEW => _t(__CLASS__ . '.STATUS_NEW', 'NEW (the subscriber has not yet been subscribed)'), - self::CHIMPLE_STATUS_PROCESSING => _t(__CLASS__ . '.STATUS_PROCESSING', 'PROCESSING (the subscriber is in the process of being subscribed)'), + self::CHIMPLE_STATUS_NEW => _t(self::class . '.STATUS_NEW', 'NEW (the subscriber has not yet been subscribed)'), + self::CHIMPLE_STATUS_PROCESSING => _t(self::class . '.STATUS_PROCESSING', 'PROCESSING (the subscriber is in the process of being subscribed)'), ]); } elseif($this->Status == self::CHIMPLE_STATUS_FAIL) { // handling when failed $status_field->setDescription( _t( - __CLASS__ . '.FAIL_RESET_NEW_STATUS', + self::class . '.FAIL_RESET_NEW_STATUS', "Reset this value to 'New' to retry a failed subscription attempt" ) ); // can retain failed or set to new for retry $status_field->setSource([ - self::CHIMPLE_STATUS_NEW => _t(__CLASS__ . '.STATUS_NEW', 'NEW (the subscriber has not yet been subscribed)'), - self::CHIMPLE_STATUS_FAIL => _t(__CLASS__ . '.STATUS_FAIL', 'FAIL (the subscriber could not be subscribed - check the \'Last Error\' value)') + self::CHIMPLE_STATUS_NEW => _t(self::class . '.STATUS_NEW', 'NEW (the subscriber has not yet been subscribed)'), + self::CHIMPLE_STATUS_FAIL => _t(self::class . '.STATUS_FAIL', "FAIL (the subscriber could not be subscribed - check the 'Last Error' value)") ]); } @@ -275,8 +271,8 @@ public function getCMSFields() 'StatusForNew', '

' . _t( - __CLASS__ . '.STATUS_NEW_MESSAGE', - 'This subscription attempt record will be given status of \'New\' and it will enter the pending subscription queue upon save' + self::class . '.STATUS_NEW_MESSAGE', + "This subscription attempt record will be given status of 'New' and it will enter the pending subscription queue upon save" ) . '

' ), @@ -286,9 +282,9 @@ public function getCMSFields() $tags = $this->getCurrentSubscriberTags(); $tag_field_description = ""; - if(!empty($tags)) { + if($tags !== []) { $tag_field_description = _t( - __CLASS__ . '.TAGS_FIELD_DESCRIPTION', + self::class . '.TAGS_FIELD_DESCRIPTION', 'The current tags for this subscriber are {tags}
Tags not in the tag update list will be removed. New tags will be added.', [ 'tags' => implode(", ", $tags) @@ -302,19 +298,19 @@ public function getCMSFields() KeyValueField::create( 'MergeFields', _t( - __CLASS__ . '.MERGE_FIELDS', + self::class . '.MERGE_FIELDS', 'Merge fields for this subscription attempt' ) )->setDescription( _t( - __CLASS__ . '.MERGE_FIELDS_DESCRIPTION', + self::class . '.MERGE_FIELDS_DESCRIPTION', 'Keys are merge tag names, values are the values for this particular subscriber' ) ), MultiValueTextField::create( 'Tags', _t( - __CLASS__ . '.TAGS_FIELD', + self::class . '.TAGS_FIELD', 'Tag update list' ) )->setDescription( @@ -332,10 +328,10 @@ public function getCMSFields() } $readonly_fields = [ - 'MailchimpListId' => _t(__CLASS__ . '.SUBSCRIBER_LIST_ID', "The Mailchimp List (audience) Id for this subscription record"), - 'SubscribedUniqueEmailId' => _t(__CLASS__ . '.SUBSCRIBER_UNIQUE_EMAIL_ID', "An identifier for the address across all of Mailchimp."), - 'SubscribedWebId' => _t(__CLASS__ . '.SUBSCRIBER_WEB_ID', "Member profile page: {link}", ['link' => $subscriber_profile_link]), - 'SubscribedId' => _t(__CLASS__ . '.SUBSCRIBER_ID', "The MD5 hash of the lowercase version of the list member's email address.") + 'MailchimpListId' => _t(self::class . '.SUBSCRIBER_LIST_ID', "The Mailchimp List (audience) Id for this subscription record"), + 'SubscribedUniqueEmailId' => _t(self::class . '.SUBSCRIBER_UNIQUE_EMAIL_ID', "An identifier for the address across all of Mailchimp."), + 'SubscribedWebId' => _t(self::class . '.SUBSCRIBER_WEB_ID', "Member profile page: {link}", ['link' => $subscriber_profile_link]), + 'SubscribedId' => _t(self::class . '.SUBSCRIBER_ID', "The MD5 hash of the lowercase version of the list member's email address.") ]; foreach($readonly_fields as $readonly_field => $description) { @@ -345,6 +341,7 @@ public function getCMSFields() if($description) { $field->setDescription($description); } + $fields->addFieldToTab( 'Root.Main', $field @@ -355,6 +352,7 @@ public function getCMSFields() return $fields; } + #[\Override] public function onBeforeWrite() { parent::onBeforeWrite(); @@ -368,7 +366,7 @@ public function onBeforeWrite() $this->Surname = $this->getSurnameFromName(); // reset name $parts = explode(" ", $this->Name, 2); - $this->Name = isset($parts[0]) ? $parts[0] : ''; + $this->Name = $parts[0] ?? ''; } } @@ -377,11 +375,10 @@ public function onBeforeWrite() * Given some meta data, update the MergeFields for this subscriber * The parameter can contain values submitted via a form * By default MergeFields doesn't allow HTML tags as keys or as values - * @param array $meta */ public function updateMergeFields(array $meta) { - if(empty($meta)) { + if($meta === []) { return; } @@ -391,15 +388,17 @@ public function updateMergeFields(array $meta) // ignore values that cannot be saved continue; } + $key = strtoupper(trim(strip_tags($k))); $value = trim(strip_tags($v)); $data[ $key ] = $value; } + $this->MergeFields = $data; $this->write(); } - public function HasLastError() + public function HasLastError(): string { return trim($this->LastError ?? '') !== '' ? "yes" : "no"; } @@ -407,17 +406,17 @@ public function HasLastError() /** * Attempt to get a surname from the name */ - public function getSurnameFromName() + public function getSurnameFromName(): ?string { $parts = explode(" ", $this->Name, 2); - if (!empty($parts[1])) { + if (isset($parts[1]) && ($parts[1] !== '' && $parts[1] !== '0')) { return $parts[1]; } + return null; } /** * Get the API client - * @return MailchimpApiClient */ public static function api(): MailchimpApiClient { @@ -425,10 +424,12 @@ public static function api(): MailchimpApiClient if(self::$mailchimp instanceof MailchimpApiClient) { return self::$mailchimp; } + $api_key = MailchimpConfig::getApiKey(); if (!$api_key) { throw new Exception("No Mailchimp API Key configured!"); } + self::$mailchimp = new MailchimpApiClient($api_key); return self::$mailchimp; } @@ -436,7 +437,7 @@ public static function api(): MailchimpApiClient /** * @deprecated use self::api() instead */ - public function getMailchimp() + public function getMailchimp(): \DrewM\MailChimp\MailChimp { return self::api(); } @@ -450,27 +451,28 @@ public function getMailchimpListId() if (!$list_id) { $list_id = MailchimpConfig::getDefaultMailchimpListId(); } + return $list_id; } /** * Applies merge fields prior to subscription attempt - * @return array */ - protected function applyMergeFields() + protected function applyMergeFields(): array { $merge_fields = []; // get subscriber meta data $meta = $this->MergeFields->getValue(); - if(is_array($meta) && !empty($meta)) { + if(is_array($meta) && $meta !== []) { foreach($meta as $k => $v) { if($v === '') { // do not set empty values, MailChimp does not like this continue; } + $k = strtoupper(trim($k)); - $merge_fields[$k] = trim($v); + $merge_fields[$k] = trim((string) $v); } } @@ -482,18 +484,17 @@ protected function applyMergeFields() // ignore non-existent fields continue; } + $value = $this->getField($field); - if(!is_string($value)) { - $value = ''; - } else { - $value = trim($value); - } + $value = is_string($value) ? trim($value) : ''; + if ($value === '') { // do not set empty values, MailChimp does not like this // "The resource submitted could not be validated" continue; } - $tag = strtoupper(trim($tag)); + + $tag = strtoupper(trim((string) $tag)); $merge_fields[$tag] = $value; } @@ -502,7 +503,6 @@ protected function applyMergeFields() /** * Get the default subscription record data for adding/updating member in list - * @return array */ public function getSubscribeRecord(): array { @@ -513,18 +513,17 @@ public function getSubscribeRecord(): array if(!$email_type || $email_type != self::MAILCHIMP_EMAIL_TYPE_HTML || $email_type != self::MAILCHIMP_EMAIL_TYPE_TEXT) { $email_type = self::MAILCHIMP_EMAIL_TYPE_HTML; } - $params = [ + return [ 'email_address' => $this->Email, 'email_type' => $email_type, 'merge_fields' => $merge_fields ]; - return $params; } /** * Return tags for this subscriber */ - public function getSubscriberTags() + public function getSubscriberTags(): array { $tags = $this->Tags->getValue(); if(!is_array($tags)) { @@ -545,17 +544,18 @@ private function obfuscate() if($length == 0) { return ""; } + $chr = $this->config()->get('obfuscation_chr'); if($chr === "") { // if no chr configured, do not obfuscate (e.g not require by project) return $in; } + $sub_length = $length - 2; if($sub_length <= 0) { return str_repeat($chr, $length); } - $replaced = substr_replace($in, str_repeat($chr, $sub_length), 1, $sub_length); - return $replaced; + return substr_replace($in, str_repeat($chr, $sub_length), 1, $sub_length); }; $this->Email = $obfuscate($this->Email); $this->Name = $obfuscate($this->Name); @@ -568,7 +568,7 @@ private function obfuscate() */ public static function getMailchimpSubscribedId($email) { - if(!is_string($email) || !$email) { + if(!is_string($email) || ($email === '' || $email === '0')) { return ''; } else { return MailchimpApiClient::subscriberHash($email); @@ -580,25 +580,24 @@ public static function getMailchimpSubscribedId($email) * @param string $list_id the Audience ID * @param string $email an email address, this is hashed using the MC hashing strategy * @param string $api_key @deprecated - * @return boolean|array */ - public static function checkExistsInList(string $list_id, string $email, string $api_key = '') + public static function checkExistsInList(string $list_id, string $email, string $api_key = ''): array|false { // sanity check on input - if(!$email) { + if($email === '' || $email === '0') { throw new \Exception( _t( - __CLASS__ . ".EMAIL_NOT_PROVIDED", + self::class . ".EMAIL_NOT_PROVIDED", "Please provide an email address" ) ); } - if(!$list_id) { + if($list_id === '' || $list_id === '0') { throw new \Exception( _t( - __CLASS__ . ".AUDIENCE_NOT_PROVIDED", + self::class . ".AUDIENCE_NOT_PROVIDED", "Please provide a Mailchimp audience/list ID" ) ); @@ -621,9 +620,8 @@ public static function checkExistsInList(string $list_id, string $email, string /** * Subscribe *this* particular record - * @return bool */ - public function subscribe() + public function subscribe(): bool { try { @@ -682,16 +680,17 @@ public function subscribe() $this->write(); return true; } elseif (!empty($result['status'])) { - $error_detail = isset($result['detail']) ? $result['detail'] : ''; + $error_detail = $result['detail'] ?? ''; $error_status = $result['status']; $error_title = $result['title']; $errors = "{$error_status}|{$error_title}|{$error_detail}"; } else { $errors = "Unhandled error for email: {$this->Email}"; } + throw new Exception(trim($errors)); - } catch (Exception $e) { - $last_error = $e->getMessage(); + } catch (Exception $exception) { + $last_error = $exception->getMessage(); } // record failure @@ -732,12 +731,12 @@ private function getCurrentSubscriberTags(bool $force = false, int $count = 10): $operation_path = "lists/{$list_id}/members/{$subscriber_hash}/tags/?count={$count}&offset={$offset}"; $result = self::api()->get($operation_path); - $total = isset($result['total_items']) ? $result['total_items'] : 0; + $total = $result['total_items'] ?? 0; // initial set of tags $tags = isset($result['tags']) && is_array($result['tags']) ? $result['tags'] : []; // populate the list of tags - $walker = function ($value, $key) use (&$list) { + $walker = function (array $value, $key) use (&$list): void { $list[] = $value['name']; }; array_walk($tags, $walker); @@ -810,7 +809,7 @@ protected function modifySubscriberTags(): bool $operation_path = "/lists/{$list_id}/members/{$subscriber_hash}/tags"; // submit payload to API - $result = self::api()->post( + self::api()->post( $operation_path, $params ); @@ -826,7 +825,6 @@ protected function modifySubscriberTags(): bool /** * Batch subscribe via API - hit from MailchimpSubscribeJob * Retrieve all subscribers marked new and attempt to subscribe them - * @return array */ public static function batch_subscribe($limit = 100, $report_only = false): array { @@ -838,11 +836,13 @@ public static function batch_subscribe($limit = 100, $report_only = false): arra if ($limit) { $subscribers = $subscribers->limit($limit); } + if ($report_only) { $results[ self::CHIMPLE_STATUS_PROCESSING ] = $subscribers->count(); foreach ($subscribers as $subscriber) { Logger::log("REPORT_ONLY: would subscribe #{$subscriber->ID} to list {$subscriber->MailchimpListId}", 'DEBUG'); } + return $results; } @@ -858,22 +858,26 @@ public static function batch_subscribe($limit = 100, $report_only = false): arra // set status to 0 $results[ $subscriber->Status ] = 0; } + // increment this status $results[ $subscriber->Status ]++; } + return $results; - } catch (Exception $e) { - Logger::log("FAIL: could not batch_subscribe, error=" . $e->getMessage(), 'NOTICE'); + } catch (Exception $exception) { + Logger::log("FAIL: could not batch_subscribe, error=" . $exception->getMessage(), 'NOTICE'); } return []; } + #[\Override] public function canView($member = null) { return Permission::checkMember($member, 'MAILCHIMP_SUBSCRIBER_VIEW'); } + #[\Override] public function canEdit($member = null) { return Permission::checkMember($member, 'MAILCHIMP_SUBSCRIBER_EDIT'); @@ -882,29 +886,32 @@ public function canEdit($member = null) /** * Only admin can delete subscribers */ + #[\Override] public function canDelete($member = null) { return Permission::checkMember($member, 'ADMIN'); } + #[\Override] public function canCreate($member = null, $context = []) { return Permission::checkMember($member, 'MAILCHIMP_SUBSCRIBER_CREATE'); } + #[\Override] public function providePermissions() { return [ 'MAILCHIMP_SUBSCRIBER_VIEW' => [ - 'name' => _t(__CLASS__ . '.MAILCHIMP_SUBSCRIBER_VIEW', 'View Mailchimp Subscribers'), + 'name' => _t(self::class . '.MAILCHIMP_SUBSCRIBER_VIEW', 'View Mailchimp Subscribers'), 'category' => 'Mailchimp', ], 'MAILCHIMP_SUBSCRIBER_EDIT' => [ - 'name' => _t(__CLASS__ . '.MAILCHIMP_SUBSCRIBER_EDIT', 'Edit Mailchimp Subscribers'), + 'name' => _t(self::class . '.MAILCHIMP_SUBSCRIBER_EDIT', 'Edit Mailchimp Subscribers'), 'category' => 'Mailchimp', ], 'MAILCHIMP_SUBSCRIBER_CREATE' => [ - 'name' => _t(__CLASS__ . '.MAILCHIMP_SUBSCRIBER_CREATE', 'Create Mailchimp Subscribers'), + 'name' => _t(self::class . '.MAILCHIMP_SUBSCRIBER_CREATE', 'Create Mailchimp Subscribers'), 'category' => 'Mailchimp', ] ]; diff --git a/tests/ChimpleConfigTest.php b/tests/ChimpleConfigTest.php index 0ccdc31..c5ea1bd 100644 --- a/tests/ChimpleConfigTest.php +++ b/tests/ChimpleConfigTest.php @@ -45,6 +45,7 @@ class ChimpleConfigTest extends SapphireTest /** * @inheritdoc */ + #[\Override] public function setUp(): void { parent::setUp(); @@ -95,7 +96,7 @@ protected function getMailchimpConfig() } - public function testConfiguration() + public function testConfiguration(): void { $forceXhr = true; @@ -115,7 +116,7 @@ public function testConfiguration() $email = $fields->dataFieldByName('Email'); $this->assertTrue($email instanceof EmailField, "Email field is not an email field"); - $name = $fields->dataFieldByName('Name'); + $fields->dataFieldByName('Name'); $this->assertTrue($email instanceof TextField, "Name field is not an text field"); $token_name = SecurityToken::get_default_name(); @@ -134,12 +135,12 @@ public function testConfiguration() $this->assertTrue($static_form instanceof DBHTMLText, "Static form for code {$code_value} was not returned"); $needle = " value=\"{$code_value}\" "; - $this->assertTrue(strpos($static_form->forTemplate(), $needle) !== false, "Missing {$code_value} input from form HTML"); + $this->assertTrue(str_contains($static_form->forTemplate(), $needle), "Missing {$code_value} input from form HTML"); } - public function testCanBeCached() + public function testCanBeCached(): void { Config::modify()->set(XhrSubscribeForm::class, 'disable_security_token', true); @@ -168,7 +169,7 @@ public function testCanBeCached() } - public function testSubscribeFormTemplateVariable() + public function testSubscribeFormTemplateVariable(): void { $config = $this->getMailchimpConfig(); $config->UseXHR = 0; @@ -176,34 +177,34 @@ public function testSubscribeFormTemplateVariable() // Use config value $template = MailchimpConfig::get_chimple_subscribe_form($config->Code, null); - $this->assertTrue(!str_contains($template, "data-xhr=\"1\""), "Attribute is not in template"); + $this->assertTrue(!str_contains($template, 'data-xhr="1"'), "Attribute is not in template"); $config->UseXHR = 1; $config->write(); $template = MailchimpConfig::get_chimple_subscribe_form($config->Code, null); - $this->assertTrue(str_contains($template, "data-xhr=\"1\""), "Attribute is in template"); + $this->assertTrue(str_contains($template, 'data-xhr="1"'), "Attribute is in template"); // template override $config->UseXHR = 0; $config->write(); $template = MailchimpConfig::get_chimple_subscribe_form($config->Code, '1'); - $this->assertTrue(str_contains($template, "data-xhr=\"1\""), "Attribute is in template"); + $this->assertTrue(str_contains($template, 'data-xhr="1"'), "Attribute is in template"); $config->UseXHR = 0; $config->write(); $template = MailchimpConfig::get_chimple_subscribe_form($config->Code, '0'); - $this->assertTrue(!str_contains($template, "data-xhr=\"1\""), "Attribute is not in template"); + $this->assertTrue(!str_contains($template, 'data-xhr="1"'), "Attribute is not in template"); } - public function testGlobalSubscribeFormTemplateVariable() + public function testGlobalSubscribeFormTemplateVariable(): void { $config = $this->getMailchimpConfig(); $config->UseXHR = 0; $config->write(); // Use config value $template = MailchimpConfig::get_chimple_global_subscribe_form(); - $this->assertTrue(strpos($template, "data-xhr=\"1\"") === false, "Attribute is not in template"); + $this->assertTrue(str_contains($template, 'data-xhr="1"') === 0 || str_contains($template, 'data-xhr="1"') === false, "Attribute is not in template"); $config->UseXHR = 1; $config->write(); $template = MailchimpConfig::get_chimple_global_subscribe_form(); - $this->assertTrue(strpos($template, "data-xhr=\"1\"") !== false, "Attribute is in template"); + $this->assertTrue(str_contains($template, 'data-xhr="1"'), "Attribute is in template"); } } diff --git a/tests/ChimpleFunctionalTest.php b/tests/ChimpleFunctionalTest.php index 4792d85..75691f9 100644 --- a/tests/ChimpleFunctionalTest.php +++ b/tests/ChimpleFunctionalTest.php @@ -50,6 +50,7 @@ class ChimpleFunctionalTest extends FunctionalTest protected $test_form_code = 'functionalformcode'; + #[\Override] public function setUp(): void { parent::setUp(); @@ -57,7 +58,7 @@ public function setUp(): void // Inject test form Injector::inst()->registerService( - new TestSubscribeForm(), + \NSWDPC\Chimple\Tests\TestSubscribeForm::create(), SubscribeForm::class ); @@ -91,10 +92,10 @@ public function setUp(): void $config->write(); } - public function testFormSubmission() + public function testFormSubmission(): void { - $this->useTestTheme(__DIR__, 'chimpletest', function () { + $this->useTestTheme(__DIR__, 'chimpletest', function (): void { // request default route $url = "/mc-subscribe/v1/"; diff --git a/tests/ChimpleSubscriberTest.php b/tests/ChimpleSubscriberTest.php index 9d8493a..d7b57b9 100644 --- a/tests/ChimpleSubscriberTest.php +++ b/tests/ChimpleSubscriberTest.php @@ -22,23 +22,20 @@ class ChimpleSubscriberTest extends SapphireTest /** * e.g example.com - * @var string */ - private static $test_email_domain = ""; + private static string $test_email_domain = ""; /** * e.g bob.smith - * @var string */ - private static $test_email_user = ""; + private static string $test_email_user = ""; /** * use plus notation in email address - * @var bool */ - private static $test_use_plus = true; + private static bool $test_use_plus = true; - public function testSubscriber() + public function testSubscriber(): void { $fname = "Test"; $lname = "Tester"; @@ -67,6 +64,7 @@ public function testSubscriber() if($test_use_plus) { $email_address_for_test .= "+unittest" . bin2hex(random_bytes(2)); } + $email_address_for_test .= "@{$test_email_domain}"; $tags = ['TestOne','TestTwo']; @@ -99,7 +97,7 @@ public function testSubscriber() $this->assertTrue(is_array($subscribe_record), "Record is not an array of values"); - $this->assertTrue(!empty($subscribe_record), "Record is empty"); + $this->assertTrue($subscribe_record !== [], "Record is empty"); $this->assertTrue(isset($subscribe_record['merge_fields']), "Record merge_fields is not set"); @@ -126,11 +124,11 @@ public function testSubscriber() $this->assertNotEmpty($subscriber->SubscribedUniqueEmailId, "SubscribedUniqueEmailId should not be empty"); - $this->assertTrue(substr_count($subscriber->Email, $obfuscation_chr) > 0, "Email is not obfuscated, it should be"); + $this->assertTrue(substr_count($subscriber->Email, (string) $obfuscation_chr) > 0, "Email is not obfuscated, it should be"); - $this->assertTrue(substr_count($subscriber->Name, $obfuscation_chr) > 0, "Name is not obfuscated, it should be"); + $this->assertTrue(substr_count($subscriber->Name, (string) $obfuscation_chr) > 0, "Name is not obfuscated, it should be"); - $this->assertTrue(substr_count($subscriber->Surname, $obfuscation_chr) > 0, "Surname is not obfuscated, it should be"); + $this->assertTrue(substr_count($subscriber->Surname, (string) $obfuscation_chr) > 0, "Surname is not obfuscated, it should be"); $mc_record = MailchimpSubscriber::checkExistsInList($list_id, $email); @@ -141,7 +139,7 @@ public function testSubscriber() $this->assertEquals(count($tags), count($mc_record['tags']), "Tag count mismatch"); $mc_tags_list = []; - array_walk($mc_record['tags'], function ($value, $key) use (&$mc_tags_list) { + array_walk($mc_record['tags'], function (array $value, $key) use (&$mc_tags_list): void { $mc_tags_list[] = $value['name']; }); diff --git a/tests/TestSubscribeForm.php b/tests/TestSubscribeForm.php index b6b43d2..0222114 100644 --- a/tests/TestSubscribeForm.php +++ b/tests/TestSubscribeForm.php @@ -13,7 +13,7 @@ class TestSubscribeForm extends SubscribeForm implements TestOnly /** * No need to spam protection on tests */ - public function enableSpamProtection() + public function enableSpamProtection(): null { return null; }