First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
namespace Civi\ActionSchedule\Event;
|
||||
|
||||
use Civi\ActionSchedule\MappingInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Class MailingQueryEvent
|
||||
* @package Civi\ActionSchedule\Event
|
||||
*
|
||||
* This event allows listeners to modify the query which generates mailing data.
|
||||
* If you want to fetch extra mail-merge data as part of an initial query, then
|
||||
* modify the mailing-query to add extra JOINs/SELECTs.
|
||||
*
|
||||
* The basic mailing query looks a bit like this (depending on configuration):
|
||||
*
|
||||
* @code
|
||||
* SELECT reminder.id AS reminderID, reminder.contact_id as contactID, ...
|
||||
* FROM `civicrm_action_log` reminder
|
||||
* ... JOIN `target_entity` e ON e.id = reminder.entity_id ...
|
||||
* WHERE reminder.action_schedule_id = #casActionScheduleId
|
||||
* @endcode
|
||||
*
|
||||
* Listeners may modify the query. For example, suppose we want to load
|
||||
* additional fields from the related 'foo' entity:
|
||||
*
|
||||
* @code
|
||||
* $event->query->join('foo', '!casMailingJoinType civicrm_foo foo ON foo.myentity_id = e.id')
|
||||
* ->select('foo.bar_value AS bar');
|
||||
* @endcode
|
||||
*
|
||||
* There are several parameters pre-set for use in queries:
|
||||
* - 'casActionScheduleId'
|
||||
* - 'casEntityJoinExpr' - eg 'e.id = reminder.entity_id'
|
||||
* - 'casMailingJoinType' - eg 'LEFT JOIN' or 'INNER JOIN' (depending on configuration)
|
||||
* - 'casMappingId'
|
||||
* - 'casMappingEntity'
|
||||
*
|
||||
* (Note: When adding more JOINs, it seems typical to use !casMailingJoinType, although
|
||||
* some hard-code a LEFT JOIN. Don't have an explanation for why.)
|
||||
*/
|
||||
class MailingQueryEvent extends Event {
|
||||
|
||||
/**
|
||||
* The schedule record which produced this mailing.
|
||||
*
|
||||
* @var \CRM_Core_DAO_ActionSchedule
|
||||
*/
|
||||
public $actionSchedule;
|
||||
|
||||
/**
|
||||
* The mapping record which produced this mailing.
|
||||
*
|
||||
* @var MappingInterface
|
||||
*/
|
||||
public $mapping;
|
||||
|
||||
/**
|
||||
* The alterable query. For details, see the class description.
|
||||
* @var \CRM_Utils_SQL_Select
|
||||
* @see MailingQueryEvent
|
||||
*/
|
||||
public $query;
|
||||
|
||||
/**
|
||||
* @param \CRM_Core_DAO_ActionSchedule $actionSchedule
|
||||
* @param MappingInterface $mapping
|
||||
* @param \CRM_Utils_SQL_Select $query
|
||||
*/
|
||||
public function __construct($actionSchedule, $mapping, $query) {
|
||||
$this->actionSchedule = $actionSchedule;
|
||||
$this->mapping = $mapping;
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
namespace Civi\ActionSchedule\Event;
|
||||
|
||||
use Civi\ActionSchedule\MappingInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Class ActionScheduleEvent
|
||||
* @package Civi\ActionSchedule\Event
|
||||
*
|
||||
* Register any available mappings.
|
||||
*/
|
||||
class MappingRegisterEvent extends Event {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* Array(scalar $id => Mapping $mapping).
|
||||
*/
|
||||
protected $mappings = array();
|
||||
|
||||
/**
|
||||
* Register a new mapping.
|
||||
*
|
||||
* @param MappingInterface $mapping
|
||||
* The new mapping.
|
||||
* @return MappingRegisterEvent
|
||||
*/
|
||||
public function register(MappingInterface $mapping) {
|
||||
$this->mappings[$mapping->getId()] = $mapping;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* Array(scalar $id => MappingInterface $mapping).
|
||||
*/
|
||||
public function getMappings() {
|
||||
ksort($this->mappings);
|
||||
return $this->mappings;
|
||||
}
|
||||
|
||||
}
|
19
sites/all/modules/civicrm/Civi/ActionSchedule/Events.php
Normal file
19
sites/all/modules/civicrm/Civi/ActionSchedule/Events.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace Civi\ActionSchedule;
|
||||
|
||||
class Events {
|
||||
|
||||
/**
|
||||
* Register any available mappings.
|
||||
*
|
||||
* @see EntityListEvent
|
||||
*/
|
||||
const MAPPINGS = 'civi.actionSchedule.getMappings';
|
||||
|
||||
/**
|
||||
* Prepare the pre-mailing query. This query loads details about
|
||||
* the contact/entity so that they're available for mail-merge.
|
||||
*/
|
||||
const MAILING_QUERY = 'civi.actionSchedule.prepareMailingQuery';
|
||||
|
||||
}
|
344
sites/all/modules/civicrm/Civi/ActionSchedule/Mapping.php
Normal file
344
sites/all/modules/civicrm/Civi/ActionSchedule/Mapping.php
Normal file
|
@ -0,0 +1,344 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| This file is a part of CiviCRM. |
|
||||
| |
|
||||
| CiviCRM is free software; you can copy, modify, and distribute it |
|
||||
| under the terms of the GNU Affero General Public License |
|
||||
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
|
||||
| |
|
||||
| CiviCRM is distributed in the hope that it will be useful, but |
|
||||
| WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
||||
| See the GNU Affero General Public License for more details. |
|
||||
| |
|
||||
| You should have received a copy of the GNU Affero General Public |
|
||||
| License and the CiviCRM Licensing Exception along |
|
||||
| with this program; if not, contact CiviCRM LLC |
|
||||
| at info[AT]civicrm[DOT]org. If you have questions about the |
|
||||
| GNU Affero General Public License or the licensing of CiviCRM, |
|
||||
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
|
||||
+--------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
namespace Civi\ActionSchedule;
|
||||
|
||||
/**
|
||||
* Class Mapping
|
||||
* @package Civi\ActionSchedule
|
||||
*
|
||||
* This is the initial implementation of MappingInterface; it was
|
||||
* constructed by cutting out swaths from CRM_Core_BAO_ActionSchedule.
|
||||
* New implementers should consider implementing MappingInterface on
|
||||
* their own.
|
||||
*
|
||||
* Background: The original designers of ActionMappings intended that
|
||||
* one could create and configure new mappings through the database.
|
||||
* To, e.g., define the filtering options for CiviEvent, you
|
||||
* would insert a record in "civicrm_action_mapping" with fields like
|
||||
* "entity" (a table name, eg "civicrm_event"), "entity_value" (an
|
||||
* option-group name, eg "event_types").
|
||||
*
|
||||
* Unfortunately, the metadata in "civicrm_action_mapping" proved
|
||||
* inadequate and was not updated to cope. Instead, a number
|
||||
* of work-arounds for specific entities were hard-coded into
|
||||
* the core action-scheduling code. Ultimately, to add a new
|
||||
* mapping type, one needed to run around and patch a dozen
|
||||
* places.
|
||||
*
|
||||
* The new MappingInterface makes no pretense of database-driven
|
||||
* configuration. The dozen places have been consolidated and
|
||||
* replaced with functions in MappingInterface.
|
||||
*
|
||||
* This "Mapping" implementation is a refactoring of the old
|
||||
* hard-coded bits. Internally, it uses the concepts from
|
||||
* "civicrm_action_mapping". The resulting code is more
|
||||
* convoluted than a clean implementation of MappingInterface, but
|
||||
* it strictly matches the old behavior (based on logging/comparing
|
||||
* the queries produced through ActionScheduleTest).
|
||||
*/
|
||||
abstract class Mapping implements MappingInterface {
|
||||
|
||||
private static $fields = array(
|
||||
'id',
|
||||
'entity',
|
||||
'entity_label',
|
||||
'entity_value',
|
||||
'entity_value_label',
|
||||
'entity_status',
|
||||
'entity_status_label',
|
||||
'entity_date_start',
|
||||
'entity_date_end',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create mapping.
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function create($params) {
|
||||
return new static($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct($params) {
|
||||
foreach (self::$fields as $field) {
|
||||
if (isset($params[$field])) {
|
||||
$this->{$field} = $params[$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The basic entity to query (table name).
|
||||
*
|
||||
* @var string
|
||||
* Ex: 'civicrm_activity', 'civicrm_event'.
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* The basic entity to query (label).
|
||||
*
|
||||
* @var
|
||||
* Ex: 'Activity', 'Event'
|
||||
*/
|
||||
private $entity_label;
|
||||
|
||||
/**
|
||||
* Level 1 filter -- the field/option-list to filter on.
|
||||
*
|
||||
* @var string
|
||||
* Ex: 'activity_type', 'civicrm_event', 'event_template'.
|
||||
*/
|
||||
private $entity_value;
|
||||
|
||||
/**
|
||||
* Level 1 filter -- The field label.
|
||||
*
|
||||
* @var string
|
||||
* Ex: 'Activity Type', 'Event Name', 'Event Template'.
|
||||
*/
|
||||
private $entity_value_label;
|
||||
|
||||
/**
|
||||
* Level 2 filter -- the field/option-list to filter on.
|
||||
* @var string
|
||||
* Ex: 'activity_status, 'civicrm_participant_status_type', 'auto_renew_options'.
|
||||
*/
|
||||
private $entity_status;
|
||||
|
||||
/**
|
||||
* Level 2 filter -- the field label.
|
||||
* @var string
|
||||
* Ex: 'Activity Status', 'Participant Status', 'Auto Rewnewal Options'.
|
||||
*/
|
||||
private $entity_status_label;
|
||||
|
||||
/**
|
||||
* Date filter -- the field name.
|
||||
* @var string|NULL
|
||||
* Ex: 'event_start_date'
|
||||
*/
|
||||
private $entity_date_start;
|
||||
|
||||
/**
|
||||
* Date filter -- the field name.
|
||||
* @var string|NULL
|
||||
* Ex: 'event_end_date'.
|
||||
*/
|
||||
private $entity_date_end;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEntity() {
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a printable label for this mapping type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->entity_label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a printable label to use a header on the 'value' filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueHeader() {
|
||||
return $this->entity_value_label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a printable label to use a header on the 'status' filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatusHeader() {
|
||||
return $this->entity_status_label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of value options.
|
||||
*
|
||||
* @return array
|
||||
* Array(string $value => string $label).
|
||||
* Ex: array(123 => 'Phone Call', 456 => 'Meeting').
|
||||
*/
|
||||
public function getValueLabels() {
|
||||
return self::getValueLabelMap($this->entity_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of status options.
|
||||
*
|
||||
* @param string|int $value
|
||||
* The list of status options may be contingent upon the selected filter value.
|
||||
* This is the selected filter value.
|
||||
* @return array
|
||||
* Array(string $value => string $label).
|
||||
* Ex: Array(123 => 'Completed', 456 => 'Scheduled').
|
||||
*/
|
||||
public function getStatusLabels($value) {
|
||||
if ($this->entity_status === 'auto_renew_options') {
|
||||
if ($value && \CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $value, 'auto_renew')) {
|
||||
return \CRM_Core_OptionGroup::values('auto_renew_options');
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
return self::getValueLabelMap($this->entity_status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of available date fields.
|
||||
*
|
||||
* @return array
|
||||
* Array(string $fieldName => string $fieldLabel).
|
||||
*/
|
||||
public function getDateFields() {
|
||||
$dateFieldLabels = array();
|
||||
if (!empty($this->entity_date_start)) {
|
||||
$dateFieldLabels[$this->entity_date_start] = ucwords(str_replace('_', ' ', $this->entity_date_start));
|
||||
}
|
||||
if (!empty($this->entity_date_end)) {
|
||||
$dateFieldLabels[$this->entity_date_end] = ucwords(str_replace('_', ' ', $this->entity_date_end));
|
||||
}
|
||||
return $dateFieldLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of recipient types.
|
||||
*
|
||||
* Note: A single schedule may filter on *zero* or *one* recipient types.
|
||||
* When an admin chooses a value, it's stored in $schedule->recipient.
|
||||
*
|
||||
* @return array
|
||||
* array(string $value => string $label).
|
||||
* Ex: array('assignee' => 'Activity Assignee').
|
||||
*/
|
||||
public function getRecipientTypes() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of recipients which match the given type.
|
||||
*
|
||||
* Note: A single schedule may filter on *multiple* recipients.
|
||||
* When an admin chooses value(s), it's stored in $schedule->recipient_listing.
|
||||
*
|
||||
* @param string $recipientType
|
||||
* Ex: 'participant_role'.
|
||||
* @return array
|
||||
* Array(mixed $name => string $label).
|
||||
* Ex: array(1 => 'Attendee', 2 => 'Volunteer').
|
||||
* @see getRecipientTypes
|
||||
*/
|
||||
public function getRecipientListing($recipientType) {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected static function getValueLabelMap($name) {
|
||||
static $valueLabelMap = NULL;
|
||||
if ($valueLabelMap === NULL) {
|
||||
// CRM-20510: Include CiviCampaign activity types along with CiviCase IF component is enabled
|
||||
$valueLabelMap['activity_type'] = \CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE);
|
||||
asort($valueLabelMap['activity_type']);
|
||||
|
||||
$valueLabelMap['activity_status'] = \CRM_Core_PseudoConstant::activityStatus();
|
||||
$valueLabelMap['event_type'] = \CRM_Event_PseudoConstant::eventType();
|
||||
$valueLabelMap['civicrm_event'] = \CRM_Event_PseudoConstant::event(NULL, FALSE, "( is_template IS NULL OR is_template != 1 )");
|
||||
$valueLabelMap['civicrm_participant_status_type'] = \CRM_Event_PseudoConstant::participantStatus(NULL, NULL, 'label');
|
||||
$valueLabelMap['event_template'] = \CRM_Event_PseudoConstant::eventTemplates();
|
||||
$valueLabelMap['auto_renew_options'] = \CRM_Core_OptionGroup::values('auto_renew_options');
|
||||
$valueLabelMap['contact_date_reminder_options'] = \CRM_Core_OptionGroup::values('contact_date_reminder_options');
|
||||
$valueLabelMap['civicrm_membership_type'] = \CRM_Member_PseudoConstant::membershipType();
|
||||
|
||||
$allCustomFields = \CRM_Core_BAO_CustomField::getFields('');
|
||||
$dateFields = array(
|
||||
'birth_date' => ts('Birth Date'),
|
||||
'created_date' => ts('Created Date'),
|
||||
'modified_date' => ts('Modified Date'),
|
||||
);
|
||||
foreach ($allCustomFields as $fieldID => $field) {
|
||||
if ($field['data_type'] == 'Date') {
|
||||
$dateFields["custom_$fieldID"] = $field['label'];
|
||||
}
|
||||
}
|
||||
$valueLabelMap['civicrm_contact'] = $dateFields;
|
||||
}
|
||||
|
||||
return $valueLabelMap[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a schedule based on this mapping is sufficiently
|
||||
* complete.
|
||||
*
|
||||
* @param \CRM_Core_DAO_ActionSchedule $schedule
|
||||
* @return array
|
||||
* Array (string $code => string $message).
|
||||
* List of error messages.
|
||||
*/
|
||||
public function validateSchedule($schedule) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a query to locate contacts who match the given
|
||||
* schedule.
|
||||
*
|
||||
* @param \CRM_Core_DAO_ActionSchedule $schedule
|
||||
* @param string $phase
|
||||
* See, e.g., RecipientBuilder::PHASE_RELATION_FIRST.
|
||||
* @param array $defaultParams
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
*/
|
||||
public abstract function createQuery($schedule, $phase, $defaultParams);
|
||||
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| This file is a part of CiviCRM. |
|
||||
| |
|
||||
| CiviCRM is free software; you can copy, modify, and distribute it |
|
||||
| under the terms of the GNU Affero General Public License |
|
||||
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
|
||||
| |
|
||||
| CiviCRM is distributed in the hope that it will be useful, but |
|
||||
| WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
||||
| See the GNU Affero General Public License for more details. |
|
||||
| |
|
||||
| You should have received a copy of the GNU Affero General Public |
|
||||
| License and the CiviCRM Licensing Exception along |
|
||||
| with this program; if not, contact CiviCRM LLC |
|
||||
| at info[AT]civicrm[DOT]org. If you have questions about the |
|
||||
| GNU Affero General Public License or the licensing of CiviCRM, |
|
||||
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
|
||||
+--------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
namespace Civi\ActionSchedule;
|
||||
|
||||
/**
|
||||
* Interface MappingInterface
|
||||
* @package Civi\ActionSchedule
|
||||
*/
|
||||
interface MappingInterface {
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEntity();
|
||||
|
||||
/**
|
||||
* Get a printable label for this mapping type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel();
|
||||
|
||||
/**
|
||||
* Get a printable label to use as the header on the 'value' filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValueHeader();
|
||||
|
||||
/**
|
||||
* Get a printable label to use as the header on the 'status' filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatusHeader();
|
||||
|
||||
/**
|
||||
* Get a list of value options.
|
||||
*
|
||||
* @return array
|
||||
* Array(string $value => string $label).
|
||||
* Ex: array(123 => 'Phone Call', 456 => 'Meeting').
|
||||
*/
|
||||
public function getValueLabels();
|
||||
|
||||
/**
|
||||
* Get a list of status options.
|
||||
*
|
||||
* @param string|int $value
|
||||
* The list of status options may be contingent upon the selected filter value.
|
||||
* This is the selected filter value.
|
||||
* @return array
|
||||
* Array(string $value => string $label).
|
||||
* Ex: Array(123 => 'Completed', 456 => 'Scheduled').
|
||||
*/
|
||||
public function getStatusLabels($value);
|
||||
|
||||
/**
|
||||
* Get a list of available date fields.
|
||||
*
|
||||
* @return array
|
||||
* Array(string $fieldName => string $fieldLabel).
|
||||
*/
|
||||
public function getDateFields();
|
||||
|
||||
/**
|
||||
* Get a list of recipient types.
|
||||
*
|
||||
* Note: A single schedule may filter on *zero* or *one* recipient types.
|
||||
* When an admin chooses a value, it's stored in $schedule->recipient.
|
||||
*
|
||||
* @return array
|
||||
* array(string $value => string $label).
|
||||
* Ex: array('assignee' => 'Activity Assignee').
|
||||
*/
|
||||
public function getRecipientTypes();
|
||||
|
||||
/**
|
||||
* Get a list of recipients which match the given type.
|
||||
*
|
||||
* Note: A single schedule may filter on *multiple* recipients.
|
||||
* When an admin chooses value(s), it's stored in $schedule->recipient_listing.
|
||||
*
|
||||
* @param string $recipientType
|
||||
* Ex: 'participant_role'.
|
||||
* @return array
|
||||
* Array(mixed $name => string $label).
|
||||
* Ex: array(1 => 'Attendee', 2 => 'Volunteer').
|
||||
* @see getRecipientTypes
|
||||
*/
|
||||
public function getRecipientListing($recipientType);
|
||||
|
||||
/**
|
||||
* Determine whether a schedule based on this mapping is sufficiently
|
||||
* complete.
|
||||
*
|
||||
* @param \CRM_Core_DAO_ActionSchedule $schedule
|
||||
* @return array
|
||||
* Array (string $code => string $message).
|
||||
* List of error messages.
|
||||
*/
|
||||
public function validateSchedule($schedule);
|
||||
|
||||
/**
|
||||
* Generate a query to locate contacts who match the given
|
||||
* schedule.
|
||||
*
|
||||
* @param \CRM_Core_DAO_ActionSchedule $schedule
|
||||
* @param string $phase
|
||||
* See, e.g., RecipientBuilder::PHASE_RELATION_FIRST.
|
||||
* @param array $defaultParams
|
||||
* Default parameters that should be included with query.
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
* @see RecipientBuilder
|
||||
*/
|
||||
public function createQuery($schedule, $phase, $defaultParams);
|
||||
|
||||
}
|
|
@ -0,0 +1,668 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| This file is a part of CiviCRM. |
|
||||
| |
|
||||
| CiviCRM is free software; you can copy, modify, and distribute it |
|
||||
| under the terms of the GNU Affero General Public License |
|
||||
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
|
||||
| |
|
||||
| CiviCRM is distributed in the hope that it will be useful, but |
|
||||
| WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
||||
| See the GNU Affero General Public License for more details. |
|
||||
| |
|
||||
| You should have received a copy of the GNU Affero General Public |
|
||||
| License and the CiviCRM Licensing Exception along |
|
||||
| with this program; if not, contact CiviCRM LLC |
|
||||
| at info[AT]civicrm[DOT]org. If you have questions about the |
|
||||
| GNU Affero General Public License or the licensing of CiviCRM, |
|
||||
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
|
||||
+--------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
namespace Civi\ActionSchedule;
|
||||
|
||||
/**
|
||||
* Class RecipientBuilder
|
||||
* @package Civi\ActionSchedule
|
||||
*
|
||||
* The RecipientBuilder prepares a list of recipients based on an action-schedule.
|
||||
*
|
||||
* This is a four-step process, with different steps depending on:
|
||||
*
|
||||
* (a) How the recipient is identified. Sometimes recipients are identified based
|
||||
* on their relations (e.g. selecting the assignees of an activity or the
|
||||
* participants of an event), and sometimes they are manually added using
|
||||
* a flat contact list (e.g. with a contact ID or group ID).
|
||||
* (b) Whether this is the first reminder or a follow-up/repeated reminder.
|
||||
*
|
||||
* The permutations of these (a)+(b) produce four phases -- RELATION_FIRST,
|
||||
* RELATION_REPEAT, ADDITION_FIRST, ADDITION_REPEAT.
|
||||
*
|
||||
* Each phase requires running a complex query. As a general rule,
|
||||
* MappingInterface::createQuery() produces a base query, and the RecipientBuilder
|
||||
* appends extra bits (JOINs/WHEREs/GROUP BYs) depending on which step is running.
|
||||
*
|
||||
* For example, suppose we want to send reminders to anyone who registers for
|
||||
* a "Conference" or "Exhibition" event with the 'pay later' option, and we want
|
||||
* to fire the reminders X days after the registration date. The
|
||||
* MappingInterface::createQuery() could return a query like:
|
||||
*
|
||||
* @code
|
||||
* CRM_Utils_SQL_Select::from('civicrm_participant e')
|
||||
* ->join('event', 'INNER JOIN civicrm_event event ON e.event_id = event.id')
|
||||
* ->where('e.is_pay_later = 1')
|
||||
* ->where('event.event_type_id IN (#myEventTypes)')
|
||||
* ->param('myEventTypes', array(2, 5))
|
||||
* ->param('casDateField', 'e.register_date')
|
||||
* ->param($defaultParams)
|
||||
* ...etc...
|
||||
* @endcode
|
||||
*
|
||||
* In the RELATION_FIRST phase, RecipientBuilder adds a LEFT-JOIN+WHERE to find
|
||||
* participants who have *not* yet received any reminder, and filters those
|
||||
* participants based on whether X days have passed since "e.register_date".
|
||||
*
|
||||
* Notice that the query may define several SQL elements directly (eg
|
||||
* via `from()`, `where()`, `join()`, `groupBy()`). Additionally, it
|
||||
* must define some parameters (eg `casDateField`). These parameters will be
|
||||
* read by RecipientBuilder and used in other parts of the query.
|
||||
*
|
||||
* At time of writing, these parameters are required:
|
||||
* - casAddlCheckFrom: string, SQL FROM expression
|
||||
* - casContactIdField: string, SQL column expression
|
||||
* - casDateField: string, SQL column expression
|
||||
* - casEntityIdField: string, SQL column expression
|
||||
*
|
||||
* Some parameters are optional:
|
||||
* - casContactTableAlias: string, SQL table alias
|
||||
* - casAnniversaryMode: bool
|
||||
* - casUseReferenceDate: bool
|
||||
*
|
||||
* Additionally, some parameters are automatically predefined:
|
||||
* - casNow
|
||||
* - casMappingEntity: string, SQL table name
|
||||
* - casMappingId: int
|
||||
* - casActionScheduleId: int
|
||||
*
|
||||
* Note: Any parameters defined by the core Civi\ActionSchedule subsystem
|
||||
* use the prefix `cas`. If you define new parameters (like `myEventTypes`
|
||||
* above), then use a different name (to avoid conflicts).
|
||||
*/
|
||||
class RecipientBuilder {
|
||||
|
||||
private $now;
|
||||
|
||||
/**
|
||||
* Generate action_log's for new, first-time alerts to related contacts.
|
||||
*
|
||||
* @see buildRelFirstPass
|
||||
*/
|
||||
const PHASE_RELATION_FIRST = 'rel-first';
|
||||
|
||||
/**
|
||||
* Generate action_log's for new, first-time alerts to additional contacts.
|
||||
*
|
||||
* @see buildAddlFirstPass
|
||||
*/
|
||||
const PHASE_ADDITION_FIRST = 'addl-first';
|
||||
|
||||
/**
|
||||
* Generate action_log's for repeated, follow-up alerts to related contacts.
|
||||
*
|
||||
* @see buildRelRepeatPass
|
||||
*/
|
||||
const PHASE_RELATION_REPEAT = 'rel-repeat';
|
||||
|
||||
/**
|
||||
* Generate action_log's for repeated, follow-up alerts to additional contacts.
|
||||
*
|
||||
* @see buildAddlRepeatPass
|
||||
*/
|
||||
const PHASE_ADDITION_REPEAT = 'addl-repeat';
|
||||
|
||||
/**
|
||||
* @var \CRM_Core_DAO_ActionSchedule
|
||||
*/
|
||||
private $actionSchedule;
|
||||
|
||||
/**
|
||||
* @var MappingInterface
|
||||
*/
|
||||
private $mapping;
|
||||
|
||||
/**
|
||||
* @param $now
|
||||
* @param \CRM_Core_DAO_ActionSchedule $actionSchedule
|
||||
* @param MappingInterface $mapping
|
||||
*/
|
||||
public function __construct($now, $actionSchedule, $mapping) {
|
||||
$this->now = $now;
|
||||
$this->actionSchedule = $actionSchedule;
|
||||
$this->mapping = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the civicrm_action_log with any new/missing TODOs.
|
||||
*
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
public function build() {
|
||||
$this->buildRelFirstPass();
|
||||
|
||||
if ($this->prepareAddlFilter('c.id')) {
|
||||
$this->buildAddlFirstPass();
|
||||
}
|
||||
|
||||
if ($this->actionSchedule->is_repeat) {
|
||||
$this->buildRelRepeatPass();
|
||||
}
|
||||
|
||||
if ($this->actionSchedule->is_repeat && $this->prepareAddlFilter('c.id')) {
|
||||
$this->buildAddlRepeatPass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate action_log's for new, first-time alerts to related contacts.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function buildRelFirstPass() {
|
||||
$query = $this->prepareQuery(self::PHASE_RELATION_FIRST);
|
||||
|
||||
$startDateClauses = $this->prepareStartDateClauses();
|
||||
|
||||
// In some cases reference_date got outdated due to many reason e.g. In Membership renewal end_date got extended
|
||||
// which means reference date mismatches with the end_date where end_date may be used as the start_action_date
|
||||
// criteria for some schedule reminder so in order to send new reminder we INSERT new reminder with new reference_date
|
||||
// value via UNION operation
|
||||
$referenceReminderIDs = array();
|
||||
$referenceDate = NULL;
|
||||
if (!empty($query['casUseReferenceDate'])) {
|
||||
// First retrieve all the action log's ids which are outdated or in other words reference_date now don't match with entity date.
|
||||
// And the retrieve the updated entity date which will later used below to update all other outdated action log records
|
||||
$sql = $query->copy()
|
||||
->select('reminder.id as id')
|
||||
->select($query['casDateField'] . ' as reference_date')
|
||||
->merge($this->joinReminder('INNER JOIN', 'rel', $query))
|
||||
->where("reminder.id IS NOT NULL AND reminder.reference_date IS NOT NULL AND reminder.reference_date <> !casDateField")
|
||||
->where($startDateClauses)
|
||||
->orderBy("reminder.id desc")
|
||||
->strict()
|
||||
->toSQL();
|
||||
$dao = \CRM_Core_DAO::executeQuery($sql);
|
||||
|
||||
while ($dao->fetch()) {
|
||||
$referenceReminderIDs[] = $dao->id;
|
||||
$referenceDate = $dao->reference_date;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($referenceReminderIDs)) {
|
||||
$firstQuery = $query->copy()
|
||||
->merge($this->selectIntoActionLog(self::PHASE_RELATION_FIRST, $query))
|
||||
->merge($this->joinReminder('LEFT JOIN', 'rel', $query))
|
||||
->where("reminder.id IS NULL")
|
||||
->where($startDateClauses)
|
||||
->strict()
|
||||
->toSQL();
|
||||
\CRM_Core_DAO::executeQuery($firstQuery);
|
||||
}
|
||||
else {
|
||||
// INSERT new log to send reminder as desired entity date got updated
|
||||
$referenceQuery = $query->copy()
|
||||
->merge($this->selectIntoActionLog(self::PHASE_RELATION_FIRST, $query))
|
||||
->merge($this->joinReminder('LEFT JOIN', 'rel', $query))
|
||||
->where("reminder.id = !reminderID")
|
||||
->where($startDateClauses)
|
||||
->param('reminderID', $referenceReminderIDs[0])
|
||||
->strict()
|
||||
->toSQL();
|
||||
\CRM_Core_DAO::executeQuery($referenceQuery);
|
||||
|
||||
// Update all the previous outdated reference date valued, action_log rows to the latest changed entity date
|
||||
$updateQuery = "UPDATE civicrm_action_log SET reference_date = '" . $referenceDate . "' WHERE id IN (" . implode(', ', $referenceReminderIDs) . ")";
|
||||
\CRM_Core_DAO::executeQuery($updateQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate action_log's for new, first-time alerts to additional contacts.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function buildAddlFirstPass() {
|
||||
$query = $this->prepareQuery(self::PHASE_ADDITION_FIRST);
|
||||
|
||||
$insertAdditionalSql = \CRM_Utils_SQL_Select::from("civicrm_contact c")
|
||||
->merge($query, array('params'))
|
||||
->merge($this->selectIntoActionLog(self::PHASE_ADDITION_FIRST, $query))
|
||||
->merge($this->joinReminder('LEFT JOIN', 'addl', $query))
|
||||
->where('reminder.id IS NULL')
|
||||
->where("c.is_deleted = 0 AND c.is_deceased = 0")
|
||||
->merge($this->prepareAddlFilter('c.id'))
|
||||
->where("c.id NOT IN (
|
||||
SELECT rem.contact_id
|
||||
FROM civicrm_action_log rem INNER JOIN {$this->mapping->getEntity()} e ON rem.entity_id = e.id
|
||||
WHERE rem.action_schedule_id = {$this->actionSchedule->id}
|
||||
AND rem.entity_table = '{$this->mapping->getEntity()}'
|
||||
)")
|
||||
// Where does e.id come from here? ^^^
|
||||
->groupBy("c.id")
|
||||
->strict()
|
||||
->toSQL();
|
||||
\CRM_Core_DAO::executeQuery($insertAdditionalSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate action_log's for repeated, follow-up alerts to related contacts.
|
||||
*
|
||||
* @throws \CRM_Core_Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function buildRelRepeatPass() {
|
||||
$query = $this->prepareQuery(self::PHASE_RELATION_REPEAT);
|
||||
$startDateClauses = $this->prepareStartDateClauses();
|
||||
|
||||
// CRM-15376 - do not send our reminders if original criteria no longer applies
|
||||
// the first part of the startDateClause array is the earliest the reminder can be sent. If the
|
||||
// event (e.g membership_end_date) has changed then the reminder may no longer apply
|
||||
// @todo - this only handles events that get moved later. Potentially they might get moved earlier
|
||||
$repeatInsert = $query
|
||||
->merge($this->joinReminder('INNER JOIN', 'rel', $query))
|
||||
->merge($this->selectActionLogFields(self::PHASE_RELATION_REPEAT, $query))
|
||||
->select("MAX(reminder.action_date_time) as latest_log_time")
|
||||
->merge($this->prepareRepetitionEndFilter($query['casDateField']))
|
||||
->where($this->actionSchedule->start_action_date ? $startDateClauses[0] : array())
|
||||
->groupBy("reminder.contact_id, reminder.entity_id, reminder.entity_table")
|
||||
// @todo replace use of timestampdiff with a direct comparison as TIMESTAMPDIFF cannot use an index.
|
||||
->having("TIMESTAMPDIFF(HOUR, latest_log_time, CAST(!casNow AS datetime)) >= TIMESTAMPDIFF(HOUR, latest_log_time, DATE_ADD(latest_log_time, INTERVAL !casRepetitionInterval))")
|
||||
->param(array(
|
||||
'casRepetitionInterval' => $this->parseRepetitionInterval(),
|
||||
))
|
||||
->strict()
|
||||
->toSQL();
|
||||
|
||||
// For unknown reasons, we manually insert each row. Why not change
|
||||
// selectActionLogFields() to selectIntoActionLog() above?
|
||||
|
||||
$arrValues = \CRM_Core_DAO::executeQuery($repeatInsert)->fetchAll();
|
||||
if ($arrValues) {
|
||||
\CRM_Core_DAO::executeQuery(
|
||||
\CRM_Utils_SQL_Insert::into('civicrm_action_log')
|
||||
->columns(array('contact_id', 'entity_id', 'entity_table', 'action_schedule_id'))
|
||||
->rows($arrValues)
|
||||
->toSQL()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate action_log's for repeated, follow-up alerts to additional contacts.
|
||||
*
|
||||
* @throws \CRM_Core_Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function buildAddlRepeatPass() {
|
||||
$query = $this->prepareQuery(self::PHASE_ADDITION_REPEAT);
|
||||
|
||||
$addlCheck = \CRM_Utils_SQL_Select::from($query['casAddlCheckFrom'])
|
||||
->select('*')
|
||||
->merge($query, array('params', 'wheres'))// why only where? why not the joins?
|
||||
->merge($this->prepareRepetitionEndFilter($query['casDateField']))
|
||||
->limit(1)
|
||||
->strict()
|
||||
->toSQL();
|
||||
|
||||
$daoCheck = \CRM_Core_DAO::executeQuery($addlCheck);
|
||||
if ($daoCheck->fetch()) {
|
||||
$repeatInsertAddl = \CRM_Utils_SQL_Select::from('civicrm_contact c')
|
||||
->merge($this->selectActionLogFields(self::PHASE_ADDITION_REPEAT, $query))
|
||||
->merge($this->joinReminder('INNER JOIN', 'addl', $query))
|
||||
->select("MAX(reminder.action_date_time) as latest_log_time")
|
||||
->merge($this->prepareAddlFilter('c.id'), array('params'))
|
||||
->where("c.is_deleted = 0 AND c.is_deceased = 0")
|
||||
->groupBy("reminder.contact_id")
|
||||
// @todo replace use of timestampdiff with a direct comparison as TIMESTAMPDIFF cannot use an index.
|
||||
->having("TIMESTAMPDIFF(HOUR, latest_log_time, CAST(!casNow AS datetime)) >= TIMESTAMPDIFF(HOUR, latest_log_time, DATE_ADD(latest_log_time, INTERVAL !casRepetitionInterval))")
|
||||
->param(array(
|
||||
'casRepetitionInterval' => $this->parseRepetitionInterval(),
|
||||
))
|
||||
->strict()
|
||||
->toSQL();
|
||||
|
||||
// For unknown reasons, we manually insert each row. Why not change
|
||||
// selectActionLogFields() to selectIntoActionLog() above?
|
||||
|
||||
$addValues = \CRM_Core_DAO::executeQuery($repeatInsertAddl)->fetchAll();
|
||||
if ($addValues) {
|
||||
\CRM_Core_DAO::executeQuery(
|
||||
\CRM_Utils_SQL_Insert::into('civicrm_action_log')
|
||||
->columns(array('contact_id', 'entity_id', 'entity_table', 'action_schedule_id'))
|
||||
->rows($addValues)
|
||||
->toSQL()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $phase
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
protected function prepareQuery($phase) {
|
||||
$defaultParams = array(
|
||||
'casActionScheduleId' => $this->actionSchedule->id,
|
||||
'casMappingId' => $this->mapping->getId(),
|
||||
'casMappingEntity' => $this->mapping->getEntity(),
|
||||
'casNow' => $this->now,
|
||||
);
|
||||
|
||||
/** @var \CRM_Utils_SQL_Select $query */
|
||||
$query = $this->mapping->createQuery($this->actionSchedule, $phase, $defaultParams);
|
||||
|
||||
if ($this->actionSchedule->limit_to /*1*/) {
|
||||
$query->merge($this->prepareContactFilter($query['casContactIdField']));
|
||||
}
|
||||
|
||||
if (empty($query['casContactTableAlias'])) {
|
||||
$query['casContactTableAlias'] = 'c';
|
||||
$query->join('c', "INNER JOIN civicrm_contact c ON c.id = !casContactIdField AND c.is_deleted = 0 AND c.is_deceased = 0 ");
|
||||
}
|
||||
$multilingual = \CRM_Core_I18n::isMultilingual();
|
||||
if ($multilingual && !empty($this->actionSchedule->filter_contact_language)) {
|
||||
$query->where($this->prepareLanguageFilter($query['casContactTableAlias']));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse repetition interval.
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
protected function parseRepetitionInterval() {
|
||||
$actionSchedule = $this->actionSchedule;
|
||||
if ($actionSchedule->repetition_frequency_unit == 'day') {
|
||||
$interval = "{$actionSchedule->repetition_frequency_interval} DAY";
|
||||
}
|
||||
elseif ($actionSchedule->repetition_frequency_unit == 'week') {
|
||||
$interval = "{$actionSchedule->repetition_frequency_interval} WEEK";
|
||||
}
|
||||
elseif ($actionSchedule->repetition_frequency_unit == 'month') {
|
||||
$interval = "{$actionSchedule->repetition_frequency_interval} MONTH";
|
||||
}
|
||||
elseif ($actionSchedule->repetition_frequency_unit == 'year') {
|
||||
$interval = "{$actionSchedule->repetition_frequency_interval} YEAR";
|
||||
}
|
||||
else {
|
||||
$interval = "{$actionSchedule->repetition_frequency_interval} HOUR";
|
||||
}
|
||||
return $interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare filter options for limiting by contact ID or group ID.
|
||||
*
|
||||
* @param string $contactIdField
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
*/
|
||||
protected function prepareContactFilter($contactIdField) {
|
||||
$actionSchedule = $this->actionSchedule;
|
||||
|
||||
if ($actionSchedule->group_id) {
|
||||
if ($this->isSmartGroup($actionSchedule->group_id)) {
|
||||
// Check that the group is in place in the cache and up to date
|
||||
\CRM_Contact_BAO_GroupContactCache::check($actionSchedule->group_id);
|
||||
return \CRM_Utils_SQL_Select::fragment()
|
||||
->join('grp', "INNER JOIN civicrm_group_contact_cache grp ON {$contactIdField} = grp.contact_id")
|
||||
->where(" grp.group_id IN ({$actionSchedule->group_id})");
|
||||
}
|
||||
else {
|
||||
return \CRM_Utils_SQL_Select::fragment()
|
||||
->join('grp', " INNER JOIN civicrm_group_contact grp ON {$contactIdField} = grp.contact_id AND grp.status = 'Added'")
|
||||
->where(" grp.group_id IN ({$actionSchedule->group_id})");
|
||||
}
|
||||
}
|
||||
elseif (!empty($actionSchedule->recipient_manual)) {
|
||||
$rList = \CRM_Utils_Type::escape($actionSchedule->recipient_manual, 'String');
|
||||
return \CRM_Utils_SQL_Select::fragment()
|
||||
->where("{$contactIdField} IN ({$rList})");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare language filter.
|
||||
*
|
||||
* @param string $contactTableAlias
|
||||
* @return string
|
||||
*/
|
||||
protected function prepareLanguageFilter($contactTableAlias) {
|
||||
$actionSchedule = $this->actionSchedule;
|
||||
|
||||
// get language filter for the schedule
|
||||
$filter_contact_language = explode(\CRM_Core_DAO::VALUE_SEPARATOR, $actionSchedule->filter_contact_language);
|
||||
$w = '';
|
||||
if (($key = array_search(\CRM_Core_I18n::NONE, $filter_contact_language)) !== FALSE) {
|
||||
$w .= "{$contactTableAlias}.preferred_language IS NULL OR {$contactTableAlias}.preferred_language = '' OR ";
|
||||
unset($filter_contact_language[$key]);
|
||||
}
|
||||
if (count($filter_contact_language) > 0) {
|
||||
$w .= "{$contactTableAlias}.preferred_language IN ('" . implode("','", $filter_contact_language) . "')";
|
||||
}
|
||||
$w = "($w)";
|
||||
return $w;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareStartDateClauses() {
|
||||
$actionSchedule = $this->actionSchedule;
|
||||
$startDateClauses = array();
|
||||
if ($actionSchedule->start_action_date) {
|
||||
$op = ($actionSchedule->start_action_condition == 'before' ? '<=' : '>=');
|
||||
$operator = ($actionSchedule->start_action_condition == 'before' ? 'DATE_SUB' : 'DATE_ADD');
|
||||
$date = $operator . "(!casDateField, INTERVAL {$actionSchedule->start_action_offset} {$actionSchedule->start_action_unit})";
|
||||
$startDateClauses[] = "'!casNow' >= {$date}";
|
||||
// This is weird. Waddupwidat?
|
||||
if ($this->mapping->getEntity() == 'civicrm_participant') {
|
||||
$startDateClauses[] = $operator . "(!casNow, INTERVAL 1 DAY ) {$op} " . '!casDateField';
|
||||
}
|
||||
else {
|
||||
$startDateClauses[] = "DATE_SUB(!casNow, INTERVAL 1 DAY ) <= {$date}";
|
||||
}
|
||||
}
|
||||
elseif ($actionSchedule->absolute_date) {
|
||||
$startDateClauses[] = "DATEDIFF(DATE('!casNow'),'{$actionSchedule->absolute_date}') = 0";
|
||||
}
|
||||
return $startDateClauses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $groupId
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSmartGroup($groupId) {
|
||||
// Then decide which table to join onto the query
|
||||
$group = \CRM_Contact_DAO_Group::getTableName();
|
||||
|
||||
// Get the group information
|
||||
$sql = "
|
||||
SELECT $group.id, $group.cache_date, $group.saved_search_id, $group.children
|
||||
FROM $group
|
||||
WHERE $group.id = {$groupId}
|
||||
";
|
||||
|
||||
$groupDAO = \CRM_Core_DAO::executeQuery($sql);
|
||||
if (
|
||||
$groupDAO->fetch() &&
|
||||
!empty($groupDAO->saved_search_id)
|
||||
) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dateField
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
*/
|
||||
protected function prepareRepetitionEndFilter($dateField) {
|
||||
$repeatEventDateExpr = ($this->actionSchedule->end_action == 'before' ? 'DATE_SUB' : 'DATE_ADD')
|
||||
. "({$dateField}, INTERVAL {$this->actionSchedule->end_frequency_interval} {$this->actionSchedule->end_frequency_unit})";
|
||||
|
||||
return \CRM_Utils_SQL_Select::fragment()
|
||||
->where("@casNow <= !repetitionEndDate")
|
||||
->param(array(
|
||||
'!repetitionEndDate' => $repeatEventDateExpr,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contactIdField
|
||||
* @return \CRM_Utils_SQL_Select|null
|
||||
*/
|
||||
protected function prepareAddlFilter($contactIdField) {
|
||||
$contactAddlFilter = NULL;
|
||||
if ($this->actionSchedule->limit_to !== NULL && !$this->actionSchedule->limit_to /*0*/) {
|
||||
$contactAddlFilter = $this->prepareContactFilter($contactIdField);
|
||||
}
|
||||
return $contactAddlFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a query fragment like for populating
|
||||
* action logs, e.g.
|
||||
*
|
||||
* "SELECT contact_id, entity_id, entity_table, action schedule_id"
|
||||
*
|
||||
* @param string $phase
|
||||
* @param \CRM_Utils_SQL_Select $query
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
protected function selectActionLogFields($phase, $query) {
|
||||
switch ($phase) {
|
||||
case self::PHASE_RELATION_FIRST:
|
||||
case self::PHASE_RELATION_REPEAT:
|
||||
$fragment = \CRM_Utils_SQL_Select::fragment();
|
||||
// CRM-15376: We are not tracking the reference date for 'repeated' schedule reminders.
|
||||
if (!empty($query['casUseReferenceDate'])) {
|
||||
$fragment->select($query['casDateField']);
|
||||
}
|
||||
$fragment->select(
|
||||
array(
|
||||
"!casContactIdField as contact_id",
|
||||
"!casEntityIdField as entity_id",
|
||||
"@casMappingEntity as entity_table",
|
||||
"#casActionScheduleId as action_schedule_id",
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
case self::PHASE_ADDITION_FIRST:
|
||||
case self::PHASE_ADDITION_REPEAT:
|
||||
//CRM-19017: Load default params for fragment query object.
|
||||
$params = array(
|
||||
'casActionScheduleId' => $this->actionSchedule->id,
|
||||
'casNow' => $this->now,
|
||||
);
|
||||
$fragment = \CRM_Utils_SQL_Select::fragment()->param($params);
|
||||
$fragment->select(
|
||||
array(
|
||||
"c.id as contact_id",
|
||||
"c.id as entity_id",
|
||||
"'civicrm_contact' as entity_table",
|
||||
"#casActionScheduleId as action_schedule_id",
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \CRM_Core_Exception("Unrecognized phase: $phase");
|
||||
}
|
||||
return $fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a query fragment like for populating
|
||||
* action logs, e.g.
|
||||
*
|
||||
* "INSERT INTO civicrm_action_log (...) SELECT (...)"
|
||||
*
|
||||
* @param string $phase
|
||||
* @param \CRM_Utils_SQL_Select $query
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
protected function selectIntoActionLog($phase, $query) {
|
||||
$actionLogColumns = array(
|
||||
"contact_id",
|
||||
"entity_id",
|
||||
"entity_table",
|
||||
"action_schedule_id",
|
||||
);
|
||||
if ($phase === self::PHASE_RELATION_FIRST || $phase === self::PHASE_RELATION_REPEAT) {
|
||||
if (!empty($query['casUseReferenceDate'])) {
|
||||
array_unshift($actionLogColumns, 'reference_date');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->selectActionLogFields($phase, $query)
|
||||
->insertInto('civicrm_action_log', $actionLogColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JOIN clause like "INNER JOIN civicrm_action_log reminder ON...".
|
||||
*
|
||||
* @param string $joinType
|
||||
* Join type (eg INNER JOIN, LEFT JOIN).
|
||||
* @param string $for
|
||||
* Ex: 'rel', 'addl'.
|
||||
* @param \CRM_Utils_SQL_Select $query
|
||||
* @return \CRM_Utils_SQL_Select
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
protected function joinReminder($joinType, $for, $query) {
|
||||
switch ($for) {
|
||||
case 'rel':
|
||||
$contactIdField = $query['casContactIdField'];
|
||||
$entityName = $this->mapping->getEntity();
|
||||
$entityIdField = $query['casEntityIdField'];
|
||||
break;
|
||||
|
||||
case 'addl':
|
||||
$contactIdField = 'c.id';
|
||||
$entityName = 'civicrm_contact';
|
||||
$entityIdField = 'c.id';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \CRM_Core_Exception("Unrecognized 'for': $for");
|
||||
}
|
||||
|
||||
$joinClause = "civicrm_action_log reminder ON reminder.contact_id = {$contactIdField} AND
|
||||
reminder.entity_id = {$entityIdField} AND
|
||||
reminder.entity_table = '{$entityName}' AND
|
||||
reminder.action_schedule_id = {$this->actionSchedule->id}";
|
||||
|
||||
// Why do we only include anniversary clause for 'rel' queries?
|
||||
if ($for === 'rel' && !empty($query['casAnniversaryMode'])) {
|
||||
// only consider reminders less than 11 months ago
|
||||
$joinClause .= " AND reminder.action_date_time > DATE_SUB(!casNow, INTERVAL 11 MONTH)";
|
||||
}
|
||||
|
||||
return \CRM_Utils_SQL_Select::fragment()->join("reminder", "$joinType $joinClause");
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue