mirror of
https://codeberg.org/artfulrobot/contactcats.git
synced 2025-06-26 11:28:04 +02:00
Rewrite sync to use new features
This commit is contained in:
parent
b0983603e5
commit
16f2b6235d
7 changed files with 502 additions and 365 deletions
246
Civi/ContactCats/Processor.php
Normal file
246
Civi/ContactCats/Processor.php
Normal file
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
namespace Civi\ContactCats;
|
||||
|
||||
use CRM_Contactcats_ExtensionUtil as E;
|
||||
use Civi;
|
||||
use Civi\Api4\Activity;
|
||||
use Civi\Api4\ContactCategoryDefinition;
|
||||
use Civi\Api4\Group;
|
||||
use Civi\Api4\SavedSearch;
|
||||
use CRM_Core_DAO;
|
||||
use CRM_Core_Exception;
|
||||
|
||||
class Processor {
|
||||
|
||||
protected array $categories = [];
|
||||
|
||||
protected array $groupDetails = [];
|
||||
|
||||
protected array $searchDetails = [];
|
||||
|
||||
public function __construct() {
|
||||
|
||||
$this->categories = ContactCategoryDefinition::get(FALSE)
|
||||
->addOrderBy('execution_order')
|
||||
->execute()->getArrayCopy();
|
||||
|
||||
// Identify groups and searches used by definitions.
|
||||
$groupIDs = $searchIDs = [];
|
||||
foreach ($this->categories as $cat) {
|
||||
if ($cat['search_type'] === 'group') {
|
||||
$groupIDs[] = (int) $cat['search_data']['group_id'];
|
||||
}
|
||||
elseif ($cat['search_type'] === 'search') {
|
||||
$searchIDs[] = (int) $cat['search_data']['saved_search_id'];
|
||||
}
|
||||
}
|
||||
|
||||
// Load group details; ensure all groups still exist.
|
||||
$this->groupDetails = [];
|
||||
if ($groupIDs) {
|
||||
$this->groupDetails = Group::get(FALSE)
|
||||
->addWhere('id', 'IN', $groupIDs)
|
||||
->execute()->indexBy('id')->getArrayCopy();
|
||||
foreach ($this->categories as $cat) {
|
||||
if ($cat['search_type'] === 'group') {
|
||||
if (!isset($this->groupDetails[$cat['search_data']['group_id'] ?? 0])) {
|
||||
throw new CRM_Core_Exception("ContactCategoryDefinition $cat[id] $cat[label] references invalid Group: " . $cat['search_data']['group_id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all searches
|
||||
$this->searchDetails = [];
|
||||
if ($searchIDs) {
|
||||
$this->searchDetails = SavedSearch::get(FALSE)
|
||||
->addWhere('id', 'IN', $searchIDs)
|
||||
->execute()->indexBy('id')->getArrayCopy();
|
||||
foreach ($this->categories as $cat) {
|
||||
if ($cat['search_type'] === 'search') {
|
||||
if (!isset($this->searchDetails[$cat['search_data']['saved_search_id'] ?? 0])) {
|
||||
throw new CRM_Core_Exception("ContactCategoryDefinition $cat[id] $cat[label] references invalid Search: " . $cat['search_data']['saved_search_id']);
|
||||
}
|
||||
$search = $this->searchDetails[$cat['search_data']['saved_search_id']];
|
||||
if (!isset($search['api_params']) || !isset($search['api_entity'])) {
|
||||
throw new CRM_Core_Exception("ContactCategoryDefinition $cat[id] $cat[label] uses search $search[id] which does not look appropriate.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check things like searches that group by contact_id
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
$this->refreshSmartGroups();
|
||||
$summary = [];
|
||||
\CRM_Core_Transaction::create()->run(function($tx) use (&$summary) {
|
||||
$this->resetTable();
|
||||
foreach ($this->categories as $cat) {
|
||||
if ($cat['search_type'] === 'group') {
|
||||
$this->assignCategoryFromGroup($cat);
|
||||
}
|
||||
elseif ($cat['search_type'] === 'search') {
|
||||
$this->assignCategoryFromSearch($cat);
|
||||
}
|
||||
// future...
|
||||
}
|
||||
$summary = $this->createChangeActivities();
|
||||
});
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
protected function createChangeActivities() {
|
||||
Civi::log()->debug("Calculate changes", ['=' => 'timed', '=start' => "changes"]);
|
||||
$changes = CRM_Core_DAO::executeQuery(<<<SQL
|
||||
SELECT id, category, next_category
|
||||
FROM civicrm_contact_category
|
||||
WHERE next_category <> category
|
||||
ORDER BY category, next_category
|
||||
SQL);
|
||||
$lastChange = [0, 0];
|
||||
$batch = [];
|
||||
$n = 0;
|
||||
$domainContactID = \CRM_Core_BAO_Domain::getDomain()->contact_id;
|
||||
$writeBatch = function() use (&$lastChange, &$batch, $domainContactID) {
|
||||
if (empty($batch)) {
|
||||
return;
|
||||
}
|
||||
$oldCategoryId = (int) ($lastChange[0] ?? 0);
|
||||
$newCategoryId = (int) ($lastChange[1] ?? 0);
|
||||
$oldName = $this->categories[$oldCategoryId]['label'] ?? E::ts('(Unknown)');
|
||||
$subject = "$oldName → " . $this->categories[$newCategoryId];
|
||||
// Create activity on the first contact
|
||||
$a = Activity::create(FALSE)
|
||||
->addValue('target_contact_id', $batch[0])
|
||||
->addValue('activity_type_id:name', 'changed_contact_category')
|
||||
->addValue('source_contact_id', $domainContactID)
|
||||
->addValue('status_id:name', 'Completed')
|
||||
->addValue('subject', $subject)
|
||||
->execute()->first();
|
||||
$activityID = (int) $a['id'];
|
||||
// Join activity to all relevant contacts
|
||||
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_activity_contact (contact_id, activity_id, record_type_id)
|
||||
SELECT id contact_id, $activityID activity_id, 3 record_type_id
|
||||
FROM civicrm_contact_category cc
|
||||
WHERE cc.category = $oldCategoryId
|
||||
AND cc.next_category = $newCategoryId
|
||||
AND contact_id <> $batch[0]
|
||||
")->free();
|
||||
Civi::log()->debug(count($batch) . " changes: $subject");
|
||||
};
|
||||
|
||||
while ($changes->fetch()) {
|
||||
$n++;
|
||||
if ($lastChange[0] !== $changes->category || $lastChange[1] !== $changes->next_category) {
|
||||
$writeBatch();
|
||||
$lastChange = [$changes->category, $changes->next_category];
|
||||
$batch = [];
|
||||
}
|
||||
$batch[] = $changes->id;
|
||||
}
|
||||
$changes->free();
|
||||
$writeBatch();
|
||||
|
||||
Civi::log()->debug('Apply changes', ['=change' => 'applyChanges', '=timed' => 1]);
|
||||
CRM_Core_DAO::executeQuery("UPDATE civicrm_contact_category
|
||||
SET category = next_category WHERE category <> next_category")->free();
|
||||
Civi::log()->debug('', ['=pop' => 1]);
|
||||
|
||||
$summary = CRM_Core_DAO::executeQuery("SELECT next_category, count(*) from civicrm_contact_category 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];
|
||||
return $summary;
|
||||
|
||||
}
|
||||
|
||||
protected function refreshSmartGroups() {
|
||||
$smartGroups = [];
|
||||
foreach ($this->groupDetails as $groupID => $group) {
|
||||
if (!empty($group['saved_search_id'])) {
|
||||
$smartGroups[] = $groupID;
|
||||
}
|
||||
}
|
||||
if ($smartGroups) {
|
||||
Civi::log()->debug('Refreshing smart groups', ['=' => 'timed,start', 'groups' => $smartGroups]);
|
||||
\CRM_Contact_BAO_GroupContactCache::loadAll($smartGroups);
|
||||
Civi::log()->debug('', ['=' => 'pop']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function resetTable() {
|
||||
Civi::log()->debug('Resetting table', ['=' => 'start,timed']);
|
||||
// clear out temp space.
|
||||
CRM_Core_DAO::executeQuery("UPDATE civicrm_contact_category SET next_category = 0;")->free();
|
||||
// ensure we have all our contacts covered.
|
||||
// TODO: is it quicker to do a WHERE NOT EXISTS?
|
||||
CRM_Core_DAO::executeQuery(<<<SQL
|
||||
INSERT IGNORE INTO civicrm_contact_category
|
||||
SELECT id, id contact_id, 0 as category, 0 next_category
|
||||
FROM civicrm_contact
|
||||
SQL)->free();
|
||||
Civi::log()->debug('', ['=' => 'pop']);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function assignCategoryFromGroup(array $cat) {
|
||||
$groupID = (int) $cat['search_data']['group_id'];
|
||||
$group = $this->groupDetails[$groupID];
|
||||
|
||||
// Put unclaimed contacts in this group into this category.
|
||||
$isSmart = !empty($group['saved_search_id']);
|
||||
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"';
|
||||
$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
|
||||
SQL;
|
||||
CRM_Core_DAO::executeQuery($sql)->free();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function assignCategoryFromSearch(array $cat) {
|
||||
$search = $this->searchDetails[$cat['search_data']['saved_search_id']];
|
||||
|
||||
$apiParams = $search['api_params'];
|
||||
if ($search['api_entity'] === 'Contact' && in_array('id', $apiParams['select'] ?? [])) {
|
||||
$contactIdKey = 'id';
|
||||
// We only need the ID.
|
||||
$apiParams['select'] = ['id'];
|
||||
}
|
||||
else {
|
||||
throw new CRM_Core_Exception("can't figure out contactID in search: " . json_encode($search));
|
||||
}
|
||||
// We don't need them ordered.
|
||||
unset($apiParams['orderBy']);
|
||||
|
||||
$contactIDs = civicrm_api4($search['api_entity'], $search['get'], $apiParams)->column($contactIdKey);
|
||||
// Unsure if this batching is needed
|
||||
$batchSize = 10000;
|
||||
while ($batch = array_splice($contactIDs, 0, $batchSize)) {
|
||||
$batch = implode(',', $batch);
|
||||
if (!preg_match('/^[0-9,]+$/', $batch)) {
|
||||
// Surely this can never happen.
|
||||
throw new CRM_Core_Exception("Erm, received non-integer contact IDs from Search $search[id]!");
|
||||
}
|
||||
$sql = <<<SQL
|
||||
UPDATE civicrm_contact_category cc
|
||||
SET next_category = {$cat['id']}
|
||||
WHERE next_category = 0 AND id IN ($batch)
|
||||
SQL;
|
||||
CRM_Core_DAO::executeQuery($sql)->free();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue