Merge pull request #17236 from civicrm/5.25
[civicrm-core.git] / CRM / Utils / Migrate / ExportJSON.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17class CRM_Utils_Migrate_ExportJSON {
7da04cde 18 const CHUNK_SIZE = 128;
6a488035
TO
19
20 protected $_contactIDs;
21
22 protected $_allContactIDs;
23
24 protected $_values;
25
26 protected $_discoverContacts = FALSE;
27
28 protected $_renameGroups = 1;
29
30 protected $_renameTags = 1;
31
3302658e
TO
32 protected $_sitePrefix = 'Site 1';
33
5bc392e6 34 /**
c490a46a 35 * @param array $params
5bc392e6 36 */
00be9182 37 public function __construct(&$params) {
6a488035
TO
38 foreach ($params as $name => $value) {
39 $varName = '_' . $name;
40 $this->$varName = $value;
41 }
42 }
43
44 /**
fe482240 45 * Split a large array of contactIDs into more manageable smaller chunks.
54957108 46 *
47 * @param array $contactIDs
48 *
49 * @return array
6a488035 50 */
00be9182 51 public function &splitContactIDs(&$contactIDs) {
6a488035
TO
52 // contactIDs could be a real large array, so we split it up into
53 // smaller chunks and then general xml for each chunk
be2fb01f 54 $chunks = [];
3302658e 55 $current = 0;
be2fb01f 56 $chunks[$current] = [];
3302658e 57 $count = 0;
6a488035
TO
58
59 foreach ($contactIDs as $k => $v) {
60 $chunks[$current][$k] = $v;
61 $count++;
62
63 if ($count == self::CHUNK_SIZE) {
64 $current++;
be2fb01f 65 $chunks[$current] = [];
6a488035
TO
66 $count = 0;
67 }
68 }
69
70 if (empty($chunks[$current])) {
71 unset($chunks[$current]);
72 }
73
74 return $chunks;
75 }
76
77 /**
fe482240 78 * Given a set of contact IDs get the values.
54957108 79 *
80 * @param array $contactIDs
81 * @param array $additionalContactIDs
6a488035 82 */
00be9182 83 public function getValues(&$contactIDs, &$additionalContactIDs) {
6a488035
TO
84
85 $this->contact($contactIDs);
86 $this->address($contactIDs);
87 $this->phone($contactIDs);
88 $this->email($contactIDs);
89 $this->im($contactIDs);
90 $this->website($contactIDs);
91 $this->note($contactIDs);
92
93 $this->group($contactIDs);
94 $this->groupContact($contactIDs);
95 $this->savedSearch($contactIDs);
96
97 $this->tag($contactIDs);
98 $this->entityTag($contactIDs);
99
100 $this->relationship($contactIDs, $additionalContactIDs);
101 $this->activity($contactIDs, $additionalContactIDs);
102 }
103
00be9182 104 public function metaData() {
be2fb01f 105 $optionGroupVars = [
6a488035
TO
106 'prefix_id' => 'individual_prefix',
107 'suffix_id' => 'individual_suffix',
108 'gender_id' => 'gender',
109 'mobile_provider' => 'mobile_provider',
110 'phone_type' => 'phone_type',
111 'activity_type' => 'activity_type',
112 'status_id' => 'activity_status_id',
113 'priority_id' => 'activity_priority_id',
114 'medium_id' => 'encounter_medium',
aa62b355 115 'communication_style_id' => 'communication_style',
6a488035
TO
116 'email_greeting' => 'email_greeting',
117 'postal_greeting' => 'postal_greeting',
118 'addressee_id' => 'addressee',
be2fb01f 119 ];
6a488035
TO
120 $this->optionGroup($optionGroupVars);
121
be2fb01f 122 $auxilaryTables = [
6a488035
TO
123 'civicrm_location_type' => 'CRM_Core_DAO_LocationType',
124 'civicrm_relationship_type' => 'CRM_Contact_DAO_RelationshipType',
be2fb01f 125 ];
6a488035
TO
126 $this->auxTable($auxilaryTables);
127 }
128
5bc392e6
EM
129 /**
130 * @param $tables
131 */
00be9182 132 public function auxTable($tables) {
6a488035 133 foreach ($tables as $tableName => $daoName) {
353ffa53 134 $fields = &$this->dbFields($daoName, TRUE);
6a488035
TO
135
136 $sql = "SELECT * from $tableName";
137 $this->sql($sql, $tableName, $fields);
138 }
139 }
140
5bc392e6
EM
141 /**
142 * @param $optionGroupVars
143 */
00be9182 144 public function optionGroup($optionGroupVars) {
6a488035 145 $names = array_values($optionGroupVars);
be2fb01f 146 $str = [];
6a488035
TO
147 foreach ($names as $name) {
148 $str[] = "'$name'";
149 }
150 $nameString = implode(",", $str);
151
152 $sql = "
153SELECT *
154FROM civicrm_option_group
155WHERE name IN ( $nameString )
156";
353ffa53 157 $fields = &$this->dbFields('CRM_Core_DAO_OptionGroup', TRUE);
6a488035
TO
158 $this->sql($sql, 'civicrm_option_group', $fields);
159
160 $sql = "
161SELECT v.*
162FROM civicrm_option_value v
163INNER JOIN civicrm_option_group g ON v.option_group_id = g.id
164WHERE g.name IN ( $nameString )
165";
353ffa53 166 $fields = &$this->dbFields('CRM_Core_DAO_OptionValue', TRUE);
6a488035
TO
167 $this->sql($sql, 'civicrm_option_value', $fields);
168 }
169
5bc392e6
EM
170 /**
171 * @param $ids
100fef9d 172 * @param string $tableName
5bc392e6
EM
173 * @param $fields
174 * @param $whereField
175 * @param null $additionalWhereCond
176 */
7c550ca0 177 public function table(
a3e55d9c
TO
178 &$ids,
179 $tableName,
180 &$fields,
181 $whereField,
182 $additionalWhereCond = NULL
6a488035
TO
183 ) {
184 if (empty($ids)) {
185 return;
186 }
187
188 $idString = implode(',', $ids);
189
190 $sql = "
191SELECT *
192 FROM $tableName
193 WHERE $whereField IN ( $idString )
194";
195
196 if ($additionalWhereCond) {
197 $sql .= " AND $additionalWhereCond";
198 }
199
200 $this->sql($sql, $tableName, $fields);
201 }
202
5bc392e6
EM
203 /**
204 * @param $sql
100fef9d 205 * @param string $tableName
5bc392e6
EM
206 * @param $fields
207 */
00be9182 208 public function sql($sql, $tableName, &$fields) {
353ffa53 209 $dao = &CRM_Core_DAO::executeQuery($sql);
6a488035
TO
210
211 while ($dao->fetch()) {
be2fb01f 212 $value = [];
6a488035
TO
213 foreach ($fields as $name) {
214 if (empty($dao->$name)) {
215 $value[$name] = NULL;
216 }
217 else {
218 $value[$name] = $dao->$name;
219 }
220 }
221 $this->appendValue($dao->id, $tableName, $value);
222 }
6a488035
TO
223 }
224
5bc392e6
EM
225 /**
226 * @param $contactIDs
227 */
00be9182 228 public function contact(&$contactIDs) {
353ffa53 229 $fields = &$this->dbFields('CRM_Contact_DAO_Contact', TRUE);
6a488035
TO
230 $this->table($contactIDs, 'civicrm_contact', $fields, 'id', NULL);
231 }
232
5bc392e6
EM
233 /**
234 * @param $contactIDs
235 */
00be9182 236 public function note(&$contactIDs) {
353ffa53 237 $fields = &$this->dbFields('CRM_Core_DAO_Note', TRUE);
6a488035
TO
238 $this->table($contactIDs, 'civicrm_note', $fields, 'entity_id', "entity_table = 'civicrm_contact'");
239 }
240
5bc392e6
EM
241 /**
242 * @param $contactIDs
243 */
00be9182 244 public function phone(&$contactIDs) {
353ffa53 245 $fields = &$this->dbFields('CRM_Core_DAO_Phone', TRUE);
6a488035
TO
246 $this->table($contactIDs, 'civicrm_phone', $fields, 'contact_id', NULL);
247 }
248
5bc392e6
EM
249 /**
250 * @param $contactIDs
251 */
00be9182 252 public function email(&$contactIDs) {
353ffa53 253 $fields = &$this->dbFields('CRM_Core_DAO_Email', TRUE);
6a488035
TO
254 $this->table($contactIDs, 'civicrm_email', $fields, 'contact_id', NULL);
255 }
256
5bc392e6
EM
257 /**
258 * @param $contactIDs
259 */
00be9182 260 public function im(&$contactIDs) {
353ffa53 261 $fields = &$this->dbFields('CRM_Core_DAO_IM', TRUE);
6a488035
TO
262 $this->table($contactIDs, 'civicrm_im', $fields, 'contact_id', NULL);
263 }
264
5bc392e6
EM
265 /**
266 * @param $contactIDs
267 */
00be9182 268 public function website(&$contactIDs) {
353ffa53 269 $fields = &$this->dbFields('CRM_Core_DAO_Website', TRUE);
6a488035
TO
270 $this->table($contactIDs, 'civicrm_website', $fields, 'contact_id', NULL);
271 }
272
5bc392e6
EM
273 /**
274 * @param $contactIDs
275 */
00be9182 276 public function address(&$contactIDs) {
353ffa53 277 $fields = &$this->dbFields('CRM_Core_DAO_Email', TRUE);
6a488035
TO
278 $this->table($contactIDs, 'civicrm_address', $fields, 'contact_id', NULL);
279 }
280
5bc392e6
EM
281 /**
282 * @param $contactIDs
283 */
00be9182 284 public function groupContact(&$contactIDs) {
353ffa53 285 $fields = &$this->dbFields('CRM_Contact_DAO_GroupContact', TRUE);
6a488035
TO
286 $this->table($contactIDs, 'civicrm_group_contact', $fields, 'contact_id', NULL);
287 }
288
5bc392e6 289 /**
50bfb460
SB
290 * @todo support group inheritance
291 *
4f1f1f2a 292 * Parent child group ids are encoded in a text string
50bfb460 293 *
5bc392e6
EM
294 * @param $contactIDs
295 */
00be9182 296 public function group(&$contactIDs) {
6a488035 297 // handle groups only once
be2fb01f 298 static $_groupsHandled = [];
6a488035
TO
299
300 $ids = implode(',', $contactIDs);
301
302 $sql = "
303SELECT DISTINCT group_id
304FROM civicrm_group_contact
305WHERE contact_id IN ( $ids )
306";
307 $dao = CRM_Core_DAO::executeQuery($sql);
be2fb01f 308 $groupIDs = [];
6a488035
TO
309 while ($dao->fetch()) {
310 if (!isset($_groupsHandled[$dao->group_id])) {
311 $groupIDs[] = $dao->group_id;
312 $_groupsHandled[$dao->group_id] = 1;
313 }
314 }
315
353ffa53 316 $fields = &$this->dbFields('CRM_Contact_DAO_Group', TRUE);
6a488035
TO
317 $this->table($groupIDs, 'civicrm_group', $fields, 'id');
318
319 $this->savedSearch($groupIDs);
320 }
321
5bc392e6 322 /**
50bfb460 323 * @todo support search builder and custom saved searches
5bc392e6
EM
324 * @param $groupIDs
325 */
00be9182 326 public function savedSearch(&$groupIDs) {
6a488035
TO
327 if (empty($groupIDs)) {
328 return;
329 }
330
331 $idString = implode(",", $groupIDs);
332 $sql = "
333SELECT s.*
334FROM civicrm_saved_search s
335INNER JOIN civicrm_group g on g.saved_search_id = s.id
336WHERE g.id IN ( $idString )
337";
338
353ffa53 339 $fields = &$this->dbFields('CRM_Contact_DAO_SavedSearch', TRUE);
6a488035
TO
340 $this->sql($sql, 'civicrm_saved_search', $fields);
341 }
342
5bc392e6
EM
343 /**
344 * @param $contactIDs
345 */
00be9182 346 public function entityTag(&$contactIDs) {
353ffa53 347 $fields = &$this->dbFields('CRM_Core_DAO_EntityTag', TRUE);
6a488035
TO
348 $this->table($contactIDs, 'civicrm_entity_tag', $fields, 'entity_id', "entity_table = 'civicrm_contact'");
349 }
350
5bc392e6
EM
351 /**
352 * @param $contactIDs
353 */
00be9182 354 public function tag(&$contactIDs) {
6a488035 355 // handle tags only once
be2fb01f 356 static $_tagsHandled = [];
6a488035
TO
357
358 $ids = implode(',', $contactIDs);
359
360 $sql = "
361SELECT DISTINCT tag_id
362FROM civicrm_entity_tag
363WHERE entity_id IN ( $ids )
364AND entity_table = 'civicrm_contact'
365";
366 $dao = CRM_Core_DAO::executeQuery($sql);
be2fb01f 367 $tagIDs = [];
6a488035
TO
368 while ($dao->fetch()) {
369 if (!isset($_tagsHandled[$dao->tag_id])) {
370 $tagIDs[] = $dao->tag_id;
371 $_tagsHandled[$dao->tag_id] = 1;
372 }
373 }
374
353ffa53 375 $fields = &$this->dbFields('CRM_Core_DAO_Tag', TRUE);
6a488035
TO
376 $this->table($tagIDs, 'civicrm_tag', $fields, 'id');
377 }
378
5bc392e6
EM
379 /**
380 * @param $contactIDs
381 * @param $additionalContacts
382 */
00be9182 383 public function relationship(&$contactIDs, &$additionalContacts) {
6a488035 384 // handle relationships only once
be2fb01f 385 static $_relationshipsHandled = [];
6a488035
TO
386
387 $ids = implode(',', $contactIDs);
388
389 $sql = "(
390 SELECT r.*
391 FROM civicrm_relationship r
392 WHERE r.contact_id_a IN ( $ids )
393) UNION (
394 SELECT r.*
395 FROM civicrm_relationship r
396 WHERE r.contact_id_b IN ( $ids )
397)
398";
399
400 $fields = $this->dbFields('CRM_Contact_DAO_Relationship', TRUE);
353ffa53 401 $dao = &CRM_Core_DAO::executeQuery($sql);
6a488035
TO
402 while ($dao->fetch()) {
403 if (isset($_relationshipsHandled[$dao->id])) {
404 continue;
405 }
406 $_relationshipsHandled[$dao->id] = $dao->id;
407
be2fb01f 408 $relationship = [];
6a488035
TO
409 foreach ($fields as $fld) {
410 if (empty($dao->$fld)) {
411 $relationship[$fld] = NULL;
412 }
413 else {
414 $relationship[$fld] = $dao->$fld;
415 }
416 }
417 $this->appendValue($dao->id, 'civicrm_relationship', $relationship);
418
be2fb01f 419 $this->addAdditionalContacts([
6714d8d2
SL
420 $dao->contact_id_a,
421 $dao->contact_id_b,
422 ],
6a488035
TO
423 $additionalContacts
424 );
425 }
6a488035
TO
426 }
427
5bc392e6
EM
428 /**
429 * @param $contactIDs
430 * @param $additionalContacts
431 */
00be9182 432 public function activity(&$contactIDs, &$additionalContacts) {
be2fb01f 433 static $_activitiesHandled = [];
44f817d4 434 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
9e74e3ce 435 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
436 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
6a488035
TO
437 $ids = implode(',', $contactIDs);
438
42d58fac
PJ
439 // query framing returning all contacts in valid activity
440 $sql = "
441SELECT a.*, ac.id as acID, ac.activity_id, ac.contact_id, ac.record_type_id
442FROM civicrm_activity a
443INNER JOIN civicrm_activity_contact ac ON ac.activity_id = a.id
444WHERE ac.contact_id IN ( $ids )
445 AND (a.activity_type_id != 3 AND a.activity_type_id != 20)
6a488035
TO
446";
447
353ffa53 448 $fields = &$this->dbFields('CRM_Activity_DAO_Activity', TRUE);
6a488035 449
353ffa53 450 $dao = &CRM_Core_DAO::executeQuery($sql);
6a488035 451 while ($dao->fetch()) {
42d58fac 452 // adding source, target and assignee contacts in additional contacts array
be2fb01f 453 $this->addAdditionalContacts([$dao->contact_id],
42d58fac
PJ
454 $additionalContacts
455 );
456
457 // append values of activity contacts
be2fb01f 458 $activityContacts = [
42d58fac
PJ
459 'id' => $dao->acID,
460 'contact_id' => $dao->contact_id,
461 'activity_id' => $dao->activity_id,
21dfd5f5 462 'record_type_id' => $dao->record_type_id,
be2fb01f 463 ];
42d58fac
PJ
464 $this->appendValue($dao->acID, 'civicrm_activity_contact', $activityContacts);
465
6a488035
TO
466 if (isset($_activitiesHandled[$dao->id])) {
467 continue;
468 }
469 $_activitiesHandled[$dao->id] = $dao->id;
6a488035 470
be2fb01f 471 $activity = [];
6a488035
TO
472 foreach ($fields as $fld) {
473 if (empty($dao->$fld)) {
474 $activity[$fld] = NULL;
475 }
476 else {
477 $activity[$fld] = $dao->$fld;
478 }
479 }
480
42d58fac 481 // append activity value
6a488035 482 $this->appendValue($dao->id, 'civicrm_activity', $activity);
6a488035 483 }
6a488035
TO
484 }
485
5bc392e6 486 /**
100fef9d
CW
487 * @param int $id
488 * @param string $name
5bc392e6
EM
489 * @param $value
490 */
00be9182 491 public function appendValue($id, $name, $value) {
6a488035
TO
492 if (empty($value)) {
493 return;
494 }
495
496 if (!isset($this->_values[$name])) {
be2fb01f 497 $this->_values[$name] = [];
6a488035
TO
498 $this->_values[$name][] = array_keys($value);
499 }
500 $this->_values[$name][] = array_values($value);
501 }
502
5bc392e6 503 /**
c490a46a 504 * @param string $daoName
5bc392e6
EM
505 * @param bool $onlyKeys
506 *
507 * @return array
508 */
00be9182 509 public function dbFields($daoName, $onlyKeys = FALSE) {
be2fb01f 510 static $_fieldsRetrieved = [];
6a488035
TO
511
512 if (!isset($_fieldsRetrieved[$daoName])) {
be2fb01f 513 $_fieldsRetrieved[$daoName] = [];
6a488035 514 $daoFile = str_replace('_',
353ffa53
TO
515 DIRECTORY_SEPARATOR,
516 $daoName
517 ) . '.php';
e7292422 518 include_once $daoFile;
6a488035 519
353ffa53 520 $daoFields = &$daoName::fields();
6a488035
TO
521
522 foreach ($daoFields as $key => & $value) {
be2fb01f 523 $_fieldsRetrieved[$daoName][$value['name']] = [
6a488035
TO
524 'uniqueName' => $key,
525 'type' => $value['type'],
6187cca4 526 'title' => $value['title'] ?? NULL,
be2fb01f 527 ];
6a488035
TO
528 }
529 }
530
531 if ($onlyKeys) {
532 return array_keys($_fieldsRetrieved[$daoName]);
533 }
534 else {
535 return $_fieldsRetrieved[$daoName];
536 }
537 }
538
5bc392e6
EM
539 /**
540 * @param $contactIDs
541 * @param $additionalContacts
542 */
00be9182 543 public function addAdditionalContacts($contactIDs, &$additionalContacts) {
6a488035
TO
544 if (!$this->_discoverContacts) {
545 return;
546 }
547
548 foreach ($contactIDs as $cid) {
549 if ($cid &&
550 !isset($this->_allContactIDs[$cid]) &&
551 !isset($additionalContacts[$cid])
552 ) {
553 $additionalContacts[$cid] = $cid;
554 }
555 }
556 }
557
5bc392e6
EM
558 /**
559 * @param $contactIDs
560 */
00be9182 561 public function export(&$contactIDs) {
353ffa53 562 $chunks = &$this->splitContactIDs($contactIDs);
6a488035 563
be2fb01f 564 $additionalContactIDs = [];
6a488035
TO
565
566 foreach ($chunks as $chunk) {
567 $this->getValues($chunk, $additionalContactIDs);
568 }
569
570 if (!empty($additionalContactIDs)) {
571 $this->_allContactIDs = $this->_allContactIDs + $additionalContactIDs;
572 $this->export($additionalContactIDs);
573 }
574 }
575
5bc392e6 576 /**
100fef9d 577 * @param string $fileName
5bc392e6
EM
578 * @param null $lastExportTime
579 * @param bool $discoverContacts
580 */
7c550ca0 581 public function run(
a3e55d9c
TO
582 $fileName,
583 $lastExportTime = NULL,
584 $discoverContacts = FALSE
6a488035
TO
585 ) {
586 $this->_discoverContacts = $discoverContacts;
587
588 if (!$lastExportTime) {
589 $sql = "
590SELECT id
591FROM civicrm_contact
592";
593 }
594 else {
595 $sql = "(
596SELECT DISTINCT entity_id
597FROM civicrm_log
598WHERE entity_table = 'civicrm_contact'
599AND modified_date >= $lastExportTime
600) UNION (
601SELECT DISTINCT contact_id
602FROM civicrm_subscription_history
603WHERE date >= $lastExportTime
604)
605";
606 }
607
a3e55d9c 608 $dao = &CRM_Core_DAO::executeQuery($sql);
6a488035 609
be2fb01f 610 $contactIDs = [];
6a488035
TO
611 while ($dao->fetch()) {
612 $contactIDs[$dao->id] = $dao->id;
613 }
614
615 $this->_allContactIDs = $contactIDs;
be2fb01f 616 $this->_values = [];
6a488035
TO
617
618 $this->metaData();
619
620 $this->export($contactIDs);
621
622 $json = json_encode($this->_values, JSON_NUMERIC_CHECK);
623 file_put_contents($fileName,
624 $json
625 );
626
627 // print_r( json_decode( $json ) );
628 }
96025800 629
6a488035 630}