From 016ca2a735b5bfa9c3641de53009e457f58ef1b2 Mon Sep 17 00:00:00 2001 From: Rich Lott / Artful Robot Date: Fri, 28 Feb 2025 21:55:04 +0000 Subject: [PATCH] Support updating a single contact --- Civi/Api4/Action/ContactCategory/Sync.php | 14 ++++++++-- Civi/ContactCats/Processor.php | 32 ++++++++++++++++++----- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/Civi/Api4/Action/ContactCategory/Sync.php b/Civi/Api4/Action/ContactCategory/Sync.php index a7cb9ec..e3ed17c 100644 --- a/Civi/Api4/Action/ContactCategory/Sync.php +++ b/Civi/Api4/Action/ContactCategory/Sync.php @@ -13,6 +13,15 @@ use Civi\ContactCats\Processor; */ class Sync extends \Civi\Api4\Generic\AbstractAction { + /** + * Contact ID + * + * Force sync for given contact only. + * + * @var int + */ + protected $contact_id; + /** * Usually this will not act unless the time is right. * @@ -24,7 +33,8 @@ class Sync extends \Civi\Api4\Generic\AbstractAction { public function _run(Result $result) { ini_set('memory_limit', '256M'); Civi::log()->debug('Begin', ['=start' => 'ContactCatSync', '=timed' => 1]); - if (!$this->force) { + + if (!$this->force && !$this->contact_id) { $nextRun = Civi::settings()->get('contactcats_next_run') ?? 0; if (time() < $nextRun) { // not needed yet. @@ -33,7 +43,7 @@ class Sync extends \Civi\Api4\Generic\AbstractAction { } } - $processor = new Processor(); + $processor = new Processor($this->contact_id); $result->exchangeArray($processor->run()); // Limit to running every 24 hours; we actually want it to be stable within one day. diff --git a/Civi/ContactCats/Processor.php b/Civi/ContactCats/Processor.php index f8e977c..a432fcd 100644 --- a/Civi/ContactCats/Processor.php +++ b/Civi/ContactCats/Processor.php @@ -18,7 +18,11 @@ class Processor { protected array $searchDetails = []; - public function __construct() { + protected ?int $contact_id = NULL; + + public function __construct(?int $contact_id = NULL) { + + $this->contact_id = $contact_id; $this->categories = ContactCategoryDefinition::get(FALSE) ->addOrderBy('execution_order') @@ -106,12 +110,16 @@ class Processor { return $summary; } + /** + * Creates change activities and actually updates the current category data. + */ protected function createChangeActivities() { + $singleContactClause = $this->contact_id ? "AND id = $this->contact_id" : ""; Civi::log()->debug("Calculate changes", ['=' => 'timed', '=start' => "changes"]); $changes = CRM_Core_DAO::executeQuery(<< category_definition_id + WHERE next_category <> category_definition_id $singleContactClause ORDER BY category_definition_id, next_category SQL); $lastChange = [0, 0]; @@ -163,10 +171,11 @@ class Processor { Civi::log()->debug('Apply changes', ['=change' => 'applyChanges', '=timed' => 1]); CRM_Core_DAO::executeQuery("UPDATE civicrm_contact_category SET category_definition_id = next_category - WHERE category_definition_id IS NULL OR category_definition_id <> next_category")->free(); + WHERE (category_definition_id IS NULL OR category_definition_id <> next_category) $singleContactClause")->free(); Civi::log()->debug('', ['=pop' => 1]); - $summary = CRM_Core_DAO::executeQuery("SELECT next_category, count(*) from civicrm_contact_category GROUP BY next_category")->fetchAll(); + $singleContactClause = $this->contact_id ? "WHERE id = $this->contact_id" : ""; + $summary = CRM_Core_DAO::executeQuery("SELECT next_category, count(*) from civicrm_contact_category $singleContactClause GROUP BY next_category")->fetchAll(); $summary['changes'] = $n; $_ = memory_get_peak_usage(TRUE); $summary['memory_use'] = @round($_ / pow(1024, ($i = floor(log($_, 1024)))), 2) . ' ' . ['b', 'kb', 'mb', 'gb', 'tb', 'pb'][$i]; @@ -194,11 +203,14 @@ class Processor { protected function resetTable() { Civi::log()->debug('Resetting table', ['=' => 'start,timed']); + $singleContactClause = $this->contact_id ? "WHERE cat.id = $this->contact_id" : ""; + // Delete our data for deleted contacts. $dao = CRM_Core_DAO::executeQuery(<<N) { Civi::log()->debug("Deleted category data for {$dao->N} trashed contacts"); @@ -206,16 +218,17 @@ class Processor { $dao->free(); // Zero our internal next_category field. - CRM_Core_DAO::executeQuery("UPDATE civicrm_contact_category SET next_category = 0;")->free(); + CRM_Core_DAO::executeQuery("UPDATE civicrm_contact_category cat SET next_category = 0 $singleContactClause;")->free(); Civi::log()->debug('stage 2'); // ensure we have all our contacts covered. // Q: is it quicker to do a WHERE NOT EXISTS? A: nope. + $singleContactClause = $this->contact_id ? "AND id = $this->contact_id" : ""; CRM_Core_DAO::executeQuery(<<free(); Civi::log()->debug('Done resetting table', ['=' => 'pop']); @@ -233,11 +246,12 @@ class Processor { Civi::log()->debug($group['title'] . ($isSmart ? '(smart)' : ''), ['=' => 'timed', '=start' => "group$groupID"]); $table = $isSmart ? 'civicrm_group_contact_cache' : 'civicrm_group_contact'; $statusWhere = $isSmart ? '' : 'AND gc.status = "Added"'; + $andContactIdCriterion = $this->contact_id ? "AND cc.id=$this->contact_id" : ''; $sql = <<free(); Civi::log()->debug('', ['=' => 'pop']); @@ -255,6 +269,10 @@ class Processor { $contactIdKey = 'id'; // We only need the ID. $apiParams['select'] = ['id']; + if ($this->contact_id) { + // Limit to one contact. + $apiParams['where'][] = ['id', '=', $this->contact_id]; + } } else { throw new CRM_Core_Exception("can't figure out contactID in search: " . json_encode($search));