Skip to content

Commit

Permalink
Add ability to import contacts as unsubscribed
Browse files Browse the repository at this point in the history
  • Loading branch information
bencroker committed May 13, 2023
1 parent 21fb7a2 commit dd4780e
Show file tree
Hide file tree
Showing 15 changed files with 111 additions and 44 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Release Notes for Campaign

## 2.7.0 - Unreleased
## 2.7.0 - 2023-05-13
### Added
- Added the ability to import contacts into a mailing list with a status of unsubscribed ([#384](https://github.com/putyourlightson/craft-campaign/issues/384)).

### Changed
- Anchor hashtags are now forced to the end of URLs when appending query string parameters ([#383](https://github.com/putyourlightson/craft-campaign/issues/383)).
- Changed the user permission for managing contacts to only show a note about the primary site if multiple sites exist.
Expand Down
2 changes: 1 addition & 1 deletion src/Campaign.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public static function editions(): array
/**
* @inheritdoc
*/
public string $schemaVersion = '2.5.1';
public string $schemaVersion = '2.7.0';

/**
* @inheritdoc
Expand Down
48 changes: 21 additions & 27 deletions src/controllers/ImportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,20 +112,11 @@ public function actionImportFile(): ?Response
{
$this->requirePostRequest();

$import = new ImportModel();
$import->assetId = $this->request->getRequiredBodyParam('assetId');
$import->fileName = $this->request->getRequiredBodyParam('fileName');

$import = $this->_getImportModelFromParams();
$mailingListIds = $this->request->getBodyParam('mailingListIds');
$import->mailingListId = $mailingListIds[0] ?? null;

$import->forceSubscribe = (bool)$this->request->getBodyParam('forceSubscribe');

// Get email and custom field indexes
$import->emailFieldIndex = $this->request->getBodyParam('emailFieldIndex');
$import->fieldIndexes = $this->request->getBodyParam('fieldIndexes');

// Validate it
if (!$import->validate()) {
$errors = implode('. ', $import->getErrorSummary(true));
Campaign::$plugin->log('Couldn’t import file. {errors}', ['errors' => $errors]);
Expand Down Expand Up @@ -190,26 +181,10 @@ public function actionImportUserGroup(): ?Response
{
$this->requirePostRequest();

$import = new ImportModel();
$import = $this->_getImportModelFromParams();
$import->userGroupId = $this->request->getRequiredBodyParam('userGroupId');

$mailingListIds = $this->request->getBodyParam('mailingListIds');
$import->mailingListId = $mailingListIds[0] ?? null;

$import->forceSubscribe = (bool)$this->request->getBodyParam('forceSubscribe');

// Get core fields and custom field indexes
$import->emailFieldIndex = $this->request->getBodyParam('emailFieldIndex');
$import->fieldIndexes = $this->request->getBodyParam('fieldIndexes', []);

// Prepend `field_` to each custom field index
foreach ($import->fieldIndexes as $key => $fieldIndex) {
if ($fieldIndex != 'firstName' && $fieldIndex != 'lastName') {
$import->fieldIndexes[$key] = 'field_' . $fieldIndex;
}
}

// Validate it
if (!$import->validate()) {
$errors = implode('. ', $import->getErrorSummary(true));
Campaign::$plugin->log('Couldn’t import user group. {errors}', ['errors' => $errors]);
Expand Down Expand Up @@ -312,4 +287,23 @@ private function _returnFieldsTemplate(ImportModel $import, array|string|null $m

return $this->renderTemplate('campaign/contacts/import/_fields', $variables);
}

private function _getImportModelFromParams(): ImportModel
{
$import = new ImportModel();
$import->assetId = $this->request->getRequiredBodyParam('assetId');
$import->fileName = $this->request->getRequiredBodyParam('fileName');
$import->unsubscribe = (bool)$this->request->getBodyParam('unsubscribe');
$import->forceSubscribe = (bool)$this->request->getBodyParam('forceSubscribe');
$import->emailFieldIndex = $this->request->getBodyParam('emailFieldIndex');
$import->fieldIndexes = $this->request->getBodyParam('fieldIndexes');

foreach ($import->fieldIndexes as $key => $fieldIndex) {
if ($fieldIndex != 'firstName' && $fieldIndex != 'lastName') {
$import->fieldIndexes[$key] = 'field_' . $fieldIndex;
}
}

return $import;
}
}
4 changes: 2 additions & 2 deletions src/controllers/TrackerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ public function actionClick(): ?Response

// Split the URL on the anchor hashtag, so we can add it at the end.
// https://github.com/putyourlightson/craft-campaign/issues/383
$urlParts = explode( "#", $url);
$urlParts = explode("#", $url);
$url = $urlParts[0];
$hashtag = !empty($urlParts[1]) ? '#' . $urlParts[1] : '';
$hashtag = !empty($urlParts[1]) ? '#' . $urlParts[1] : '';

$url .= str_contains($url, '?') ? '&' : '?';
$url .= trim($queryStringParameters, '?&');
Expand Down
1 change: 1 addition & 0 deletions src/migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ protected function createTables(): bool
'userGroupId' => $this->integer(),
'userId' => $this->integer(),
'mailingListId' => $this->integer(),
'unsubscribe' => $this->boolean()->defaultValue(false)->notNull(),
'forceSubscribe' => $this->boolean()->defaultValue(false)->notNull(),
'emailFieldIndex' => $this->string(),
'fieldIndexes' => $this->text(),
Expand Down
35 changes: 35 additions & 0 deletions src/migrations/m230513_110000_add_unsubscribe_column.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace putyourlightson\campaign\migrations;

use craft\db\Migration;
use putyourlightson\campaign\records\ImportRecord;

class m230513_110000_add_unsubscribe_column extends Migration
{
/**
* @inheritdoc
*/
public function safeUp(): bool
{
if (!$this->db->columnExists(ImportRecord::tableName(), 'unsubscribe')) {
$this->addColumn(
ImportRecord::tableName(),
'unsubscribe',
$this->boolean()->defaultValue(false)->notNull()->after('mailingListId'),
);
}

return true;
}

/**
* @inheritdoc
*/
public function safeDown(): bool
{
echo self::class . " cannot be reverted.\n";

return false;
}
}
5 changes: 5 additions & 0 deletions src/models/ImportModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ class ImportModel extends Model
*/
public ?int $mailingListId = null;

/**
* @var bool Unsubscribe
*/
public bool $unsubscribe = false;

/**
* @var bool Force subscribe
*/
Expand Down
3 changes: 2 additions & 1 deletion src/records/ImportRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
* @property int $userGroupId User group ID
* @property int $userId User ID
* @property int $mailingListId Mailing list ID
* @property bool $forceSubscribeForce
* @property bool $unsubscribe
* @property bool $forceSubscribe
* @property string $emailFieldIndex Email field index
* @property mixed $fieldIndexesField
* @property int $added Added
Expand Down
5 changes: 3 additions & 2 deletions src/services/ImportsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,9 @@ public function importRow(ImportModel $import, array $row, int $lineNumber): Imp

Campaign::$plugin->imports->saveImport($import);

// Subscribe contact only if contact's mailing list subscription status is empty or forcing is enabled
if ($contact->getMailingListSubscriptionStatus($import->mailingListId) == '' || $import->forceSubscribe) {
if ($import->unsubscribe) {
Campaign::$plugin->mailingLists->addContactInteraction($contact, $mailingList, 'unsubscribed', 'import', $import->id);
} elseif ($contact->getMailingListSubscriptionStatus($import->mailingListId) == '' || $import->forceSubscribe) {
Campaign::$plugin->mailingLists->addContactInteraction($contact, $mailingList, 'subscribed', 'import', $import->id);
}

Expand Down
28 changes: 21 additions & 7 deletions src/templates/contacts/import/_fields.twig
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,27 @@
required: true
}) }}

{{ forms.lightswitchField({
label: "Force Subscribe"|t('campaign'),
instructions: "Whether contacts should be subscribed to the mailing list even if they were previously unsubscribed."|t('campaign'),
name: 'forceSubscribe',
on: import.forceSubscribe,
errors: import.getErrors('forceSubscribe')
}) }}
<div id="unsubscribeField" class="field">
{{ forms.lightswitchField({
label: "Unsubscribe"|t('campaign'),
instructions: "Whether contacts should be imported to the mailing list with a status of unsubscribed."|t('campaign'),
name: 'unsubscribe',
on: import.unsubscribe,
errors: import.getErrors('unsubscribe'),
reverseToggle: 'forceSubscribeField',
}) }}
</div>

<div id="forceSubscribeField" class="field">
{{ forms.lightswitchField({
label: "Force Subscribe"|t('campaign'),
instructions: "Whether contacts should be subscribed to the mailing list even if they were previously unsubscribed."|t('campaign'),
name: 'forceSubscribe',
on: import.forceSubscribe,
errors: import.getErrors('forceSubscribe'),
reverseToggle: 'unsubscribeField',
}) }}
</div>

{% set input %}
<div class="select">
Expand Down
9 changes: 6 additions & 3 deletions src/templates/contacts/import/index.twig
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,11 @@
class: 'go',
target: '_blank',
}) : '-',
forceSubscribe: forms.lightswitch({
on: import.forceSubscribe,
disabled: true,
unsubscribe: tag('span', {
class: 'status ' ~ (import.unsubscribe ? 'enabled'),
}),
forceSubscribe: tag('span', {
class: 'status ' ~ (import.forceSubscribe ? 'enabled'),
}),
added: import.added,
updated: import.updated,
Expand All @@ -133,6 +135,7 @@
{ name: '__slot:title', title: Craft.t('campaign', 'Import') },
{ name: 'source', title: Craft.t('campaign', 'Source') },
{ name: 'mailingList', title: Craft.t('campaign', 'Mailing List') },
{ name: 'unsubscribe', title: Craft.t('campaign', 'Unsubscribe') },
{ name: 'forceSubscribe', title: Craft.t('campaign', 'Force Subscribe') },
{ name: 'added', title: Craft.t('campaign', 'Added') },
{ name: 'updated', title: Craft.t('campaign', 'Updated') },
Expand Down
1 change: 1 addition & 0 deletions src/translations/de/campaign.php
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@
'What this campaign type will be called in the CP.' => 'Wie dieser Kampagnen-Typ im Control Panel genannt wird.',
'What this mailing list type will be called in the CP.' => 'Wie dieser Mailing-Listen-Typ im Control Panel genannt wird.',
'Whether contacts are allowed to unsubscribe themselves through an unsubscribe form.' => 'Ob es Kontakten erlaubt ist, sich selbst über ein Abmeldeformular abzumelden.',
'Whether contacts should be imported to the mailing list with a status of unsubscribed.' => '',
'Whether contacts should be subscribed to the mailing list even if they were previously unsubscribed.' => 'Ob Kontakte zur Mailing-Liste hinzugefügt werden sollen, auch wenn sie vorher schon einmal ausgetragen wurden.',
'Whether the contact needs to verify their email address by clicking on a link in a verification email that will be sent to them after submitting a subscribe form (highly recommended).' => 'Ob der Kontakt seine E-Mail-Adresse durch Klick auf einen Link in einer Verifizierungs-E-Mail bestätigen muss, die ihm geschickt wird, nachdem er ein Anmeldeformular abgeschickt hat (dringend empfohlen).',
'Whether the sendout can be sent to contacts multiple times.' => 'Ob die Sendung mehrmals an Kontakte verschickt werden kann.',
Expand Down
1 change: 1 addition & 0 deletions src/translations/en/campaign.php
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@
'What this campaign type will be called in the CP.' => '',
'What this mailing list type will be called in the CP.' => '',
'Whether contacts are allowed to unsubscribe themselves through an unsubscribe form.' => '',
'Whether contacts should be imported to the mailing list with a status of unsubscribed.' => '',
'Whether contacts should be subscribed to the mailing list even if they were previously unsubscribed.' => '',
'Whether the contact needs to verify their email address by clicking on a link in a verification email that will be sent to them after submitting a subscribe form (highly recommended).' => '',
'Whether the sendout can be sent to contacts multiple times.' => '',
Expand Down
1 change: 1 addition & 0 deletions src/translations/fr/campaign.php
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@
'What this campaign type will be called in the CP.' => 'Comment ce type de newsletter sera appelé dans le CP.',
'What this mailing list type will be called in the CP.' => 'Comment ce type de liste de diffusion sera appelé dans le CP.',
'Whether contacts are allowed to unsubscribe themselves through an unsubscribe form.' => 'Si les contacts sont autorisés à se désabonner eux-mêmes par le biais d’un formulaire de désabonnement.',
'Whether contacts should be imported to the mailing list with a status of unsubscribed.' => '',
'Whether contacts should be subscribed to the mailing list even if they were previously unsubscribed.' => 'Précisez si les contacts doivent être inscrits à la liste de diffusion même s’ils ont été précédemment désabonnés.',
'Whether the contact needs to verify their email address by clicking on a link in a verification email that will be sent to them after submitting a subscribe form (highly recommended).' => 'Précisez si le contact doit vérifier son adresse mail en cliquant sur un lien dans un mail de vérification qui lui sera envoyé après avoir soumis un formulaire d‘inscription (fortement recommandé).',
'Whether the sendout can be sent to contacts multiple times.' => 'Précisez si la campagne peut être envoyée plusieurs fois aux contacts.',
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/services/ImportsServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,12 @@ public function testImportRow(): void

// Assert that contact is subscribed to the mailing list
$this->assertEquals('subscribed', $contact->getMailingListSubscriptionStatus($mailingList->id));

$import->unsubscribe = true;
$import->forceSubscribe = false;
Campaign::$plugin->imports->importRow($import, $row, 1);

// Assert that contact is unsubscribed from the mailing list
$this->assertEquals('unsubscribed', $contact->getMailingListSubscriptionStatus($mailingList->id));
}
}

0 comments on commit dd4780e

Please sign in to comment.