isEnabled()) { try { $mailingList->delete(); } catch (MailinglistException $e) { $errorCode = $e->getCode(); if ($errorCode !== MailinglistException::ERROR_CODE_DELETE_EMAIL_ADDRESS_FAILED) { \Civi::log(E::LONG_NAME)->error($e->getMessage()); } CRM_Core_Session::setStatus( E::ts('Failed to delete mlmmj mailing list: %1', [1 => $e->getMessage()]), E::ts('Deletion failed'), 'alert', ); } } // Handle group and event mailing list updates which affect the mlmmj mailing list if ($_REQUEST['action'] === 'update' && $mailingList->isEnabled()) { $customFields = $mailingList::translateCustomFields($fields); // If enabled changes from TRUE to FALSE, delete the mlmmj mailing list if ($customFields['Enable_mailing_list']['value'] === "0") { try { $mailingList->delete(); } catch (MailinglistException $e) { $errorCode = $e->getCode(); if ($errorCode !== MailinglistException::ERROR_CODE_DELETE_EMAIL_ADDRESS_FAILED) { \Civi::log(E::LONG_NAME)->error($e->getMessage()); } CRM_Core_Session::setStatus( E::ts('Failed to delete mlmmj mailing list: %1', [1 => $e->getMessage()]), E::ts('Deletion failed'), 'alert', ); } } // If email address has changed, delete and mark group for creation elseif ($customFields['E_mail_address']['value'] !== $mailingList->getEmailAddress()) { try { $mailingList->delete(); } catch (MailinglistException $e) { $errorCode = $e->getCode(); if ($errorCode !== MailinglistException::ERROR_CODE_DELETE_EMAIL_ADDRESS_FAILED) { \Civi::log(E::LONG_NAME)->error($e->getMessage()); } CRM_Core_Session::setStatus( E::ts('Failed to delete mlmmj mailing list: %1', [1 => $e->getMessage()]), E::ts('Deletion failed'), 'alert', ); } // Queue mailinglist for synchronization with mlmmj if ($mailingList::class === GroupMailingList::class) { $queue = \Civi::queue('propeace-mailinglist-group-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeInstance'], // arguments [$mailingList->getId(), $mailingList::class], // title "Sync Group ID '{$mailingList->getId()}'" )); } elseif ($mailingList::class === EventMailingList::class) { $queue = \Civi::queue('propeace-mailinglist-event-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeInstance'], // arguments [$mailingList->getId(), $mailingList::class], // title "Sync Event ID '{$mailingList->getId()}'" )); } } } // Validate custom fields $mailingList::validateCustomFields($fields, $form, $errors); } // Check permission to alter group membership via form if ($formName === 'CRM_Contact_Form_GroupContact' || $formName === 'CRM_Contact_Form_Task_AddToGroup') { $mailingList = new GroupMailingList($fields['group_id']); _mailinglistsync_check_group_membership_permissions($mailingList, $errors); } elseif ($formName === 'CRM_Event_Form_Participant') { $mailingList = new EventMailingList($fields['event_id']); _mailinglistsync_check_event_membership_permissions($mailingList, $errors); } } /** * Implements hook_civicrm_post() to check on permissions to alter mailing list * groups and sync group with mlmmj. * * @throws \Civi\API\Exception\UnauthorizedException * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException * @throws \CRM_Core_Exception */ function mailinglistsync_civicrm_post(string $op, string $objectName, int $objectId, &$objectRef) { if ($op === 'delete' || $op === 'edit' || $op === 'create') { // Check on groups mailing list // Note: I wonder why $objectId refers to the group instead of the group contact here. if ($objectName === 'GroupContact') { $mailingList = new GroupMailingList($objectRef->group_id ?? $objectId); // Check permission to alter group membership if ($mailingList->isEnabled() && !CRM_Core_Permission::check('manage_group_mailinglists')) { CRM_Core_Session::setStatus( E::ts('You do not have permission to manage memberships of mailing list groups.'), E::ts('Permission Denied'), 'alert', ); throw new MailinglistException( 'Permission to manage group membership denied', MailinglistException::ERROR_CODE_PERMISSION_DENIED ); } // Check permission to alter AD group membership if ($mailingList->isADGroup() && !CRM_Core_Permission::check('manage_ad_mailinglists')) { CRM_Core_Session::setStatus( E::ts('You do not have permission to manage memberships of AD mailing list groups.'), E::ts('Permission Denied'), 'alert', ); throw new MailinglistException( 'Permission to manage AD group membership denied', MailinglistException::ERROR_CODE_PERMISSION_DENIED ); } } // Check on event mailing lists elseif ($objectName === 'Participant') { $eventId = $objectRef->event_id ?? Event::get() ->addSelect('event_id') ->addWhere('id', '=', $objectId) ->execute() ->first()['event_id']; $mailingList = new GroupMailingList($eventId); // Check permission to alter event mailing list if ($mailingList->isEnabled() && !CRM_Core_Permission::check('manage_event_mailinglists')) { CRM_Core_Session::setStatus( E::ts('You do not have permission to manage event mailing lists.'), E::ts('Permission Denied'), 'alert', ); throw new MailinglistException( 'Permission to manage event mailing list denied', MailinglistException::ERROR_CODE_PERMISSION_DENIED ); } } // If this is an e-mail address of a location type used for mailing lists if ($objectName === 'Email' && in_array((int) $objectRef->location_type_id, array_keys(getLocationTypes()))) { // Ensure that only one e-mail address of this location type is set for this contact $result = Email::get() ->addSelect('contact_id', 'contact.display_name') ->addJoin('LocationType AS location_type', 'INNER', ['location_type_id', '=', 'location_type.id']) ->addWhere('location_type_id', '=', $objectRef->location_type_id) ->addWhere('contact_id', '=', $objectRef->contact_id) ->addWhere('id', '!=', $objectRef->id) ->execute() ->first(); if (!empty($result) && $op != 'delete') { throw new MailinglistException( "An e-mail address of a mailing list type is already used for this contact", MailinglistException::ERROR_CODE_UPDATE_EMAIL_ADDRESS_FAILED ); } // Ensure that this email address is only used for one contact $results = Email::get() ->addSelect('contact.id', 'contact.display_name') ->addJoin('Contact AS contact', 'LEFT', [ 'contact_id', '=', 'contact.id', ]) ->addWhere('contact_id', '!=', $objectRef->contact_id) ->addWhere('email', '=', $objectRef->email) ->addWhere('location_type_id', '=', $objectRef->location_type_id) ->execute() ->getArrayCopy(); if (!empty($results) && $op != 'delete') { // Delete e-mail address if it is not allowed // TODO: This is a workaround. We should prevent the creation of the e-mail address in the first place. _mailinglistsync_delete_email_address((int) $objectRef->id); $contacts = []; foreach ($results as $result) { $contactId = $result['contact.id']; $contactName = $result['contact.display_name']; $contactUrl = CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $contactId, TRUE); $contacts[] = "$contactName (id: $contactId)"; } $message = count($contacts) > 1 ? E::ts( "This e-mail address is already used for other contacts: %1", [1 => implode(', ', $contacts)] ) : E::ts( "This e-mail address is already used for another contact: %1", [1 => array_pop($contacts)] ); CRM_Core_Session::setStatus( E::ts($message), E::ts('E-Mail Address Already Used'), 'error', ['unique' => TRUE] ); } } } } /** * Synchronize group and event mailing lists whenever they change by adding * them to the queue which will be processed by the cron job. * * @throws \Civi\API\Exception\UnauthorizedException * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException * @throws \CRM_Core_Exception */ function mailinglistsync_civicrm_postCommit(string $op, string $objectName, int $objectId, &$objectRef) { // Sync groups mailing list // Note: I wonder why $objectId refers to the group instead of the group contact here. if ($objectName === 'GroupContact') { $mailingList = new GroupMailingList($objectRef->group_id ?? $objectId); if ($mailingList->isEnabled()) { // Queue group for synchronization with mlmmj $queue = \Civi::queue('propeace-mailinglist-group-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeInstance'], // arguments [$mailingList->getId(), $mailingList::class], // title "Sync Group ID '{$mailingList->getId()}'" )); } } elseif ($objectName === 'Group') { $mailingList = new GroupMailingList($objectId); if ($mailingList->isEnabled()) { // Queue group for synchronization with mlmmj $queue = \Civi::queue('propeace-mailinglist-group-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeInstance'], // arguments [$mailingList->getId(), $mailingList::class], // title "Sync Group ID '{$mailingList->getId()}'" )); } } // Sync event mailing lists elseif ($objectName === 'Participant') { $eventId = $objectRef->event_id ?? Participant::get() ->addSelect('event_id') ->addWhere('id', '=', $objectId) ->execute() ->first()['event_id']; $mailingList = new EventMailingList($eventId); if ($mailingList->isEnabled()) { // Queue event for synchronization with mlmmj $queue = \Civi::queue('propeace-mailinglist-event-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeInstance'], // arguments [$mailingList->getId(), $mailingList::class], // title "Sync Event ID '{$mailingList->getId()}'" )); } } elseif ($objectName === 'Event') { $mailingList = new EventMailingList($objectId); if ($mailingList->isEnabled()) { // Queue event for synchronization with mlmmj $queue = \Civi::queue('propeace-mailinglist-event-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeInstance'], // arguments [$mailingList->getId(), $mailingList::class], // title "Sync Event ID '{$mailingList->getId()}'" )); } } if ($objectName === 'GroupContact') { $mailingList = new GroupMailingList($objectRef->group_id ?? $objectId); if ($mailingList->isEnabled()) { // Queue group for synchronization with mlmmj $queue = \Civi::queue('propeace-mailinglist-group-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeInstance'], // arguments [$mailingList->getId(), $mailingList::class], // title "Sync Group ID '{$mailingList->getId()}'" )); } } // Sync e-mail addresses elseif ($objectName === 'Email') { // If an email address update comes from an api call, the // objectRef->location_type_id is not set. So we have to get it from the // database. if (empty($objectRef->location_type_id)) { $locationTypeId = \Civi\Api4\Email::get() ->addSelect('location_type_id') ->addWhere('id', '=', $objectRef->id) ->addWhere('location_type_id', 'IN', array_keys(getLocationTypes())) ->execute() ->first()['location_type_id']; } if ( !empty($locationTypeId) || in_array((int) $objectRef->location_type_id, array_keys(getLocationTypes())) ) { // Queue email address for synchronization with mlmmj $queue = \Civi::queue('propeace-mailinglist-email-queue', [ 'type' => 'SqlParallel', 'is_autorun' => FALSE, 'reset' => FALSE, 'error' => 'drop', ]); $queue->createItem(new CRM_Queue_Task( // callback ['Civi\Mailinglistsync\QueueHelper', 'storeEmail'], // arguments [$objectRef->email], // title "Sync E-Mail '$objectRef->email'" )); } } } /** * Implementation of hook_civicrm_permission * * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_permission/ */ function mailinglistsync_civicrm_permission(&$permissions) { $permissions['manage_ad_mailinglists'] = [ 'label' => E::SHORT_NAME . ': ' . E::ts('Manage AD Mailing Lists'), 'description' => E::ts('Manage mailing list groups originating from Active Directory'), 'disabled' => FALSE, ]; $permissions['manage_ad_address_type'] = [ 'label' => E::SHORT_NAME . ': ' . E::ts('Manage AD Addresses'), 'description' => E::ts('Manage addresses reserved for data originating from Active Directory'), 'disabled' => FALSE, ]; $permissions['manage_group_mailinglists'] = [ 'label' => E::SHORT_NAME . ': ' . E::ts('Manage Group Mailing Lists'), 'description' => E::ts('Manage groups used as mailing lists'), 'disabled' => FALSE, ]; $permissions['manage_event_mailinglists'] = [ 'label' => E::SHORT_NAME . ': ' . E::ts('Manage Event Mailing Lists'), 'description' => E::ts('Manage events used as mailing lists'), 'disabled' => FALSE, ]; } /** * Check permissions to alter group membership. * * @param \Civi\Mailinglistsync\GroupMailingList $mailingList * @param $errors * * @return void */ function _mailinglistsync_check_group_membership_permissions(GroupMailingList $mailingList, &$errors): void { if ($mailingList->isEnabled() && !CRM_Core_Permission::check('manage_group_mailinglists')) { $errors['group_id'] = E::ts('You do not have permission to manage group membership.'); } if ($mailingList->isADGroup() && !CRM_Core_Permission::check('manage_ad_mailinglists')) { $errors['group_id'] = E::ts('You do not have permission to manage AD group membership.'); } } /** * Check permissions to alter event membership. * * @param \Civi\Mailinglistsync\EventMailingList $mailingList * @param $errors * * @return void */ function _mailinglistsync_check_event_membership_permissions(EventMailingList $mailingList, &$errors): void { if ($mailingList->isEnabled() && !CRM_Core_Permission::check('manage_event_mailinglists')) { $errors['event_id'] = E::ts('You do not have permission to manage event membership.'); } } /** * Unfortunately, we cannot prevent the creation of a new e-mail address within * the post hook, so we have to delete it again if it is not allowed. * * @param int $emailId * * @return void * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ function _mailinglistsync_delete_email_address(int $emailId): void { try { $email = Email::delete() ->addWhere('id', '=', $emailId) ->execute(); } catch (\Exception $e) { throw new MailinglistException( "Failed to delete e-mail address: {$e}", MailinglistException::ERROR_CODE_DELETE_EMAIL_ADDRESS_FAILED ); } }