(NFC) HookStyleListener - Comments
[civicrm-core.git] / CRM / Member / ActionMapping.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12
13 /**
14 * Class CRM_Member_ActionMapping
15 *
16 * This defines the scheduled-reminder functionality for CiviMember
17 * memberships. It allows one to target reminders based on join date
18 * or end date, with additional filtering based on membership-type.
19 */
20 class CRM_Member_ActionMapping extends \Civi\ActionSchedule\Mapping {
21
22 /**
23 * The value for civicrm_action_schedule.mapping_id which identifies the
24 * "Membership Type" mapping.
25 *
26 * Note: This value is chosen to match legacy DB IDs.
27 */
28 const MEMBERSHIP_TYPE_MAPPING_ID = 4;
29
30 /**
31 * Register CiviMember-related action mappings.
32 *
33 * @param \Civi\ActionSchedule\Event\MappingRegisterEvent $registrations
34 */
35 public static function onRegisterActionMappings(\Civi\ActionSchedule\Event\MappingRegisterEvent $registrations) {
36 $registrations->register(CRM_Member_ActionMapping::create([
37 'id' => CRM_Member_ActionMapping::MEMBERSHIP_TYPE_MAPPING_ID,
38 'entity' => 'civicrm_membership',
39 'entity_label' => ts('Membership'),
40 'entity_value' => 'civicrm_membership_type',
41 'entity_value_label' => ts('Membership Type'),
42 'entity_status' => 'auto_renew_options',
43 'entity_status_label' => ts('Auto Renew Options'),
44 ]));
45 }
46
47 /**
48 * Get a list of available date fields.
49 *
50 * @return array
51 * Array(string $fieldName => string $fieldLabel).
52 */
53 public function getDateFields() {
54 return [
55 'join_date' => ts('Membership Join Date'),
56 'start_date' => ts('Membership Start Date'),
57 'end_date' => ts('Membership End Date'),
58 ];
59 }
60
61 /**
62 * Generate a query to locate recipients who match the given
63 * schedule.
64 *
65 * @param \CRM_Core_DAO_ActionSchedule $schedule
66 * The schedule as configured by the administrator.
67 * @param string $phase
68 * See, e.g., RecipientBuilder::PHASE_RELATION_FIRST.
69 * @param array $defaultParams
70 *
71 * @return \CRM_Utils_SQL_Select
72 * @see RecipientBuilder
73 */
74 public function createQuery($schedule, $phase, $defaultParams) {
75 $selectedValues = (array) \CRM_Utils_Array::explodePadded($schedule->entity_value);
76 $selectedStatuses = (array) \CRM_Utils_Array::explodePadded($schedule->entity_status);
77
78 $query = \CRM_Utils_SQL_Select::from("{$this->entity} e")->param($defaultParams);
79 $query['casAddlCheckFrom'] = 'civicrm_membership e';
80 $query['casContactIdField'] = 'e.contact_id';
81 $query['casEntityIdField'] = 'e.id';
82 $query['casContactTableAlias'] = NULL;
83
84 // Leaving this in case of legacy databases
85 $query['casDateField'] = str_replace('membership_', 'e.', $schedule->start_action_date);
86
87 // Options currently are just 'join_date', 'start_date', and 'end_date':
88 // they need an alias
89 if (strpos($query['casDateField'], 'e.') !== 0) {
90 $query['casDateField'] = 'e.' . $query['casDateField'];
91 }
92
93 // FIXME: Numbers should be constants.
94 if (in_array(2, $selectedStatuses)) {
95 //auto-renew memberships
96 $query->where("e.contribution_recur_id IS NOT NULL");
97 }
98 elseif (in_array(1, $selectedStatuses)) {
99 $query->where("e.contribution_recur_id IS NULL");
100 }
101
102 if (!empty($selectedValues)) {
103 $query->where("e.membership_type_id IN (@memberTypeValues)")
104 ->param('memberTypeValues', $selectedValues);
105 }
106 else {
107 // FIXME: The membership type is never null, so nobody will ever get a
108 // reminder if no membership types are selected. Either this should be a
109 // validation on the reminder form or all types should get a reminder if
110 // no types are selected.
111 $query->where("e.membership_type_id IS NULL");
112 }
113
114 // FIXME: This makes a lot of sense for renewal reminders, but a user
115 // scheduling another kind of reminder might not expect members to be
116 // excluded if they have status overrides. Ideally there would be some kind
117 // of setting per reminder.
118 $query->where("( e.is_override IS NULL OR e.is_override = 0 )");
119
120 // FIXME: Similarly to overrides, excluding contacts who can't edit the
121 // primary member makes sense in the context of renewals (see CRM-11342) but
122 // would be a surprise for other use cases.
123 $query->merge($this->prepareMembershipPermissionsFilter());
124
125 // FIXME: A lot of undocumented stuff happens with regard to
126 // `is_current_member`, and this is no exception. Ideally there would be an
127 // opportunity to pick statuses when setting up the scheduled reminder
128 // rather than making the assumptions here.
129 $query->where("e.status_id IN (#memberStatus)")
130 ->param('memberStatus', \CRM_Member_PseudoConstant::membershipStatus(NULL, "(is_current_member = 1 OR name = 'Expired')", 'id'));
131
132 return $query;
133 }
134
135 /**
136 * Filter out the memberships that are inherited from a contact that the
137 * recipient cannot edit.
138 *
139 * @return CRM_Utils_SQL_Select
140 */
141 protected function prepareMembershipPermissionsFilter() {
142 $joins = [
143 'cm' => 'LEFT JOIN civicrm_membership cm ON cm.id = e.owner_membership_id',
144 'rela' => 'LEFT JOIN civicrm_relationship rela ON rela.contact_id_a = e.contact_id AND rela.contact_id_b = cm.contact_id AND rela.is_permission_a_b = #editPerm',
145 'relb' => 'LEFT JOIN civicrm_relationship relb ON relb.contact_id_a = cm.contact_id AND relb.contact_id_b = e.contact_id AND relb.is_permission_b_a = #editPerm',
146 ];
147
148 return \CRM_Utils_SQL_Select::fragment()
149 ->join(NULL, $joins)
150 ->param('#editPerm', CRM_Contact_BAO_Relationship::EDIT)
151 ->where('!( e.owner_membership_id IS NOT NULL AND rela.id IS NULL and relb.id IS NULL )');
152 }
153
154 /**
155 * Determine whether a schedule based on this mapping should
156 * reset the reminder state if the trigger date changes.
157 *
158 * @return bool
159 *
160 * @param \CRM_Core_DAO_ActionSchedule $schedule
161 */
162 public function resetOnTriggerDateChange($schedule) {
163 if ($schedule->absolute_date !== NULL) {
164 return FALSE;
165 }
166 else {
167 return TRUE;
168 }
169 }
170
171 /**
172 * Determine whether a schedule based on this mapping should
173 * send to additional contacts.
174 */
175 public function sendToAdditional($entityId): bool {
176 return TRUE;
177 }
178
179 }