Support updating a single contact

This commit is contained in:
Rich Lott / Artful Robot 2025-02-28 21:55:04 +00:00
parent 8d0a9d0926
commit 016ca2a735
2 changed files with 37 additions and 9 deletions

View file

@ -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.

View file

@ -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(<<<SQL
SELECT id, category_definition_id, next_category
FROM civicrm_contact_category
WHERE next_category <> 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(<<<SQL
DELETE cat
FROM civicrm_contact_category cat
INNER JOIN civicrm_contact ct ON ct.id = cat.id AND ct.is_deleted = 1
$singleContactClause
SQL);
if ($dao->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(<<<SQL
INSERT INTO civicrm_contact_category
SELECT id, NULL AS category_definition_id, 0 AS next_category
FROM civicrm_contact
WHERE is_deleted = 0
WHERE is_deleted = 0 $singleContactClause
AND NOT EXISTS (SELECT id FROM civicrm_contact_category WHERE id = civicrm_contact.id)
SQL)->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 = <<<SQL
UPDATE civicrm_contact_category cc
INNER JOIN $table gc ON gc.contact_id = cc.id AND group_id = $groupID $statusWhere
SET next_category = {$cat['id']}
WHERE next_category = 0
WHERE next_category = 0 $andContactIdCriterion
SQL;
CRM_Core_DAO::executeQuery($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));