Add expiration notification for paid subscriptions
authorMatthias Schmidt <gravatronics@live.com>
Tue, 30 May 2017 16:33:58 +0000 (18:33 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Tue, 30 May 2017 16:33:58 +0000 (18:33 +0200)
Close #2216

14 files changed:
com.woltlab.wcf/cronjob.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/userNotificationEvent.xml
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscription.class.php
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUser.class.php
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserAction.class.php
wcfsetup/install/files/lib/system/cronjob/ExpiringPaidSubscriptionUserCronjob.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/template/plugin/DateDiffModifierTemplatePlugin.class.php
wcfsetup/install/files/lib/system/user/notification/event/PaidSubscriptionUserUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/PaidSubscriptionUserUserNotificationObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/type/PaidSubscriptionUserUserNotificationObjectType.class.php [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index 494f1ca1a47d4452544272effa11cd8434f80734..2babef090102f28e5131e1927865d0313b448a8c 100644 (file)
                        <canbeedited>1</canbeedited>
                        <canbedisabled>1</canbedisabled>
                </cronjob>
+               
+               <cronjob name="com.woltlab.wcf.expiringPaidSubscriptionUser">
+                       <classname>wcf\system\cronjob\ExpiringPaidSubscriptionUserCronjob</classname>
+                       <description>Sends notifications about expiring paid subscriptions</description>
+                       <description language="de">Sendet Benachrichtigungen über ablaufende bezahlte Mitgliedschaften</description>
+                       <startminute>0</startminute>
+                       <starthour>2</starthour>
+                       <startdom>*</startdom>
+                       <startmonth>*</startmonth>
+                       <startdow>*</startdow>
+                       <canbeedited>1</canbeedited>
+                       <canbedisabled>1</canbedisabled>
+               </cronjob>
        </import>
        
        <delete>
index 70db36e6771b6846fad9ab34332122dae6cf427b..bf77d9dcbdcd1b5346b32b93b3e6912887dd3ce4 100644 (file)
                        <classname>wcf\system\payment\method\PaypalPaymentMethod</classname>
                </type>
                
+               <!-- paid subscriptions -->
                <type>
                        <name>com.woltlab.wcf.payment.type.paidSubscription</name>
                        <definitionname>com.woltlab.wcf.payment.type</definitionname>
                        <classname>wcf\system\payment\type\PaidSubscriptionPaymentType</classname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.paidSubscription.user</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>wcf\system\user\notification\object\type\PaidSubscriptionUserUserNotificationObjectType</classname>
+                       <category>com.woltlab.wcf.user</category>
+               </type>
+               <!-- /paid subscriptions -->
                
                <!-- bulk processable objects -->
                <type>
index 3f18636be40bc4be15081b61aea14945ee860d8c..553523db92b55815b67e371dd78fc9ad9214de52 100644 (file)
                        <preset>1</preset>
                        <permissions>mod.general.canUseModeration</permissions>
                </event>
+               
+               <event>
+                       <name>expiring</name>
+                       <objecttype>com.woltlab.wcf.paidSubscription.user</objecttype>
+                       <classname>wcf\system\user\notification\event\PaidSubscriptionUserUserNotificationEvent</classname>
+                       <options>module_paid_subscription</options>
+               </event>
        </import>
 </data>
index d6a257d25bec79d09f67a14813fb19560503eb02..e67371d96b2bca3a374ebcb0d9221a613eb37f06 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\paid\subscription;
+use wcf\data\ITitledObject;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\DatabaseObject;
 use wcf\system\html\output\HtmlOutputProcessor;
@@ -28,7 +29,7 @@ use wcf\system\WCF;
  * @property-read      string          $groupIDs                       comma-separated list with the ids of the user groups for which the subscription pays membership
  * @property-read      string          $excludedSubscriptionIDs        comma-separated list with the ids of paid subscriptions which prohibit purchase of this paid subscription
  */
-class PaidSubscription extends DatabaseObject {
+class PaidSubscription extends DatabaseObject implements ITitledObject {
        /**
         * Returns list of purchase buttons.
         * 
@@ -88,4 +89,12 @@ class PaidSubscription extends DatabaseObject {
                
                return $this->description;
        }
+       
+       /**
+        * @see         ITitledObject::getTitle()
+        * @since       3.1
+        */
+       public function getTitle() {
+               return WCF::getLanguage()->get($this->title);
+       }
 }
index 10c0c50761c8bd03be40b3e160a2f3e115c92a06..d1f15ca2eac1a0b6a98ad44d5ddf89c7c27586ad 100644 (file)
@@ -13,12 +13,13 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Paid\Subscription\User
  * 
- * @property-read      integer         $subscriptionUserID     unique id of the paid subscription-user-association
- * @property-read      integer         $subscriptionID         id of the paid subscription the paid subscription-user-association belongs to
- * @property-read      integer         $userID                 id of the user the paid subscription-user-association belongs to
- * @property-read      integer         $startDate              timestamp at which the paid subscription started
- * @property-read      integer         $endDate                timestamp at which the paid subscription ended or will end
- * @property-read      integer         $isActive               is `1` if the user's paid subscription is currently active and thus not expired, otherwise `0`
+ * @property-read      integer         $subscriptionUserID             unique id of the paid subscription-user-association
+ * @property-read      integer         $subscriptionID                 id of the paid subscription the paid subscription-user-association belongs to
+ * @property-read      integer         $userID                         id of the user the paid subscription-user-association belongs to
+ * @property-read      integer         $startDate                      timestamp at which the paid subscription started
+ * @property-read      integer         $endDate                        timestamp at which the paid subscription ended or will end
+ * @property-read      integer         $isActive                       is `1` if the user's paid subscription is currently active and thus not expired, otherwise `0`
+ * @property-read      integer         $sentExpirationNotification     is `1` if the user has been notified that the paid subscription is expiring
  */
 class PaidSubscriptionUser extends DatabaseObject {
        /**
index 412ad153cdc80fca79920fce83dcac9920f4a3f1..a9dfe65558c0c00f63db7d83c658c4785269bf06 100644 (file)
@@ -104,7 +104,8 @@ class PaidSubscriptionUserAction extends AbstractDatabaseObjectAction {
                        
                        $subscriptionUser->update([
                                'endDate' => $endDate,
-                               'isActive' => 1
+                               'isActive' => 1,
+                               'sentExpirationNotification' => 0
                        ]);
                        
                        if (!$subscriptionUser->isActive) {
diff --git a/wcfsetup/install/files/lib/system/cronjob/ExpiringPaidSubscriptionUserCronjob.class.php b/wcfsetup/install/files/lib/system/cronjob/ExpiringPaidSubscriptionUserCronjob.class.php
new file mode 100644 (file)
index 0000000..6471430
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserEditor;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\user\notification\object\PaidSubscriptionUserUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+
+/**
+ * Sends notifications for expiring paid subscriptions.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cronjob
+ * @since      3.1
+ */
+class ExpiringPaidSubscriptionUserCronjob extends AbstractCronjob {
+       /**
+        * @inheritDoc
+        */
+       public function execute(Cronjob $cronjob) {
+               parent::execute($cronjob);
+               
+               // determine when the notification will be send prior to its expiration
+               $conditionBuilder = new PreparedStatementConditionBuilder(false, 'OR');
+               
+               // one week before if the subscription lasts months or years (and not just days)
+               $conditionBuilder->add(
+                       '(paid_subscription.subscriptionLengthUnit <> ? AND paid_subscription_user.endDate < ?)',
+                       ['d', TIME_NOW + 7 * 24 * 3600]
+               );
+               // one week before if the subscription lasts for more than two weeks (2 * 7 days)
+               $conditionBuilder->add(
+                       '(paid_subscription.subscriptionLengthUnit = ? AND paid_subscription.subscriptionLength > ? AND paid_subscription_user.endDate < ?)',
+                       ['d', 2 * 7, TIME_NOW + 7 * 24 * 3600]
+               );
+               // two days before if the subscription lasts for less than two weeks (2 * 7 days)
+               $conditionBuilder->add(
+                       '(paid_subscription.subscriptionLengthUnit = ? AND paid_subscription.subscriptionLength <= ? AND paid_subscription_user.endDate < ?)',
+                       ['d', 2 * 7, TIME_NOW + 2 * 24 * 3600]
+               );
+               
+               $paidSubscriptionUserList = new PaidSubscriptionUserList();
+               $paidSubscriptionUserList->sqlJoins .= " LEFT JOIN wcf".WCF_N."_paid_subscription paid_subscription ON (paid_subscription.subscriptionID = paid_subscription_user.subscriptionID)";
+               $paidSubscriptionUserList->getConditionBuilder()->add('paid_subscription_user.endDate <> ?', [0]);
+               $paidSubscriptionUserList->getConditionBuilder()->add('(' . $conditionBuilder . ')', $conditionBuilder->getParameters());
+               $paidSubscriptionUserList->getConditionBuilder()->add('paid_subscription_user.isActive = ?', [1]);
+               $paidSubscriptionUserList->getConditionBuilder()->add('paid_subscription_user.sentExpirationNotification = ?', [0]);
+               $paidSubscriptionUserList->readObjects();
+               
+               foreach ($paidSubscriptionUserList as $paidSubscriptionUser) {
+                       UserNotificationHandler::getInstance()->fireEvent(
+                               'expiring',
+                               'com.woltlab.wcf.paidSubscription.user',
+                               new PaidSubscriptionUserUserNotificationObject($paidSubscriptionUser),
+                               [$paidSubscriptionUser->userID]
+                       );
+               }
+               
+               // remember that notification has already been sent (or at least
+               // considered if the user does not want to receive notifications)
+               WCF::getDB()->beginTransaction();
+               foreach ($paidSubscriptionUserList as $paidSubscriptionUser) {
+                       (new PaidSubscriptionUserEditor($paidSubscriptionUser))->update([
+                               'sentExpirationNotification' => 1
+                       ]);
+               }
+               WCF::getDB()->commitTransaction();
+       }
+}
\ No newline at end of file
index f5262c254e9af85a8eeceecaa61f2810400a56a2..6032896b1c62fb080129ea08346a56e8635343cd 100644 (file)
@@ -9,8 +9,9 @@ use wcf\util\DateUtil;
  * indicates if the full difference is returned or just a rounded difference.
  * 
  * Usage:
- *     {$timestamp|dateDiff}
- *     {"123456789"|dateDiff:$timestamp:$fullInverval}
+ *     {$endTimestamp|dateDiff}
+ *     {$endTimestamp|dateDiff:$startTimestamp:$fullInterval}
+ *     {$endTimestamp|dateDiff:$startTimestamp:$fullInterval:$inSentence}
  * 
  * @author     Matthias Schmidt, Marcel Werk
  * @copyright  2001-2017 WoltLab GmbH
@@ -31,9 +32,14 @@ class DateDiffModifierTemplatePlugin implements IModifierTemplatePlugin {
                        $fullInterval = $tagArgs[2];
                }
                
+               $inSentence = false;
+               if (isset($tagArgs[3])) {
+                       $inSentence = $tagArgs[3];
+               }
+               
                $startTime = DateUtil::getDateTimeByTimestamp($tagArgs[1]);
                $endTime = DateUtil::getDateTimeByTimestamp($tagArgs[0]);
                
-               return DateUtil::formatInterval($endTime->diff($startTime), $fullInterval);
+               return DateUtil::formatInterval($endTime->diff($startTime), $fullInterval, $inSentence);
        }
 }
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/PaidSubscriptionUserUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/PaidSubscriptionUserUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..52cf13d
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\object\PaidSubscriptionUserUserNotificationObject;
+use wcf\system\WCF;
+
+/**
+ * Notification event for followers.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      3.1
+ * 
+ * @method     PaidSubscriptionUserUserNotificationObject      getUserNotificationObject()
+ */
+class PaidSubscriptionUserUserNotificationEvent extends AbstractUserNotificationEvent {
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return LinkHandler::getInstance()->getLink('PaidSubscriptionList', ['forceFrontend' => true]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getMessage() {
+               return $this->getLanguage()->getDynamicVariable('wcf.paidSubscription.notification.message', [
+                       'author' => $this->author,
+                       'notification' => $this->notification, 
+                       'userNotificationObject' => $this->getUserNotificationObject()
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getLanguage()->get('wcf.paidSubscription.notification.title');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function isVisible() {
+               $userSubscriptionList = new PaidSubscriptionUserList();
+               $userSubscriptionList->getConditionBuilder()->add('userID = ?', [WCF::getUser()->userID]);
+               $userSubscriptionList->getConditionBuilder()->add('isActive = ?', [1]);
+               
+               return $userSubscriptionList->countObjects() > 0;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/PaidSubscriptionUserUserNotificationObject.class.php b/wcfsetup/install/files/lib/system/user/notification/object/PaidSubscriptionUserUserNotificationObject.class.php
new file mode 100644 (file)
index 0000000..de680d4
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\system\request\LinkHandler;
+
+/**
+ * Represents a paid subscription user as a notification object.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Object
+ * @since      3.1
+ * 
+ * @method     PaidSubscriptionUser    getDecoratedObject()
+ * @mixin      PaidSubscriptionUser
+ */
+class PaidSubscriptionUserUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = PaidSubscriptionUser::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getAuthorID() {
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getSubscription()->getTitle();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getURL() {
+               return LinkHandler::getInstance()->getLink('PaidSubscriptionList', ['forceFrontend' => true]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/type/PaidSubscriptionUserUserNotificationObjectType.class.php b/wcfsetup/install/files/lib/system/user/notification/object/type/PaidSubscriptionUserUserNotificationObjectType.class.php
new file mode 100644 (file)
index 0000000..46276d9
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\system\user\notification\object\type;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\system\user\notification\object\PaidSubscriptionUserUserNotificationObject;
+
+/**
+ * Represents a paid subscription user notification object type.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Notification\Object\Type
+ * @since      3.1
+ */
+class PaidSubscriptionUserUserNotificationObjectType extends AbstractUserNotificationObjectType {
+       /**
+        * @inheritDoc
+        */
+       protected static $decoratorClassName = PaidSubscriptionUserUserNotificationObject::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $objectClassName = PaidSubscriptionUser::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $objectListClassName = PaidSubscriptionUserList::class;
+}
index aba1341ea57091a8749ea2d100941f3b3e2c1267..a18bc18df6e4033197e97635b7b7091c63461531 100644 (file)
@@ -2990,6 +2990,8 @@ Fehler sind beispielsweise:
                <item name="wcf.paidSubscription.returnMessage"><![CDATA[Danke für {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Zahlung. {if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Transaktion wurde abgeschlossen. Sobald {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Zahlung von uns verarbeitet wurde, wird die erworbene Mitgliedschaft aktiviert.]]></item>
                <item name="wcf.paidSubscription.confirmTOS"><![CDATA[Hiermit bestätige ich mein Einverständnis mit den <a href="{PAID_SUBSCRIPTION_TOS_URL}">Nutzungsbedingungen</a>]]></item>
                <item name="wcf.paidSubscription.button.moreInformation"><![CDATA[Mehr Informationen]]></item>
+               <item name="wcf.paidSubscription.notification.title"><![CDATA[Ablaufende Mitgliedschaft]]></item>
+               <item name="wcf.paidSubscription.notification.message"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Mitgliedschaft „{$userNotificationObject->getTitle()}“ läuft {$userNotificationObject->endDate|dateDiff:$notification->time:false:true} (am {$userNotificationObject->endDate|date:'d. F'}) ab.]]></item>
        </category>
        
        <category name="wcf.payment">
@@ -3650,6 +3652,7 @@ Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponseOwner"><![CDATA[Neue Antwort auf einen Kommentar an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.like.notification.like"><![CDATA[Jemandem gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Kommentar]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.like.notification.like"><![CDATA[Jemandem gefällt {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.paidSubscription.user.expiring"><![CDATA[Eine {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Mitgliedschaft läuft bald ab]]></item>
                
                <item name="wcf.user.notification.com.woltlab.wcf.moderation"><![CDATA[Moderation]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.moderation.queue.notification.comment"><![CDATA[Neuer Kommentar in der Moderation]]></item>
index 004199ee56d35af81ceac697401c2f11c6701803..244bfd9029e9fdcda5605a924542d2688f708d2f 100644 (file)
@@ -2983,6 +2983,8 @@ Errors are:
                <item name="wcf.paidSubscription.returnMessage"><![CDATA[Thank you for your payment, the transaction has been completed. Your subscription will be active once your payment has been processed by us.]]></item>
                <item name="wcf.paidSubscription.confirmTOS"><![CDATA[I agree to the <a href="{PAID_SUBSCRIPTION_TOS_URL}">Terms of Service</a>]]></item>
                <item name="wcf.paidSubscription.button.moreInformation"><![CDATA[More Details]]></item>
+               <item name="wcf.paidSubscription.notification.title"><![CDATA[Expiring Subscription]]></item>
+               <item name="wcf.paidSubscription.notification.message"><![CDATA[Your subscription “{$userNotificationObject->getTitle()}” will expire {$userNotificationObject->endDate|dateDiff:$notification->time:false:true} (on {$userNotificationObject->endDate|date:'F jS'}).]]></item>
        </category>
        
        <category name="wcf.payment">
@@ -3639,6 +3641,7 @@ your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponseOwner"><![CDATA[Notify me of new replies to comments on my wall]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.like.notification.like"><![CDATA[Notify me when my comments are liked]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.like.notification.like"><![CDATA[Notify me when replies to my comments are liked]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.paidSubscription.user.expiring"><![CDATA[Notify me before a subscription will expire]]></item>
                
                <item name="wcf.user.notification.com.woltlab.wcf.moderation"><![CDATA[Moderation]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.moderation.queue.notification.comment"><![CDATA[Notify me when new comments are written in moderation]]></item>
index 00a1f6c60d4a3cddf334f521120bdd022e68d116..e98c7e87e02d6c33146661516a9c1612f5134418 100644 (file)
@@ -1036,6 +1036,7 @@ CREATE TABLE wcf1_paid_subscription_user (
        startDate INT(10) NOT NULL DEFAULT 0,
        endDate INT(10) NOT NULL DEFAULT 0,
        isActive TINYINT(1) NOT NULL DEFAULT 1,
+       sentExpirationNotification TINYINT(1) NOT NULL DEFAULT 0,
        
        UNIQUE KEY (subscriptionID, userID),
        KEY (isActive)