First commit
This commit is contained in:
		
						commit
						c6e2478c40
					
				
					 13918 changed files with 2303184 additions and 0 deletions
				
			
		|  | @ -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