Skip to content

Commit

Permalink
Add LastActivity and CampaignActivity rules
Browse files Browse the repository at this point in the history
  • Loading branch information
bencroker committed Apr 26, 2022
1 parent c5fdb58 commit 1c6a7b6
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Added user group permissions for campaign types and mailing list types.
- Added the ability to view disabled campaigns using a token URL.
- Added a contact condition builder to regular segment types, that should be used going forward since `legacy` and `template` segment types will be removed in Campaign 3.
- Added a "Campaign Activity" condition rule for segmenting by contacts who have opened or clicked a link in any or a specific campaign ([#244](https://github.com/putyourlightson/craft-campaign/issues/244)).
- Added the `campaign/reports/anonymize` console controller that anonymizes all previously collected personal data.
- Added an "Export to CSV" button to all datatables in reports ([#245](https://github.com/putyourlightson/craft-campaign/issues/245)).
- Added the `enableAnonymousTracking` setting, which prevents tracking of contact interactions ([#115](https://github.com/putyourlightson/craft-campaign/issues/115)).
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
### Added
- Added a condition builder field to the sendout schedule for automated and recurring sendout types ([#305](https://github.com/putyourlightson/craft-campaign/issues/305)).
- Added a contact condition builder to regular segment types, that should be used going forward since `legacy` and `template` segment types will be removed in Campaign 3.
- Added a "Campaign Activity" condition rule for segmenting by contacts who have opened or clicked a link in any or a specific campaign ([#244](https://github.com/putyourlightson/craft-campaign/issues/244)).
- Added a list of failed contacts to sendouts that have failures ([#311](https://github.com/putyourlightson/craft-campaign/issues/311)).

### Changed
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"require": {
"php": "^8.0.2",
"ext-dom": "*",
"craftcms/cms": "^4.0.0-beta.3",
"craftcms/cms": "^4.0.0-RC1",
"matomo/device-detector": "^3.9.1",
"html2text/html2text": "^4.3.1",
"elvanto/litemoji": "^1.4.4|^2.0.4|^3.0.1|^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/base/ScheduleModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public function canSendNow(SendoutElement $sendout): bool
}
}

// Ensure sendout passes condition rule
// Ensure sendout passes condition rules
if (!$this->getCondition()->matchElement($sendout)) {
return false;
}
Expand Down
138 changes: 138 additions & 0 deletions src/elements/conditions/contacts/CampaignActivityConditionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* @copyright Copyright (c) PutYourLightsOn
*/

namespace putyourlightson\campaign\elements\conditions\contacts;

use Craft;
use craft\base\conditions\BaseElementSelectConditionRule;
use craft\base\ElementInterface;
use craft\elements\conditions\ElementConditionRuleInterface;
use craft\elements\db\ElementQueryInterface;
use putyourlightson\campaign\elements\CampaignElement;
use putyourlightson\campaign\elements\db\ContactElementQuery;
use putyourlightson\campaign\records\ContactCampaignRecord;

/**
* @since 2.0.0
*/
class CampaignActivityConditionRule extends BaseElementSelectConditionRule implements ElementConditionRuleInterface
{
/**
* @inheritdoc
*/
public string $operator = 'opened';

/**
* @inheritdoc
*/
protected bool $reloadOnOperatorChange = true;

/**
* @inheritdoc
*/
protected function elementType(): string
{
return CampaignElement::class;
}

/**
* @inheritdoc
*/
public function getLabel(): string
{
return Craft::t('campaign', 'Campaign Activity');
}

/**
* @inheritdoc
*/
public function getExclusiveQueryParams(): array
{
return [];
}

public function modifyQuery(ElementQueryInterface $query): void
{
/** @var ContactElementQuery $query */
$query->innerJoin(ContactCampaignRecord::tableName(), '[[campaign_contacts.id]] = [[contactId]]');

$query->andWhere(['not', [
$this->_operatorColumn($this->operator) => null,
]]);

$elementId = $this->getElementId();
if ($elementId !== null) {
$query->andWhere(['campaignId' => $elementId]);
}
}

/**
* @inheritdoc
*/
protected function inputHtml(): string
{
return match ($this->operator) {
'openedCampaign', 'clickedCampaign' => parent::inputHtml(),
default => '',
};
}

protected function operators(): array
{
return [
'opened',
'clicked',
'openedCampaign',
'clickedCampaign',
];
}

/**
* @inheritdoc
*/
protected function operatorLabel(string $operator): string
{
return match ($operator) {
'opened' => Craft::t('campaign', 'opened any campaign'),
'clicked' => Craft::t('campaign', 'clicked a link in any campaign'),
'openedCampaign' => Craft::t('campaign', 'opened the campaign'),
'clickedCampaign' => Craft::t('campaign', 'clicked a link in the campaign'),
default => parent::operatorLabel($operator),
};
}

/**
* @inheritdoc
*/
public function matchElement(ElementInterface $element): bool
{
$query = ContactCampaignRecord::find()
->where([
'contactId' => $element->id,
])
->andWhere(['not', [
$this->_operatorColumn($this->operator) => null,
]
]);

$elementId = $this->getElementId();
if ($elementId !== null) {
$query->andWhere(['campaignId' => $elementId]);
}

return $query->exists();
}

/**
* Returns the column to query on based on the operator.
*/
private function _operatorColumn(string $operator): string
{
return match ($operator) {
'opened', 'openedCampaign' => 'opened',
'clicked', 'clickedCampaign' => 'clicked',
};
}
}
2 changes: 2 additions & 0 deletions src/elements/conditions/contacts/ContactCondition.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ protected function conditionRuleTypes(): array
{
return array_merge(parent::conditionRuleTypes(), [
EmailConditionRule::class,
LastActivityConditionRule::class,
CampaignActivityConditionRule::class,
]);
}
}
54 changes: 54 additions & 0 deletions src/elements/conditions/contacts/LastActivityConditionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* @copyright Copyright (c) PutYourLightsOn
*/

namespace putyourlightson\campaign\elements\conditions\contacts;

use Craft;
use craft\base\conditions\BaseDateRangeConditionRule;
use craft\base\ElementInterface;
use craft\elements\conditions\ElementConditionRuleInterface;
use craft\elements\db\ElementQueryInterface;
use putyourlightson\campaign\elements\ContactElement;
use putyourlightson\campaign\elements\db\ContactElementQuery;

/**
* @since 2.0.0
*/
class LastActivityConditionRule extends BaseDateRangeConditionRule implements ElementConditionRuleInterface
{
/**
* @inheritdoc
*/
public function getLabel(): string
{
return Craft::t('campaign', 'Last Activity');
}

/**
* @inheritdoc
*/
public function getExclusiveQueryParams(): array
{
return ['lastActivity'];
}

/**
* @inheritdoc
*/
public function modifyQuery(ElementQueryInterface $query): void
{
/** @var ContactElementQuery $query */
$query->lastActivity($this->queryParamValue());
}

/**
* @inheritdoc
*/
public function matchElement(ElementInterface $element): bool
{
/** @var ContactElement $element */
return $this->matchValue($element->lastActivity);
}
}
16 changes: 15 additions & 1 deletion src/elements/db/ContactElementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use craft\elements\db\ElementQuery;
use craft\helpers\Db;
use putyourlightson\campaign\Campaign;

use putyourlightson\campaign\elements\ContactElement;
use putyourlightson\campaign\records\ContactMailingListRecord;
use yii\db\Connection;
Expand Down Expand Up @@ -45,6 +44,11 @@ class ContactElementQuery extends ElementQuery
*/
public ?int $segmentId = null;

/**
* @var mixed When the resulting contacts were last active.
*/
public mixed $lastActivity = null;

/**
* Sets the [[userId]] property.
*/
Expand Down Expand Up @@ -95,6 +99,16 @@ public function segmentId(int $value): static
return $this;
}

/**
* Sets the [[lastActivity]] property.
*/
public function lastActivity(mixed $value): static
{
$this->lastActivity = $value;

return $this;
}

/**
* @inheritdoc
*/
Expand Down
3 changes: 2 additions & 1 deletion src/templates/segments/_includes/segmentTypes/regular.twig
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{% import '_includes/forms' as forms %}
{% import 'campaign/_macros' as macros %}

{% set input %}
{{ segment.contactCondition.getBuilderHtml()|raw }}
{% endset %}

{{ forms.field({
label: 'Contact Condition'|t('campaign'),
instructions: 'One or more condition rules to apply to this segment.'|t('campaign'),
instructions: 'One or more condition rules to apply to this segment.'|t('campaign') ~ macros.info('Condition rules can be created via a custom plugin/module.'),
errors: segment.getErrors('contactCondition')
}, input) }}

0 comments on commit 1c6a7b6

Please sign in to comment.