Merge pull request #23824 from totten/mixin-refresh
[civicrm-core.git] / tests / phpunit / CiviTest / CiviUnitTestCase.php
CommitLineData
6a488035
TO
1<?php
2/**
3 * File for the CiviUnitTestCase class
4 *
5 * (PHP 5)
6 *
7 * @copyright Copyright CiviCRM LLC (C) 2009
8 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html
9 * GNU Affero General Public License version 3
10 * @package CiviCRM
11 *
12 * This file is part of CiviCRM
13 *
14 * CiviCRM is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Affero General Public License
16 * as published by the Free Software Foundation; either version 3 of
17 * the License, or (at your option) any later version.
18 *
19 * CiviCRM is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Affero General Public License for more details.
23 *
24 * You should have received a copy of the GNU Affero General Public
25 * License along with this program. If not, see
26 * <http://www.gnu.org/licenses/>.
27 */
28
aa7dd30f 29use Civi\Api4\Address;
26076348 30use Civi\Api4\Contribution;
bb97035f
EM
31use Civi\Api4\CustomField;
32use Civi\Api4\CustomGroup;
59c1dcb4 33use Civi\Api4\FinancialType;
f34913f4 34use Civi\Api4\LineItem;
f8f6fc4e 35use Civi\Api4\MembershipType;
87fa0491 36use Civi\Api4\OptionGroup;
aa7dd30f 37use Civi\Api4\Phone;
9203ed4b 38use Civi\Api4\RelationshipType;
1fee3ad2 39use Civi\Payment\System;
87fa0491 40use Civi\Api4\OptionValue;
9db37f9f 41use Civi\Test\Api3DocTrait;
68989e71 42use League\Csv\Reader;
1fee3ad2 43
6a488035
TO
44/**
45 * Include class definitions
46 */
6a488035 47require_once 'api/api.php';
6a488035
TO
48define('API_LATEST_VERSION', 3);
49
50/**
51 * Base class for CiviCRM unit tests
52 *
d67f1f28
TO
53 * This class supports two (mutually-exclusive) techniques for cleaning up test data. Subclasses
54 * may opt for one or neither:
55 *
56 * 1. quickCleanup() is a helper which truncates a series of tables. Call quickCleanup()
57 * as part of setUp() and/or tearDown(). quickCleanup() is thorough - but it can
58 * be cumbersome to use (b/c you must identify the tables to cleanup) and slow to execute.
59 * 2. useTransaction() executes the test inside a transaction. It's easier to use
60 * (because you don't need to identify specific tables), but it doesn't work for tests
61 * which manipulate schema or truncate data -- and could behave inconsistently
62 * for tests which specifically examine DB transactions.
63 *
6a488035 64 * Common functions for unit tests
7cf5bd55 65 *
6a488035
TO
66 * @package CiviCRM
67 */
1ee79258 68class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
6a488035 69
9db37f9f 70 use Api3DocTrait;
1426d341 71 use \Civi\Test\GenericAssertionsTrait;
8c68b9c9 72 use \Civi\Test\DbTestTrait;
a23e13eb 73 use \Civi\Test\ContactTestTrait;
71f1227b 74 use \Civi\Test\MailingTestTrait;
30f5345c 75
6a488035 76 /**
eceb18cc 77 * Database has been initialized.
6a488035 78 *
d51c6add 79 * @var bool
6a488035
TO
80 */
81 private static $dbInit = FALSE;
82
83 /**
eceb18cc 84 * Database connection.
6a488035
TO
85 *
86 * @var PHPUnit_Extensions_Database_DB_IDatabaseConnection
87 */
88 protected $_dbconn;
89
90 /**
eceb18cc 91 * The database name.
6a488035
TO
92 *
93 * @var string
94 */
95 static protected $_dbName;
96
80723927
EM
97 /**
98 * API version in use.
99 *
100 * @var int
101 */
102 protected $_apiversion = 3;
103
0b6f58fa 104 /**
eceb18cc 105 * Track tables we have modified during a test.
7cf5bd55 106 *
39b959db 107 * @var array
0b6f58fa 108 */
e255b57a 109 protected $_tablesToTruncate = [];
0b6f58fa 110
6a488035 111 /**
1d3260ea
SL
112 * @var array
113 * Array of temporary directory names
6a488035
TO
114 */
115 protected $tempDirs;
116
6a488035 117 /**
1d3260ea
SL
118 * @var bool
119 * populateOnce allows to skip db resets in setUp
6a488035
TO
120 *
121 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
122 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
123 * "CHECK RUNS" ONLY!
124 *
125 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
126 *
1d3260ea 127 * @see http://forum.civicrm.org/index.php/topic,18065.0.html
6a488035
TO
128 */
129 public static $populateOnce = FALSE;
130
6a488035 131 /**
1d3260ea
SL
132 * DBResetRequired allows skipping DB reset
133 * in specific test case. If you still need
134 * to reset single test (method) of such case, call
135 * $this->cleanDB() in the first line of this
136 * test (method).
137 * @var bool
6a488035
TO
138 */
139 public $DBResetRequired = TRUE;
140
d67f1f28 141 /**
1d3260ea 142 * @var CRM_Core_Transaction|null
d67f1f28
TO
143 */
144 private $tx = NULL;
145
f9a5aadd 146 /**
147 * Array of IDs created to support the test.
148 *
149 * e.g
150 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
151 *
152 * @var array
153 */
154 protected $ids = [];
155
62e432ac 156 /**
157 * Should financials be checked after the test but before tear down.
158 *
159 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
26076348 160 * are some test data issues and some real bugs currently blocking.
62e432ac 161 *
162 * @var bool
163 */
3b016424 164 protected $isValidateFinancialsOnPostAssert = TRUE;
62e432ac 165
f04b3586 166 /**
167 * Should location types be checked to ensure primary addresses are correctly assigned after each test.
168 *
169 * @var bool
170 */
1a526b68 171 protected $isLocationTypesOnPostAssert = TRUE;
f04b3586 172
5fa9612e
EM
173 /**
174 * Has the test class been verified as 'getsafe'.
175 *
176 * If a class is getsafe it means that where
177 * callApiSuccess is called 'return' is specified or 'return' =>'id'
178 * can be added by that function. This is part of getting away
179 * from open-ended get calls.
180 *
181 * Eventually we want to not be doing these in our test classes & start
182 * to work to not do them in our main code base. Note they mainly
183 * cause issues for activity.get and contact.get as these are where the
184 * too many joins limit is most likely to be hit.
185 *
186 * @var bool
187 */
188 protected $isGetSafe = FALSE;
189
6c466b51 190 /**
2af6f0c0 191 * Class used for hooks during tests.
192 *
193 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
194 *
d4cf4164 195 * $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
2af6f0c0 196 *
1d3260ea 197 * @var \CRM_Utils_Hook_UnitTests
6c466b51 198 */
d4cf4164 199 public $hookClass;
6c466b51
EM
200
201 /**
1d3260ea
SL
202 * @var array
203 * Common values to be re-used multiple times within a class - usually to create the relevant entity
6c466b51 204 */
7cf5bd55 205 protected $_params = [];
6c466b51
EM
206
207 /**
208 * @var CRM_Extension_System
209 */
210 protected $origExtensionSystem;
211
204beda3 212 /**
213 * Array of IDs created during test setup routine.
214 *
215 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
216 *
217 * @var array
218 */
7cf5bd55 219 public $setupIDs = [];
204beda3 220
6a488035 221 /**
eceb18cc 222 * Constructor.
6a488035
TO
223 *
224 * Because we are overriding the parent class constructor, we
225 * need to show the same arguments as exist in the constructor of
226 * PHPUnit_Framework_TestCase, since
227 * PHPUnit_Framework_TestSuite::createTest() creates a
228 * ReflectionClass of the Test class and checks the constructor
229 * of that class to decide how to set up the test.
230 *
e16033b4
TO
231 * @param string $name
232 * @param array $data
233 * @param string $dataName
6a488035 234 */
7cf5bd55 235 public function __construct($name = NULL, array $data = [], $dataName = '') {
6a488035
TO
236 parent::__construct($name, $data, $dataName);
237
238 // we need full error reporting
239 error_reporting(E_ALL & ~E_NOTICE);
240
f3e16511 241 self::$_dbName = self::getDBName();
6a488035 242
6a488035
TO
243 // also load the class loader
244 require_once 'CRM/Core/ClassLoader.php';
245 CRM_Core_ClassLoader::singleton()->register();
a130e045
DG
246 if (function_exists('_civix_phpunit_setUp')) {
247 // FIXME: loosen coupling
6a488035
TO
248 _civix_phpunit_setUp();
249 }
250 }
251
f0be539a
EM
252 /**
253 * Override to run the test and assert its state.
7cf5bd55 254 *
f0be539a
EM
255 * @return mixed
256 * @throws \Exception
257 * @throws \PHPUnit_Framework_IncompleteTest
258 * @throws \PHPUnit_Framework_SkippedTest
259 */
2e90bf37
TO
260 protected function runTest() {
261 try {
262 return parent::runTest();
5896d037
TO
263 }
264 catch (PEAR_Exception $e) {
2e90bf37
TO
265 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
266 throw new Exception(\CRM_Core_Error::formatTextException($e), $e->getCode());
267 }
268 }
269
4cbe18b8
EM
270 /**
271 * @return bool
272 */
00be9182 273 public function requireDBReset() {
6a488035
TO
274 return $this->DBResetRequired;
275 }
276
4cbe18b8
EM
277 /**
278 * @return string
279 */
00be9182 280 public static function getDBName() {
f3e16511 281 static $dbName = NULL;
649d6dd0 282 if ($dbName === NULL) {
f3e16511 283 require_once "DB.php";
58d1e21e
SL
284 $dsn = CRM_Utils_SQL::autoSwitchDSN(CIVICRM_DSN);
285 $dsninfo = DB::parseDSN($dsn);
f3e16511
TO
286 $dbName = $dsninfo['database'];
287 }
6a488035
TO
288 return $dbName;
289 }
290
291 /**
eceb18cc 292 * Create database connection for this instance.
6a488035
TO
293 *
294 * Initialize the test database if it hasn't been initialized
295 *
6a488035
TO
296 */
297 protected function getConnection() {
6a488035
TO
298 if (!self::$dbInit) {
299 $dbName = self::getDBName();
300
301 // install test database
302 echo PHP_EOL . "Installing {$dbName} database" . PHP_EOL;
303
99117745 304 static::_populateDB(FALSE, $this);
6a488035
TO
305
306 self::$dbInit = TRUE;
307 }
bc560b0a 308
6a488035
TO
309 }
310
311 /**
eceb18cc 312 * Required implementation of abstract method.
6a488035
TO
313 */
314 protected function getDataSet() {
315 }
316
99117745
TO
317 /**
318 * @param bool $perClass
319 * @param null $object
7cf5bd55 320 *
a6c01b45
CW
321 * @return bool
322 * TRUE if the populate logic runs; FALSE if it is skipped
99117745
TO
323 */
324 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
8582a9fc 325 if (CIVICRM_UF !== 'UnitTests') {
cdc44255 326 throw new \RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
8582a9fc 327 }
6a488035
TO
328
329 if ($perClass || $object == NULL) {
330 $dbreset = TRUE;
331 }
332 else {
333 $dbreset = $object->requireDBReset();
334 }
335
336 if (self::$populateOnce || !$dbreset) {
99117745 337 return FALSE;
6a488035
TO
338 }
339 self::$populateOnce = NULL;
340
728bbd5b 341 Civi\Test::data()->populate();
be709432
TO
342
343 return TRUE;
6a488035
TO
344 }
345
e26a40f6 346 public static function setUpBeforeClass(): void {
99117745 347 static::_populateDB(TRUE);
6a488035
TO
348
349 // also set this global hack
7cf5bd55 350 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
6a488035
TO
351 }
352
353 /**
eceb18cc 354 * Common setup functions for all unit tests.
6a488035 355 */
b07d2871 356 protected function setUp(): void {
2c40fdaa 357 parent::setUp();
4db17da0
TO
358 $session = CRM_Core_Session::singleton();
359 $session->set('userID', NULL);
360
2d932085
CW
361 $this->_apiversion = 3;
362
6a488035
TO
363 // Use a temporary file for STDIN
364 $GLOBALS['stdin'] = tmpfile();
365 if ($GLOBALS['stdin'] === FALSE) {
366 echo "Couldn't open temporary file\n";
367 exit(1);
368 }
369
370 // Get and save a connection to the database
371 $this->_dbconn = $this->getConnection();
372
373 // reload database before each test
374 // $this->_populateDB();
375
376 // "initialize" CiviCRM to avoid problems when running single tests
377 // FIXME: look at it closer in second stage
378
c0a1f187
TO
379 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
380 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
6a488035
TO
381
382 // disable any left-over test extensions
383 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
384
385 // reset all the caches
386 CRM_Utils_System::flushCache();
387
c0a1f187 388 // initialize the object once db is loaded
7cf5bd55 389 \Civi::$statics = [];
39b959db
SL
390 // ugh, performance
391 $config = CRM_Core_Config::singleton(TRUE, TRUE);
c0a1f187
TO
392
393 // when running unit tests, use mockup user framework
edbcbd96 394 $this->hookClass = CRM_Utils_Hook::singleton();
c0a1f187 395
26684821
TO
396 // Make sure the DB connection is setup properly
397 $config->userSystem->setMySQLTimeZone();
a8de1c22 398 $env = new CRM_Utils_Check_Component_Env();
26684821
TO
399 CRM_Utils_Check::singleton()->assertValid($env->checkMysqlTime());
400
6a488035 401 // clear permissions stub to not check permissions
6a488035
TO
402 $config->userPermissionClass->permissions = NULL;
403
404 //flush component settings
405 CRM_Core_Component::getEnabledComponents(TRUE);
406
88a90442 407 $_REQUEST = $_GET = $_POST = [];
c3d574fc
EM
408 error_reporting(E_ALL);
409
91786f44 410 $this->renameLabels();
e7aa5b6b 411 $this->ensureMySQLMode(['IGNORE_SPACE', 'ERROR_FOR_DIVISION_BY_ZERO', 'STRICT_TRANS_TABLES']);
c8cf43a5 412 putenv('CIVICRM_SMARTY_DEFAULT_ESCAPE=1');
6a488035
TO
413 }
414
0b6f58fa 415 /**
eceb18cc 416 * Read everything from the datasets directory and insert into the db.
0b6f58fa 417 */
e7aa5b6b 418 public function loadAllFixtures(): void {
0b6f58fa
ARW
419 $fixturesDir = __DIR__ . '/../../fixtures';
420
8fee8f1c 421 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
0b6f58fa 422
b01b546f
TO
423 $jsonFiles = glob($fixturesDir . '/*.json');
424 foreach ($jsonFiles as $jsonFixture) {
425 $json = json_decode(file_get_contents($jsonFixture));
426 foreach ($json as $tableName => $vars) {
8fee8f1c 427 if ($tableName === 'civicrm_contact') {
428 CRM_Core_DAO::executeQuery('DELETE c FROM civicrm_contact c LEFT JOIN civicrm_domain d ON d.contact_id = c.id WHERE d.id IS NULL');
429 }
430 else {
431 CRM_Core_DAO::executeQuery("TRUNCATE $tableName");
432 }
433 foreach ($vars as $entity) {
434 $keys = $values = [];
435 foreach ($entity as $key => $value) {
436 $keys[] = $key;
437 $values[] = is_numeric($value) ? $value : "'{$value}'";
438 }
439 CRM_Core_DAO::executeQuery("
440 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
441 );
442 }
443
444 }
0b6f58fa
ARW
445 }
446
8fee8f1c 447 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
0b6f58fa
ARW
448 }
449
08d620bc 450 /**
451 * Load the data that used to be handled by the discontinued dbunit class.
452 *
453 * This could do with further tidy up - the initial priority is simply to get rid of
454 * the dbunity package which is no longer supported.
455 *
456 * @param string $fileName
457 */
458 protected function loadXMLDataSet($fileName) {
459 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
460 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
461 foreach ($xml as $tableName => $element) {
462 if (!empty($element)) {
463 foreach ($element as $row) {
464 $keys = $values = [];
465 if (isset($row['@attributes'])) {
466 foreach ($row['@attributes'] as $key => $value) {
467 $keys[] = $key;
468 $values[] = is_numeric($value) ? $value : "'{$value}'";
469 }
470 }
1d291c60 471 elseif (!empty($row)) {
08d620bc 472 // cos we copied it & it is inconsistent....
473 foreach ($row as $key => $value) {
474 $keys[] = $key;
475 $values[] = is_numeric($value) ? $value : "'{$value}'";
476 }
477 }
478
1d291c60 479 if (!empty($values)) {
480 CRM_Core_DAO::executeQuery("
08d620bc 481 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
1d291c60 482 );
483 }
08d620bc 484 }
485 }
486 }
487 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
488 }
489
5767aa07 490 /**
eceb18cc 491 * Create default domain contacts for the two domains added during test class.
5767aa07
AN
492 * database population.
493 */
3cf02d63 494 public function createDomainContacts(): void {
e50378d4
EM
495 try {
496 $this->organizationCreate(['api.Email.create' => ['email' => 'fixme.domainemail@example.org']]);
497 $this->organizationCreate([
498 'organization_name' => 'Second Domain',
499 'api.Email.create' => ['email' => 'domainemail2@example.org'],
500 'api.Address.create' => [
501 'street_address' => '15 Main St',
502 'location_type_id' => 1,
503 'city' => 'Collinsville',
504 'country_id' => 1228,
505 'state_province_id' => 1003,
506 'postal_code' => 6022,
507 ],
508 ]);
509 OptionValue::replace(FALSE)->addWhere(
510 'option_group_id:name', '=', 'from_email_address'
511 )->setDefaults([
512 'is_default' => 1,
513 'name' => '"FIXME" <info@EXAMPLE.ORG>',
514 'label' => '"FIXME" <info@EXAMPLE.ORG>',
515 ])->setRecords([['domain_id' => 1], ['domain_id' => 2]])->execute();
516 }
517 catch (API_Exception $e) {
518 $this->fail('failed to re-instate domain contacts ' . $e->getMessage());
519 }
5767aa07
AN
520 }
521
6a488035 522 /**
eceb18cc 523 * Common teardown functions for all unit tests.
6a488035 524 */
5d345864 525 protected function tearDown(): void {
2d932085 526 $this->_apiversion = 3;
91786f44 527 $this->resetLabels();
2d932085 528
6a488035 529 error_reporting(E_ALL & ~E_NOTICE);
6c466b51 530 CRM_Utils_Hook::singleton()->reset();
36a29dc0
TO
531 if ($this->hookClass) {
532 $this->hookClass->reset();
533 }
cbe41575 534 CRM_Core_Session::singleton()->reset(1);
d67f1f28
TO
535
536 if ($this->tx) {
537 $this->tx->rollback()->commit();
538 $this->tx = NULL;
539
540 CRM_Core_Transaction::forceRollbackIfEnabled();
541 \Civi\Core\Transaction\Manager::singleton(TRUE);
5896d037
TO
542 }
543 else {
d67f1f28
TO
544 CRM_Core_Transaction::forceRollbackIfEnabled();
545 \Civi\Core\Transaction\Manager::singleton(TRUE);
546
3cf02d63 547 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match', 'civicrm_email', 'civicrm_address'];
d67f1f28 548 $this->quickCleanup($tablesToTruncate);
a335f6b2 549 $this->createDomainContacts();
d67f1f28
TO
550 }
551
6a488035
TO
552 $this->cleanTempDirs();
553 $this->unsetExtensionSystem();
a80606eb 554 $this->assertEquals([], CRM_Core_DAO::$_nullArray);
555 $this->assertEquals(NULL, CRM_Core_DAO::$_nullObject);
edcbcbb0 556 // Ensure the destruct runs by unsetting it. Also, unsetting
557 // classes frees memory as they are not otherwise unset until the
558 // very end.
559 unset($this->mut);
2c40fdaa 560 parent::tearDown();
6a488035
TO
561 }
562
62e432ac 563 /**
564 * CHeck that all tests that have created payments have created them with the right financial entities.
565 *
87fa0491 566 * @throws \API_Exception
62e432ac 567 * @throws \CRM_Core_Exception
568 */
703a1c6a 569 protected function assertPostConditions(): void {
79abc760
YN
570 // Reset to version 3 as not all (e.g payments) work on v4
571 $this->_apiversion = 3;
f04b3586 572 if ($this->isLocationTypesOnPostAssert) {
573 $this->assertLocationValidity();
574 }
87fa0491
EM
575 $this->assertCount(1, OptionGroup::get(FALSE)
576 ->addWhere('name', '=', 'from_email_address')
577 ->execute());
62e432ac 578 if (!$this->isValidateFinancialsOnPostAssert) {
579 return;
580 }
581 $this->validateAllPayments();
582 $this->validateAllContributions();
583 }
584
888dab1e
TO
585 /**
586 * Create a batch of external API calls which can
587 * be executed concurrently.
588 *
0b882a86 589 * ```
888dab1e
TO
590 * $calls = $this->createExternalAPI()
591 * ->addCall('Contact', 'get', ...)
592 * ->addCall('Contact', 'get', ...)
593 * ...
594 * ->run()
595 * ->getResults();
0b882a86 596 * ```
888dab1e
TO
597 *
598 * @return \Civi\API\ExternalBatch
599 * @throws PHPUnit_Framework_SkippedTestError
600 */
601 public function createExternalAPI() {
602 global $civicrm_root;
7cf5bd55 603 $defaultParams = [
888dab1e
TO
604 'version' => $this->_apiversion,
605 'debug' => 1,
7cf5bd55 606 ];
888dab1e
TO
607
608 $calls = new \Civi\API\ExternalBatch($defaultParams);
888dab1e
TO
609
610 if (!$calls->isSupported()) {
611 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
612 }
613
614 return $calls;
615 }
616
fbda92d3 617 /**
618 * Create required data based on $this->entity & $this->params
619 * This is just a way to set up the test data for delete & get functions
620 * so the distinction between set
621 * up & tested functions is clearer
622 *
a6c01b45
CW
623 * @return array
624 * api Result
fbda92d3 625 */
5896d037 626 public function createTestEntity() {
fbda92d3 627 return $entity = $this->callAPISuccess($this->entity, 'create', $this->params);
628 }
629
4cbe18b8 630 /**
100fef9d 631 * @param int $contactTypeId
4cbe18b8
EM
632 *
633 * @throws Exception
634 */
00be9182 635 public function contactTypeDelete($contactTypeId) {
6a488035
TO
636 $result = CRM_Contact_BAO_ContactType::del($contactTypeId);
637 if (!$result) {
638 throw new Exception('Could not delete contact type');
639 }
640 }
641
4cbe18b8
EM
642 /**
643 * @param array $params
644 *
2c2c8771 645 * @return int
4cbe18b8 646 */
7cf5bd55 647 public function membershipTypeCreate($params = []) {
6a488035
TO
648 CRM_Member_PseudoConstant::flush('membershipType');
649 CRM_Core_Config::clearDBCache();
204beda3 650 $this->setupIDs['contact'] = $memberOfOrganization = $this->organizationCreate();
7cf5bd55 651 $params = array_merge([
6a488035
TO
652 'name' => 'General',
653 'duration_unit' => 'year',
654 'duration_interval' => 1,
655 'period_type' => 'rolling',
0090e3d2 656 'member_of_contact_id' => $memberOfOrganization,
6a488035 657 'domain_id' => 1,
4303ea89 658 'financial_type_id' => 2,
6a488035 659 'is_active' => 1,
6a488035 660 'sequential' => 1,
eacf220c 661 'visibility' => 'Public',
7cf5bd55 662 ], $params);
75638074 663
664 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
6a488035 665
6a488035
TO
666 CRM_Member_PseudoConstant::flush('membershipType');
667 CRM_Utils_Cache::singleton()->flush();
6a488035 668
2c2c8771 669 return (int) $result['id'];
6a488035
TO
670 }
671
4cbe18b8 672 /**
c6ede1be 673 * Create membership.
674 *
c490a46a 675 * @param array $params
4cbe18b8 676 *
c6ede1be 677 * @return int
4cbe18b8 678 */
88060045 679 public function contactMembershipCreate(array $params): int {
7cf5bd55 680 $params = array_merge([
6a488035
TO
681 'join_date' => '2007-01-21',
682 'start_date' => '2007-01-21',
683 'end_date' => '2007-12-21',
684 'source' => 'Payment',
5d8b37be 685 'membership_type_id' => 'General',
7cf5bd55 686 ], $params);
5d8b37be 687 if (!is_numeric($params['membership_type_id'])) {
7cf5bd55 688 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
c6ede1be 689 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
7cf5bd55 690 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
6a488035
TO
691 }
692 }
693
f6722559 694 $result = $this->callAPISuccess('Membership', 'create', $params);
6a488035
TO
695 return $result['id'];
696 }
697
698 /**
eceb18cc 699 * Delete Membership Type.
6a488035 700 *
c490a46a 701 * @param array $params
6a488035 702 */
00be9182 703 public function membershipTypeDelete($params) {
cab024d4 704 $this->callAPISuccess('MembershipType', 'Delete', $params);
6a488035
TO
705 }
706
4cbe18b8 707 /**
100fef9d 708 * @param int $membershipID
4cbe18b8 709 */
00be9182 710 public function membershipDelete($membershipID) {
7cf5bd55 711 $deleteParams = ['id' => $membershipID];
f6722559 712 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
6a488035
TO
713 }
714
4cbe18b8
EM
715 /**
716 * @param string $name
717 *
718 * @return mixed
719 */
00be9182 720 public function membershipStatusCreate($name = 'test member status') {
6a488035
TO
721 $params['name'] = $name;
722 $params['start_event'] = 'start_date';
723 $params['end_event'] = 'end_date';
724 $params['is_current_member'] = 1;
725 $params['is_active'] = 1;
6a488035 726
f6722559 727 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
6a488035 728 CRM_Member_PseudoConstant::flush('membershipStatus');
31c3ac91 729 return (int) $result['id'];
6a488035
TO
730 }
731
4cbe18b8 732 /**
31c3ac91 733 * Delete the given membership status, deleting any memberships of the status first.
734 *
100fef9d 735 * @param int $membershipStatusID
31c3ac91 736 *
737 * @throws \CRM_Core_Exception
4cbe18b8 738 */
aa7dd30f 739 public function membershipStatusDelete(int $membershipStatusID): void {
31c3ac91 740 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
741 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
6a488035
TO
742 }
743
17e615c7
MW
744 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
745 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
746 $renewedMembershipEndDate = new DateTime($membershipEndDate);
0d7e780e
TO
747 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
748 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
749 $renewedMembershipEndDate->add(new DateInterval('P1D'));
17e615c7
MW
750 switch ($durationUnit) {
751 case 'year':
752 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
753 break;
754
755 case 'month':
17e615c7 756 $renewedMembershipEndDate->add(new DateInterval('P1M'));
17e615c7
MW
757 break;
758 }
0d7e780e 759 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
17e615c7
MW
760 return $renewedMembershipEndDate->format('Y-m-d');
761 }
762
4cbe18b8 763 /**
b086dc3a 764 * Create a relationship type.
765 *
4cbe18b8
EM
766 * @param array $params
767 *
b086dc3a 768 * @return int
769 *
770 * @throws \CRM_Core_Exception
4cbe18b8 771 */
7cf5bd55 772 public function relationshipTypeCreate($params = []) {
773 $params = array_merge([
39b959db
SL
774 'name_a_b' => 'Relation 1 for relationship type create',
775 'name_b_a' => 'Relation 2 for relationship type create',
776 'contact_type_a' => 'Individual',
777 'contact_type_b' => 'Organization',
778 'is_reserved' => 1,
779 'is_active' => 1,
7cf5bd55 780 ], $params);
6a488035 781
f6722559 782 $result = $this->callAPISuccess('relationship_type', 'create', $params);
6a488035
TO
783 CRM_Core_PseudoConstant::flush('relationshipType');
784
785 return $result['id'];
786 }
787
788 /**
eceb18cc 789 * Delete Relatinship Type.
6a488035
TO
790 *
791 * @param int $relationshipTypeID
792 */
00be9182 793 public function relationshipTypeDelete($relationshipTypeID) {
6a488035 794 $params['id'] = $relationshipTypeID;
a60c0bc8
SL
795 $check = $this->callAPISuccess('relationship_type', 'get', $params);
796 if (!empty($check['count'])) {
797 $this->callAPISuccess('relationship_type', 'delete', $params);
798 }
6a488035
TO
799 }
800
4cbe18b8 801 /**
100fef9d 802 * @param array $params
4cbe18b8
EM
803 *
804 * @return mixed
b086dc3a 805 * @throws \CRM_Core_Exception
4cbe18b8 806 */
0524826d 807 public function paymentProcessorTypeCreate($params = []) {
808 $params = array_merge([
809 'name' => 'API_Test_PP',
810 'title' => 'API Test Payment Processor',
811 'class_name' => 'CRM_Core_Payment_APITest',
812 'billing_mode' => 'form',
813 'is_recur' => 0,
814 'is_reserved' => 1,
815 'is_active' => 1,
816 ], $params);
817 $result = $this->callAPISuccess('PaymentProcessorType', 'create', $params);
6a488035 818
6a488035
TO
819 CRM_Core_PseudoConstant::flush('paymentProcessorType');
820
821 return $result['id'];
822 }
823
d6944518
EM
824 /**
825 * Create test Authorize.net instance.
826 *
827 * @param array $params
828 *
829 * @return mixed
830 */
7cf5bd55 831 public function paymentProcessorAuthorizeNetCreate($params = []) {
832 $params = array_merge([
d6944518
EM
833 'name' => 'Authorize',
834 'domain_id' => CRM_Core_Config::domainID(),
835 'payment_processor_type_id' => 'AuthNet',
836 'title' => 'AuthNet',
837 'is_active' => 1,
838 'is_default' => 0,
839 'is_test' => 1,
840 'is_recur' => 1,
841 'user_name' => '4y5BfuW7jm',
842 'password' => '4cAmW927n8uLf5J8',
843 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
844 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
845 'class_name' => 'Payment_AuthorizeNet',
846 'billing_mode' => 1,
7cf5bd55 847 ], $params);
d6944518 848
4ecbf127 849 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
dbb17d48 850 return (int) $result['id'];
d6944518
EM
851 }
852
6a488035 853 /**
eceb18cc 854 * Create Participant.
6a488035 855 *
e16033b4
TO
856 * @param array $params
857 * Array of contact id and event id values.
6a488035 858 *
a6c01b45
CW
859 * @return int
860 * $id of participant created
6a488035 861 */
f399fbd8 862 public function participantCreate(array $params = []) {
5896d037 863 if (empty($params['contact_id'])) {
0d6fa54d 864 $this->ids['Contact']['participant'] = $params['contact_id'] = $this->individualCreate();
94692f2d 865 }
5896d037 866 if (empty($params['event_id'])) {
d037332e 867 $event = $this->eventCreate(['end_date' => 20081023, 'registration_end_date' => 20081015]);
94692f2d 868 $params['event_id'] = $event['id'];
869 }
7cf5bd55 870 $defaults = [
6a488035
TO
871 'status_id' => 2,
872 'role_id' => 1,
873 'register_date' => 20070219,
874 'source' => 'Wimbeldon',
875 'event_level' => 'Payment',
94692f2d 876 'debug' => 1,
7cf5bd55 877 ];
6a488035
TO
878
879 $params = array_merge($defaults, $params);
f6722559 880 $result = $this->callAPISuccess('Participant', 'create', $params);
6a488035
TO
881 return $result['id'];
882 }
883
884 /**
eceb18cc 885 * Create Payment Processor.
6a488035 886 *
8950ecdc 887 * @return int
888 * Id Payment Processor
6a488035 889 */
7cf5bd55 890 public function processorCreate($params = []) {
891 $processorParams = [
6a488035
TO
892 'domain_id' => 1,
893 'name' => 'Dummy',
202723ae 894 'title' => 'Dummy',
8950ecdc 895 'payment_processor_type_id' => 'Dummy',
6a488035 896 'financial_account_id' => 12,
a8215a8d 897 'is_test' => TRUE,
6a488035
TO
898 'is_active' => 1,
899 'user_name' => '',
900 'url_site' => 'http://dummy.com',
901 'url_recur' => 'http://dummy.com',
902 'billing_mode' => 1,
8950ecdc 903 'sequential' => 1,
204efedd 904 'payment_instrument_id' => 'Debit Card',
7cf5bd55 905 ];
0f07bb06 906 $processorParams = array_merge($processorParams, $params);
8950ecdc 907 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
908 return $processor['id'];
22e39333 909 }
910
911 /**
912 * Create Payment Processor.
913 *
914 * @param array $processorParams
915 *
0193ebdb 916 * @return \CRM_Core_Payment_Dummy
39b959db 917 * Instance of Dummy Payment Processor
31eddd0d 918 *
919 * @throws \CiviCRM_API3_Exception
22e39333 920 */
31eddd0d 921 public function dummyProcessorCreate($processorParams = []) {
8950ecdc 922 $paymentProcessorID = $this->processorCreate($processorParams);
31eddd0d 923 // For the tests we don't need a live processor, but as core ALWAYS creates a processor in live mode and one in test mode we do need to create both
924 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
925 $processorParams['is_test'] = FALSE;
926 $this->processorCreate($processorParams);
7a3b0ca3 927 return System::singleton()->getById($paymentProcessorID);
6a488035
TO
928 }
929
930 /**
eceb18cc 931 * Create contribution page.
6a488035 932 *
c490a46a 933 * @param array $params
7cf5bd55 934 *
16b10e64
CW
935 * @return array
936 * Array of contribution page
6a488035 937 */
7cf5bd55 938 public function contributionPageCreate($params = []) {
939 $this->_pageParams = array_merge([
6a488035
TO
940 'title' => 'Test Contribution Page',
941 'financial_type_id' => 1,
942 'currency' => 'USD',
943 'financial_account_id' => 1,
6a488035
TO
944 'is_active' => 1,
945 'is_allow_other_amount' => 1,
946 'min_amount' => 10,
947 'max_amount' => 1000,
7cf5bd55 948 ], $params);
23ead872 949 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
6a488035
TO
950 }
951
a646bdd2
SB
952 /**
953 * Create a sample batch.
954 */
955 public function batchCreate() {
956 $params = $this->_params;
957 $params['name'] = $params['title'] = 'Batch_433397';
958 $params['status_id'] = 1;
959 $result = $this->callAPISuccess('batch', 'create', $params);
960 return $result['id'];
961 }
962
6a488035 963 /**
eceb18cc 964 * Create Tag.
6a488035 965 *
2a6da8d7 966 * @param array $params
7cf5bd55 967 *
a6c01b45
CW
968 * @return array
969 * result of created tag
6a488035 970 */
7cf5bd55 971 public function tagCreate($params = []) {
972 $defaults = [
fb32de45 973 'name' => 'New Tag3',
974 'description' => 'This is description for Our New Tag ',
975 'domain_id' => '1',
7cf5bd55 976 ];
fb32de45 977 $params = array_merge($defaults, $params);
6c6e6187 978 $result = $this->callAPISuccess('Tag', 'create', $params);
fb32de45 979 return $result['values'][$result['id']];
6a488035
TO
980 }
981
982 /**
eceb18cc 983 * Delete Tag.
6a488035 984 *
e16033b4
TO
985 * @param int $tagId
986 * Id of the tag to be deleted.
16b10e64
CW
987 *
988 * @return int
6a488035 989 */
00be9182 990 public function tagDelete($tagId) {
6a488035 991 require_once 'api/api.php';
7cf5bd55 992 $params = [
6a488035 993 'tag_id' => $tagId,
7cf5bd55 994 ];
f6722559 995 $result = $this->callAPISuccess('Tag', 'delete', $params);
6a488035
TO
996 return $result['id'];
997 }
998
999 /**
1000 * Add entity(s) to the tag
1001 *
e16033b4 1002 * @param array $params
77b97be7
EM
1003 *
1004 * @return bool
6a488035 1005 */
00be9182 1006 public function entityTagAdd($params) {
f6722559 1007 $result = $this->callAPISuccess('entity_tag', 'create', $params);
6a488035
TO
1008 return TRUE;
1009 }
1010
1011 /**
5210fa35 1012 * Create pledge.
6a488035 1013 *
5210fa35 1014 * @param array $params
1015 * Parameters.
77b97be7 1016 *
a6c01b45 1017 * @return int
5210fa35 1018 * id of created pledge
6a488035 1019 */
aa7dd30f 1020 public function pledgeCreate($params): int {
7cf5bd55 1021 $params = array_merge([
6a488035
TO
1022 'pledge_create_date' => date('Ymd'),
1023 'start_date' => date('Ymd'),
1024 'scheduled_date' => date('Ymd'),
1025 'amount' => 100.00,
1026 'pledge_status_id' => '2',
1027 'financial_type_id' => '1',
1028 'pledge_original_installment_amount' => 20,
1029 'frequency_interval' => 5,
1030 'frequency_unit' => 'year',
1031 'frequency_day' => 15,
1032 'installments' => 5,
7cf5bd55 1033 ],
1034 $params);
6a488035 1035
f6722559 1036 $result = $this->callAPISuccess('Pledge', 'create', $params);
6a488035
TO
1037 return $result['id'];
1038 }
1039
1040 /**
eceb18cc 1041 * Delete contribution.
6a488035 1042 *
c490a46a 1043 * @param int $pledgeId
eca28463 1044 *
1045 * @throws \CRM_Core_Exception
6a488035 1046 */
00be9182 1047 public function pledgeDelete($pledgeId) {
7cf5bd55 1048 $params = [
6a488035 1049 'pledge_id' => $pledgeId,
7cf5bd55 1050 ];
f6722559 1051 $this->callAPISuccess('Pledge', 'delete', $params);
6a488035
TO
1052 }
1053
1054 /**
eceb18cc 1055 * Create contribution.
6a488035 1056 *
c3a3074f 1057 * @param array $params
1058 * Array of parameters.
16b10e64 1059 *
a6c01b45
CW
1060 * @return int
1061 * id of created contribution
6a488035 1062 */
f34913f4 1063 public function contributionCreate(array $params): int {
7cf5bd55 1064 $params = array_merge([
6a488035 1065 'domain_id' => 1,
6a488035
TO
1066 'receive_date' => date('Ymd'),
1067 'total_amount' => 100.00,
69fccd8f 1068 'fee_amount' => 5.00,
78ab0ca4 1069 'financial_type_id' => 1,
1070 'payment_instrument_id' => 1,
6a488035 1071 'non_deductible_amount' => 10.00,
6a488035 1072 'source' => 'SSF',
6a488035 1073 'contribution_status_id' => 1,
7cf5bd55 1074 ], $params);
6a488035 1075
f6722559 1076 $result = $this->callAPISuccess('contribution', 'create', $params);
6a488035
TO
1077 return $result['id'];
1078 }
1079
6a488035 1080 /**
eceb18cc 1081 * Delete contribution.
6a488035
TO
1082 *
1083 * @param int $contributionId
8deefe64
EM
1084 *
1085 * @return array|int
b086dc3a 1086 * @throws \CRM_Core_Exception
6a488035 1087 */
00be9182 1088 public function contributionDelete($contributionId) {
7cf5bd55 1089 $params = [
6a488035 1090 'contribution_id' => $contributionId,
7cf5bd55 1091 ];
f6722559 1092 $result = $this->callAPISuccess('contribution', 'delete', $params);
6a488035
TO
1093 return $result;
1094 }
1095
1096 /**
eceb18cc 1097 * Create an Event.
6a488035 1098 *
e16033b4
TO
1099 * @param array $params
1100 * Name-value pair for an event.
6a488035 1101 *
a6c01b45 1102 * @return array
6a488035 1103 */
f399fbd8 1104 public function eventCreate(array $params = []): array {
6a488035
TO
1105 // if no contact was passed, make up a dummy event creator
1106 if (!isset($params['contact_id'])) {
7cf5bd55 1107 $params['contact_id'] = $this->_contactCreate([
6a488035
TO
1108 'contact_type' => 'Individual',
1109 'first_name' => 'Event',
1110 'last_name' => 'Creator',
7cf5bd55 1111 ]);
6a488035
TO
1112 }
1113
1114 // set defaults for missing params
7cf5bd55 1115 $params = array_merge([
6a488035
TO
1116 'title' => 'Annual CiviCRM meet',
1117 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
f399fbd8 1118 'description' => 'This event is intended to give brief idea about progress of CiviCRM and giving solutions to common user issues',
6a488035
TO
1119 'event_type_id' => 1,
1120 'is_public' => 1,
1121 'start_date' => 20081021,
d037332e 1122 'end_date' => '+ 1 month',
6a488035
TO
1123 'is_online_registration' => 1,
1124 'registration_start_date' => 20080601,
d037332e 1125 'registration_end_date' => '+ 1 month',
6a488035
TO
1126 'max_participants' => 100,
1127 'event_full_text' => 'Sorry! We are already full',
c039f658 1128 'is_monetary' => 0,
6a488035 1129 'is_active' => 1,
6a488035 1130 'is_show_location' => 0,
e579af79 1131 'is_email_confirm' => 1,
7cf5bd55 1132 ], $params);
6a488035 1133
f399fbd8
EM
1134 $event = $this->callAPISuccess('Event', 'create', $params);
1135 $this->ids['event'][] = $event['id'];
1136 return $event;
6a488035
TO
1137 }
1138
f660d2ad 1139 /**
1140 * Create a paid event.
1141 *
1142 * @param array $params
1143 *
2d6f64f4 1144 * @param array $options
1145 *
beac0c97 1146 * @param string $key
1147 * Index for storing event ID in ids array.
1148 *
f660d2ad 1149 * @return array
3fc37a30 1150 *
1151 * @throws \CRM_Core_Exception
f660d2ad 1152 */
beac0c97 1153 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
57a7aa8a 1154 $params['is_monetary'] = TRUE;
f660d2ad 1155 $event = $this->eventCreate($params);
57a7aa8a 1156 $this->ids['Event'][$key] = (int) $event['id'];
1157 $this->ids['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1158 CRM_Price_BAO_PriceSet::addTo('civicrm_event', $event['id'], $this->ids['PriceSet'][$key]);
1159 $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($this->ids['PriceSet'][$key], TRUE, FALSE);
1160 $priceSet = $priceSet[$this->ids['PriceSet'][$key]] ?? NULL;
849ac823 1161 $this->eventFeeBlock = $priceSet['fields'] ?? NULL;
f660d2ad 1162 return $event;
1163 }
1164
6a488035 1165 /**
eceb18cc 1166 * Delete event.
6a488035 1167 *
e16033b4
TO
1168 * @param int $id
1169 * ID of the event.
8deefe64
EM
1170 *
1171 * @return array|int
6a488035 1172 */
00be9182 1173 public function eventDelete($id) {
7cf5bd55 1174 $params = [
6a488035 1175 'event_id' => $id,
7cf5bd55 1176 ];
8cba5814 1177 return $this->callAPISuccess('event', 'delete', $params);
6a488035
TO
1178 }
1179
1180 /**
eceb18cc 1181 * Delete participant.
6a488035
TO
1182 *
1183 * @param int $participantID
8deefe64
EM
1184 *
1185 * @return array|int
6a488035 1186 */
00be9182 1187 public function participantDelete($participantID) {
7cf5bd55 1188 $params = [
6a488035 1189 'id' => $participantID,
7cf5bd55 1190 ];
a60c0bc8
SL
1191 $check = $this->callAPISuccess('Participant', 'get', $params);
1192 if ($check['count'] > 0) {
1193 return $this->callAPISuccess('Participant', 'delete', $params);
1194 }
6a488035
TO
1195 }
1196
1197 /**
eceb18cc 1198 * Create participant payment.
6a488035 1199 *
100fef9d
CW
1200 * @param int $participantID
1201 * @param int $contributionID
7cf5bd55 1202 *
a6c01b45
CW
1203 * @return int
1204 * $id of created payment
6a488035 1205 */
00be9182 1206 public function participantPaymentCreate($participantID, $contributionID = NULL) {
6a488035 1207 //Create Participant Payment record With Values
7cf5bd55 1208 $params = [
6a488035
TO
1209 'participant_id' => $participantID,
1210 'contribution_id' => $contributionID,
7cf5bd55 1211 ];
6a488035 1212
f6722559 1213 $result = $this->callAPISuccess('participant_payment', 'create', $params);
6a488035
TO
1214 return $result['id'];
1215 }
1216
1217 /**
eceb18cc 1218 * Delete participant payment.
6a488035
TO
1219 *
1220 * @param int $paymentID
1221 */
00be9182 1222 public function participantPaymentDelete($paymentID) {
7cf5bd55 1223 $params = [
6a488035 1224 'id' => $paymentID,
7cf5bd55 1225 ];
f6722559 1226 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
6a488035
TO
1227 }
1228
1229 /**
eceb18cc 1230 * Add a Location.
6a488035 1231 *
100fef9d 1232 * @param int $contactID
7cf5bd55 1233 *
a6c01b45
CW
1234 * @return int
1235 * location id of created location
6a488035 1236 */
00be9182 1237 public function locationAdd($contactID) {
7cf5bd55 1238 $address = [
1239 1 => [
6a488035
TO
1240 'location_type' => 'New Location Type',
1241 'is_primary' => 1,
1242 'name' => 'Saint Helier St',
1243 'county' => 'Marin',
86797006 1244 'country' => 'UNITED STATES',
6a488035
TO
1245 'state_province' => 'Michigan',
1246 'supplemental_address_1' => 'Hallmark Ct',
1247 'supplemental_address_2' => 'Jersey Village',
207f62c6 1248 'supplemental_address_3' => 'My Town',
7cf5bd55 1249 ],
1250 ];
6a488035 1251
7cf5bd55 1252 $params = [
6a488035
TO
1253 'contact_id' => $contactID,
1254 'address' => $address,
6a488035
TO
1255 'location_format' => '2.0',
1256 'location_type' => 'New Location Type',
7cf5bd55 1257 ];
6a488035 1258
f6722559 1259 $result = $this->callAPISuccess('Location', 'create', $params);
6a488035
TO
1260 return $result;
1261 }
1262
1263 /**
eceb18cc 1264 * Delete Locations of contact.
6a488035 1265 *
e16033b4
TO
1266 * @param array $params
1267 * Parameters.
6a488035 1268 */
00be9182 1269 public function locationDelete($params) {
c490a46a 1270 $this->callAPISuccess('Location', 'delete', $params);
6a488035
TO
1271 }
1272
1273 /**
eceb18cc 1274 * Add a Location Type.
6a488035 1275 *
100fef9d 1276 * @param array $params
7cf5bd55 1277 *
16b10e64
CW
1278 * @return CRM_Core_DAO_LocationType
1279 * location id of created location
6a488035 1280 */
00be9182 1281 public function locationTypeCreate($params = NULL) {
6a488035 1282 if ($params === NULL) {
7cf5bd55 1283 $params = [
6a488035
TO
1284 'name' => 'New Location Type',
1285 'vcard_name' => 'New Location Type',
1286 'description' => 'Location Type for Delete',
1287 'is_active' => 1,
7cf5bd55 1288 ];
6a488035
TO
1289 }
1290
6a488035
TO
1291 $locationType = new CRM_Core_DAO_LocationType();
1292 $locationType->copyValues($params);
1293 $locationType->save();
1294 // clear getfields cache
2683ce94 1295 CRM_Core_PseudoConstant::flush();
7cf5bd55 1296 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
d4d66593 1297 return $locationType->id;
6a488035
TO
1298 }
1299
1300 /**
eceb18cc 1301 * Delete a Location Type.
6a488035 1302 *
79d7553f 1303 * @param int $locationTypeId
6a488035 1304 */
00be9182 1305 public function locationTypeDelete($locationTypeId) {
6a488035
TO
1306 $locationType = new CRM_Core_DAO_LocationType();
1307 $locationType->id = $locationTypeId;
1308 $locationType->delete();
1309 }
1310
927f93e2
TM
1311 /**
1312 * Add a Mapping.
1313 *
1314 * @param array $params
7cf5bd55 1315 *
927f93e2
TM
1316 * @return CRM_Core_DAO_Mapping
1317 * Mapping id of created mapping
1318 */
1319 public function mappingCreate($params = NULL) {
1320 if ($params === NULL) {
7cf5bd55 1321 $params = [
927f93e2
TM
1322 'name' => 'Mapping name',
1323 'description' => 'Mapping description',
1324 // 'Export Contact' mapping.
1325 'mapping_type_id' => 7,
7cf5bd55 1326 ];
927f93e2
TM
1327 }
1328
1329 $mapping = new CRM_Core_DAO_Mapping();
1330 $mapping->copyValues($params);
1331 $mapping->save();
1332 // clear getfields cache
1333 CRM_Core_PseudoConstant::flush();
7cf5bd55 1334 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
927f93e2
TM
1335 return $mapping;
1336 }
1337
1338 /**
1339 * Delete a Mapping
1340 *
1341 * @param int $mappingId
1342 */
1343 public function mappingDelete($mappingId) {
1344 $mapping = new CRM_Core_DAO_Mapping();
1345 $mapping->id = $mappingId;
1346 $mapping->delete();
1347 }
1348
5e8daa54 1349 /**
1350 * Prepare class for ACLs.
1351 */
1352 protected function prepareForACLs() {
1353 $config = CRM_Core_Config::singleton();
7cf5bd55 1354 $config->userPermissionClass->permissions = [];
5e8daa54 1355 }
1356
1357 /**
1358 * Reset after ACLs.
1359 */
1360 protected function cleanUpAfterACLs() {
1361 CRM_Utils_Hook::singleton()->reset();
7cf5bd55 1362 $tablesToTruncate = [
5e8daa54 1363 'civicrm_acl',
1364 'civicrm_acl_cache',
1365 'civicrm_acl_entity_role',
1366 'civicrm_acl_contact_cache',
7cf5bd55 1367 ];
5e8daa54 1368 $this->quickCleanup($tablesToTruncate);
1369 $config = CRM_Core_Config::singleton();
1370 unset($config->userPermissionClass->permissions);
1371 }
39b959db 1372
c3137c08 1373 /**
1374 * Create a smart group.
1375 *
1376 * By default it will be a group of households.
1377 *
1378 * @param array $smartGroupParams
1379 * @param array $groupParams
0d38cb6d 1380 * @param string $contactType
1381 *
c3137c08 1382 * @return int
1383 */
0d38cb6d 1384 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
fe38e286 1385 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
c3137c08 1386 $savedSearch = CRM_Contact_BAO_SavedSearch::create($smartGroupParams);
1387
1388 $groupParams['saved_search_id'] = $savedSearch->id;
1389 return $this->groupCreate($groupParams);
1390 }
7811a84b 1391
5c496c74 1392 /**
eceb18cc 1393 * Create a UFField.
7cf5bd55 1394 *
5c496c74 1395 * @param array $params
1396 */
7cf5bd55 1397 public function uFFieldCreate($params = []) {
1398 $params = array_merge([
5c496c74 1399 'uf_group_id' => 1,
1400 'field_name' => 'first_name',
1401 'is_active' => 1,
1402 'is_required' => 1,
1403 'visibility' => 'Public Pages and Listings',
1404 'is_searchable' => '1',
1405 'label' => 'first_name',
1406 'field_type' => 'Individual',
1407 'weight' => 1,
7cf5bd55 1408 ], $params);
5c496c74 1409 $this->callAPISuccess('uf_field', 'create', $params);
1410 }
2a6da8d7 1411
6a488035 1412 /**
eceb18cc 1413 * Add a UF Join Entry.
6a488035 1414 *
100fef9d 1415 * @param array $params
7cf5bd55 1416 *
a6c01b45
CW
1417 * @return int
1418 * $id of created UF Join
6a488035 1419 */
00be9182 1420 public function ufjoinCreate($params = NULL) {
6a488035 1421 if ($params === NULL) {
7cf5bd55 1422 $params = [
6a488035
TO
1423 'is_active' => 1,
1424 'module' => 'CiviEvent',
1425 'entity_table' => 'civicrm_event',
1426 'entity_id' => 3,
1427 'weight' => 1,
1428 'uf_group_id' => 1,
7cf5bd55 1429 ];
6a488035 1430 }
f6722559 1431 $result = $this->callAPISuccess('uf_join', 'create', $params);
6a488035
TO
1432 return $result;
1433 }
1434
c03f1689
EM
1435 /**
1436 * @param array $params
1437 * Optional parameters.
d31d0888 1438 * @param bool $reloadConfig
1439 * While enabling CiviCampaign component, we shouldn't always forcibly
1440 * reload config as this hinder hook call in test environment
c03f1689
EM
1441 *
1442 * @return int
1443 * Campaign ID.
1444 */
7cf5bd55 1445 public function campaignCreate($params = [], $reloadConfig = TRUE) {
d31d0888 1446 $this->enableCiviCampaign($reloadConfig);
7cf5bd55 1447 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
c03f1689
EM
1448 'name' => 'big_campaign',
1449 'title' => 'Campaign',
7cf5bd55 1450 ], $params));
c03f1689
EM
1451 return $campaign['id'];
1452 }
1453
6a488035 1454 /**
eceb18cc 1455 * Create Group for a contact.
6a488035
TO
1456 *
1457 * @param int $contactId
1458 */
00be9182 1459 public function contactGroupCreate($contactId) {
7cf5bd55 1460 $params = [
6a488035
TO
1461 'contact_id.1' => $contactId,
1462 'group_id' => 1,
7cf5bd55 1463 ];
6a488035 1464
f6722559 1465 $this->callAPISuccess('GroupContact', 'Create', $params);
6a488035
TO
1466 }
1467
1468 /**
eceb18cc 1469 * Delete Group for a contact.
6a488035 1470 *
100fef9d 1471 * @param int $contactId
6a488035 1472 */
00be9182 1473 public function contactGroupDelete($contactId) {
7cf5bd55 1474 $params = [
6a488035
TO
1475 'contact_id.1' => $contactId,
1476 'group_id' => 1,
7cf5bd55 1477 ];
0f643fb2 1478 $this->civicrm_api('GroupContact', 'Delete', $params);
6a488035
TO
1479 }
1480
1481 /**
eceb18cc 1482 * Create Activity.
6a488035 1483 *
c490a46a 1484 * @param array $params
7cf5bd55 1485 *
2a6da8d7 1486 * @return array|int
b650fe2c 1487 *
1488 * @throws \CRM_Core_Exception
1489 * @throws \CiviCRM_API3_Exception
6a488035 1490 */
7cf5bd55 1491 public function activityCreate($params = []) {
1492 $params = array_merge([
5d8b37be 1493 'subject' => 'Discussion on warm beer',
1494 'activity_date_time' => date('Ymd'),
da9977bd 1495 'duration' => 90,
5d8b37be 1496 'location' => 'Baker Street',
1497 'details' => 'Lets schedule a meeting',
1498 'status_id' => 1,
3af8de9f 1499 'activity_type_id' => 'Meeting',
7cf5bd55 1500 ], $params);
5d8b37be 1501 if (!isset($params['source_contact_id'])) {
1502 $params['source_contact_id'] = $this->individualCreate();
1503 }
1504 if (!isset($params['target_contact_id'])) {
7cf5bd55 1505 $params['target_contact_id'] = $this->individualCreate([
6a488035 1506 'first_name' => 'Julia',
573aab99 1507 'last_name' => 'Anderson',
6a488035
TO
1508 'prefix' => 'Ms.',
1509 'email' => 'julia_anderson@civicrm.org',
1510 'contact_type' => 'Individual',
7cf5bd55 1511 ]);
5d8b37be 1512 }
1513 if (!isset($params['assignee_contact_id'])) {
1514 $params['assignee_contact_id'] = $params['target_contact_id'];
6a488035
TO
1515 }
1516
2d932085 1517 $result = civicrm_api3('Activity', 'create', $params);
6a488035 1518
5d8b37be 1519 $result['target_contact_id'] = $params['target_contact_id'];
1520 $result['assignee_contact_id'] = $params['assignee_contact_id'];
6a488035
TO
1521 return $result;
1522 }
1523
1524 /**
eceb18cc 1525 * Create an activity type.
6a488035 1526 *
e16033b4
TO
1527 * @param array $params
1528 * Parameters.
7cf5bd55 1529 *
f0be539a 1530 * @return array
6a488035 1531 */
00be9182 1532 public function activityTypeCreate($params) {
f0be539a 1533 return $this->callAPISuccess('ActivityType', 'create', $params);
79d7553f 1534 }
6a488035
TO
1535
1536 /**
eceb18cc 1537 * Delete activity type.
6a488035 1538 *
e16033b4
TO
1539 * @param int $activityTypeId
1540 * Id of the activity type.
7cf5bd55 1541 *
f0be539a 1542 * @return array
6a488035 1543 */
00be9182 1544 public function activityTypeDelete($activityTypeId) {
6a488035 1545 $params['activity_type_id'] = $activityTypeId;
f0be539a 1546 return $this->callAPISuccess('ActivityType', 'delete', $params);
79d7553f 1547 }
6a488035
TO
1548
1549 /**
eceb18cc 1550 * Create custom group.
6a488035 1551 *
2a6da8d7 1552 * @param array $params
7cf5bd55 1553 *
27dd6252 1554 * @return array
6a488035 1555 */
7cf5bd55 1556 public function customGroupCreate($params = []) {
1557 $defaults = [
f6722559 1558 'title' => 'new custom group',
1559 'extends' => 'Contact',
1560 'domain_id' => 1,
1561 'style' => 'Inline',
1562 'is_active' => 1,
7cf5bd55 1563 ];
6a488035 1564
f6722559 1565 $params = array_merge($defaults, $params);
1566
f6722559 1567 return $this->callAPISuccess('custom_group', 'create', $params);
6a488035
TO
1568 }
1569
1570 /**
100fef9d 1571 * Existing function doesn't allow params to be over-ridden so need a new one
6a488035 1572 * this one allows you to only pass in the params you want to change
7cf5bd55 1573 *
1e1fdcf6 1574 * @param array $params
7cf5bd55 1575 *
1e1fdcf6 1576 * @return array|int
6a488035 1577 */
7cf5bd55 1578 public function CustomGroupCreateByParams($params = []) {
1579 $defaults = [
6a488035
TO
1580 'title' => "API Custom Group",
1581 'extends' => 'Contact',
1582 'domain_id' => 1,
1583 'style' => 'Inline',
1584 'is_active' => 1,
7cf5bd55 1585 ];
6a488035 1586 $params = array_merge($defaults, $params);
f6722559 1587 return $this->callAPISuccess('custom_group', 'create', $params);
6a488035
TO
1588 }
1589
1590 /**
eceb18cc 1591 * Create custom group with multi fields.
7cf5bd55 1592 *
1e1fdcf6 1593 * @param array $params
7cf5bd55 1594 *
1e1fdcf6 1595 * @return array|int
6a488035 1596 */
7cf5bd55 1597 public function CustomGroupMultipleCreateByParams($params = []) {
1598 $defaults = [
6a488035
TO
1599 'style' => 'Tab',
1600 'is_multiple' => 1,
7cf5bd55 1601 ];
6a488035 1602 $params = array_merge($defaults, $params);
f6722559 1603 return $this->CustomGroupCreateByParams($params);
6a488035
TO
1604 }
1605
1606 /**
eceb18cc 1607 * Create custom group with multi fields.
7cf5bd55 1608 *
1e1fdcf6 1609 * @param array $params
7cf5bd55 1610 *
1e1fdcf6 1611 * @return array
6a488035 1612 */
7cf5bd55 1613 public function CustomGroupMultipleCreateWithFields($params = []) {
6a488035 1614 // also need to pass on $params['custom_field'] if not set but not in place yet
7cf5bd55 1615 $ids = [];
6a488035
TO
1616 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1617 $ids['custom_group_id'] = $customGroup['id'];
6a488035 1618
7cf5bd55 1619 $customField = $this->customFieldCreate([
92915c55
TO
1620 'custom_group_id' => $ids['custom_group_id'],
1621 'label' => 'field_1' . $ids['custom_group_id'],
e525d6af 1622 'in_selector' => 1,
7cf5bd55 1623 ]);
6a488035
TO
1624
1625 $ids['custom_field_id'][] = $customField['id'];
f6722559 1626
7cf5bd55 1627 $customField = $this->customFieldCreate([
92915c55
TO
1628 'custom_group_id' => $ids['custom_group_id'],
1629 'default_value' => '',
1630 'label' => 'field_2' . $ids['custom_group_id'],
e525d6af 1631 'in_selector' => 1,
7cf5bd55 1632 ]);
6a488035 1633 $ids['custom_field_id'][] = $customField['id'];
f6722559 1634
7cf5bd55 1635 $customField = $this->customFieldCreate([
92915c55
TO
1636 'custom_group_id' => $ids['custom_group_id'],
1637 'default_value' => '',
1638 'label' => 'field_3' . $ids['custom_group_id'],
e525d6af 1639 'in_selector' => 1,
7cf5bd55 1640 ]);
6a488035 1641 $ids['custom_field_id'][] = $customField['id'];
f6722559 1642
6a488035
TO
1643 return $ids;
1644 }
1645
1646 /**
1647 * Create a custom group with a single text custom field. See
1648 * participant:testCreateWithCustom for how to use this
1649 *
e16033b4
TO
1650 * @param string $function
1651 * __FUNCTION__.
5a4f6742
CW
1652 * @param string $filename
1653 * $file __FILE__.
6a488035 1654 *
a6c01b45
CW
1655 * @return array
1656 * ids of created objects
6a488035 1657 */
00be9182 1658 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
7cf5bd55 1659 $params = ['title' => $function];
6a488035 1660 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
6c6e6187 1661 $params['extends'] = $entity ? $entity : 'Contact';
e255b57a 1662 $customGroup = $this->customGroupCreate($params);
7cf5bd55 1663 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
80085473 1664 CRM_Core_PseudoConstant::flush();
6a488035 1665
7cf5bd55 1666 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
6a488035
TO
1667 }
1668
d215c0e1
MH
1669 /**
1670 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1671 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1672 *
1673 * @param string $function
1674 * __FUNCTION__.
1675 * @param string $filename
1676 * $file __FILE__.
1677 *
1678 * @return array
1679 * ids of created objects
1680 */
1681 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
7cf5bd55 1682 $params = ['title' => $function];
d215c0e1
MH
1683 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1684 $params['extends'] = $entity ? $entity : 'Contact';
e255b57a 1685 $customGroup = $this->customGroupCreate($params);
7cf5bd55 1686 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
d215c0e1
MH
1687 CRM_Core_PseudoConstant::flush();
1688 $options = [
1689 'defaultValue' => 'Default Value',
1690 'lowercasevalue' => 'Lowercase Value',
1691 1 => 'Integer Value',
08703635 1692 'NULL' => 'NULL',
d215c0e1
MH
1693 ];
1694 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1695 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1696 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1697 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1698 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1699 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1700 foreach ($options as $option_value => $option_label) {
1701 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1702 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1703 }
1704
7cf5bd55 1705 return [
1706 'custom_group_id' => $customGroup['id'],
1707 'custom_field_id' => $customField['id'],
1708 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1709 'custom_field_group_options' => $options,
1710 ];
d215c0e1
MH
1711 }
1712
6a488035 1713 /**
eceb18cc 1714 * Delete custom group.
6a488035 1715 *
8deefe64
EM
1716 * @param int $customGroupID
1717 *
1718 * @return array|int
6a488035 1719 */
00be9182 1720 public function customGroupDelete($customGroupID) {
6a488035 1721 $params['id'] = $customGroupID;
f6722559 1722 return $this->callAPISuccess('custom_group', 'delete', $params);
6a488035
TO
1723 }
1724
1725 /**
eceb18cc 1726 * Create custom field.
6a488035 1727 *
e16033b4
TO
1728 * @param array $params
1729 * (custom_group_id) is required.
7cf5bd55 1730 *
507d3b64 1731 * @return array
6a488035 1732 */
00be9182 1733 public function customFieldCreate($params) {
7cf5bd55 1734 $params = array_merge([
b422b715 1735 'label' => 'Custom Field',
6a488035
TO
1736 'data_type' => 'String',
1737 'html_type' => 'Text',
1738 'is_searchable' => 1,
1739 'is_active' => 1,
5c496c74 1740 'default_value' => 'defaultValue',
7cf5bd55 1741 ], $params);
6a488035 1742
5c496c74 1743 $result = $this->callAPISuccess('custom_field', 'create', $params);
507d3b64
EM
1744 // these 2 functions are called with force to flush static caches
1745 CRM_Core_BAO_CustomField::getTableColumnGroup($result['id'], 1);
1746 CRM_Core_Component::getEnabledComponents(1);
1747 return $result;
6a488035
TO
1748 }
1749
1750 /**
eceb18cc 1751 * Delete custom field.
6a488035
TO
1752 *
1753 * @param int $customFieldID
8deefe64
EM
1754 *
1755 * @return array|int
6a488035 1756 */
00be9182 1757 public function customFieldDelete($customFieldID) {
6a488035
TO
1758
1759 $params['id'] = $customFieldID;
f6722559 1760 return $this->callAPISuccess('custom_field', 'delete', $params);
6a488035
TO
1761 }
1762
1763 /**
eceb18cc 1764 * Create note.
6a488035 1765 *
100fef9d 1766 * @param int $cId
7cf5bd55 1767 *
a6c01b45 1768 * @return array
6a488035 1769 */
00be9182 1770 public function noteCreate($cId) {
7cf5bd55 1771 $params = [
6a488035
TO
1772 'entity_table' => 'civicrm_contact',
1773 'entity_id' => $cId,
1774 'note' => 'hello I am testing Note',
1775 'contact_id' => $cId,
1776 'modified_date' => date('Ymd'),
1777 'subject' => 'Test Note',
7cf5bd55 1778 ];
6a488035 1779
f6722559 1780 return $this->callAPISuccess('Note', 'create', $params);
6a488035
TO
1781 }
1782
07fd63f5 1783 /**
eceb18cc 1784 * Enable CiviCampaign Component.
07fd63f5 1785 */
e689a01a 1786 public function enableCiviCampaign(): void {
07fd63f5 1787 CRM_Core_BAO_ConfigSetting::enableComponent('CiviCampaign');
07fd63f5
E
1788 }
1789
6a488035 1790 /**
eceb18cc 1791 * Create custom field with Option Values.
6a488035 1792 *
77b97be7 1793 * @param array $customGroup
e16033b4
TO
1794 * @param string $name
1795 * Name of custom field.
412585fb 1796 * @param array $extraParams
1797 * Additional parameters to pass through.
77b97be7
EM
1798 *
1799 * @return array|int
6a488035 1800 */
7cf5bd55 1801 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1802 $fieldParams = [
6a488035
TO
1803 'custom_group_id' => $customGroup['id'],
1804 'name' => 'test_custom_group',
1805 'label' => 'Country',
1806 'html_type' => 'Select',
1807 'data_type' => 'String',
1808 'weight' => 4,
1809 'is_required' => 1,
1810 'is_searchable' => 0,
1811 'is_active' => 1,
7cf5bd55 1812 ];
6a488035 1813
7cf5bd55 1814 $optionGroup = [
6a488035
TO
1815 'domain_id' => 1,
1816 'name' => 'option_group1',
1817 'label' => 'option_group_label1',
7cf5bd55 1818 ];
6a488035 1819
7cf5bd55 1820 $optionValue = [
1821 'option_label' => ['Label1', 'Label2'],
1822 'option_value' => ['value1', 'value2'],
1823 'option_name' => [$name . '_1', $name . '_2'],
1824 'option_weight' => [1, 2],
1825 'option_status' => [1, 1],
1826 ];
6a488035 1827
412585fb 1828 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
6a488035 1829
f6722559 1830 return $this->callAPISuccess('custom_field', 'create', $params);
6a488035
TO
1831 }
1832
4cbe18b8
EM
1833 /**
1834 * @param $entities
1835 *
1836 * @return bool
1837 */
00be9182 1838 public function confirmEntitiesDeleted($entities) {
6a488035
TO
1839 foreach ($entities as $entity) {
1840
7cf5bd55 1841 $result = $this->callAPISuccess($entity, 'Get', []);
6a488035
TO
1842 if ($result['error'] == 1 || $result['count'] > 0) {
1843 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1844 return TRUE;
1845 }
1846 }
f0be539a 1847 return FALSE;
6a488035
TO
1848 }
1849
4cbe18b8 1850 /**
0fff773f 1851 * Quick clean by emptying tables created for the test.
1852 *
1853 * @param array $tablesToTruncate
4cbe18b8
EM
1854 * @param bool $dropCustomValueTables
1855 */
d706c149 1856 public function quickCleanup(array $tablesToTruncate, $dropCustomValueTables = FALSE): void {
d67f1f28 1857 if ($this->tx) {
d706c149 1858 $this->fail('CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()');
d67f1f28 1859 }
6a488035 1860 if ($dropCustomValueTables) {
8a475c3d 1861 $this->cleanupCustomGroups();
bb97035f 1862 // Reset autoincrement too.
6a488035
TO
1863 $tablesToTruncate[] = 'civicrm_custom_group';
1864 $tablesToTruncate[] = 'civicrm_custom_field';
1865 }
1866
0b6f58fa
ARW
1867 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate, $tablesToTruncate));
1868
d706c149 1869 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 0;');
6a488035
TO
1870 foreach ($tablesToTruncate as $table) {
1871 $sql = "TRUNCATE TABLE $table";
1872 CRM_Core_DAO::executeQuery($sql);
1873 }
d706c149 1874 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 1;');
6a488035
TO
1875 }
1876
b38530f2
EM
1877 /**
1878 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1879 */
d706c149 1880 public function quickCleanUpFinancialEntities(): void {
7cf5bd55 1881 $tablesToTruncate = [
a380f4a0
EM
1882 'civicrm_activity',
1883 'civicrm_activity_contact',
b38530f2 1884 'civicrm_contribution',
39d632fd 1885 'civicrm_contribution_soft',
945f423d 1886 'civicrm_contribution_product',
b38530f2 1887 'civicrm_financial_trxn',
39d632fd 1888 'civicrm_financial_item',
b38530f2
EM
1889 'civicrm_contribution_recur',
1890 'civicrm_line_item',
1891 'civicrm_contribution_page',
1892 'civicrm_payment_processor',
1893 'civicrm_entity_financial_trxn',
1894 'civicrm_membership',
1895 'civicrm_membership_type',
1896 'civicrm_membership_payment',
cab024d4 1897 'civicrm_membership_log',
f9342903 1898 'civicrm_membership_block',
b38530f2
EM
1899 'civicrm_event',
1900 'civicrm_participant',
1901 'civicrm_participant_payment',
1902 'civicrm_pledge',
4fa732d2 1903 'civicrm_pcp_block',
1904 'civicrm_pcp',
01ead735 1905 'civicrm_pledge_block',
294cc627 1906 'civicrm_pledge_payment',
1cf3c2b1 1907 'civicrm_price_set_entity',
108ff21a
EM
1908 'civicrm_price_field_value',
1909 'civicrm_price_field',
7cf5bd55 1910 ];
b38530f2 1911 $this->quickCleanup($tablesToTruncate);
4278b952 1912 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
108ff21a 1913 $this->restoreDefaultPriceSetConfig();
9fbf312f 1914 $this->disableTaxAndInvoicing();
83644f47 1915 $this->setCurrencySeparators(',');
d706c149
EM
1916 try {
1917 FinancialType::delete(FALSE)->addWhere(
1918 'name', 'NOT IN', [
1919 'Donation',
1920 'Member Dues',
1921 'Campaign Contribution',
1922 'Event Fee',
1923 ]
1924 )->execute();
1925 }
1926 catch (API_Exception $e) {
1927 $this->fail('failed to cleanup financial types ' . $e->getMessage());
1928 }
83644f47 1929 CRM_Core_PseudoConstant::flush('taxRates');
7a3b0ca3 1930 System::singleton()->flushProcessors();
bc438a52 1931 // @fixme this parameter is leaking - it should not be defined as a class static
1932 // but for now we just handle in tear down.
1933 CRM_Contribute_BAO_Query::$_contribOrSoftCredit = 'only contribs';
108ff21a
EM
1934 }
1935
5a433c56 1936 /**
1937 * Reset the price set config so results exist.
1938 */
f8f6fc4e 1939 public function restoreDefaultPriceSetConfig(): void {
cdd71d6b 1940 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1941 CRM_Core_DAO::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
108ff21a 1942 CRM_Core_DAO::executeQuery("INSERT INTO `civicrm_price_field` (`id`, `price_set_id`, `name`, `label`, `html_type`, `is_enter_qty`, `help_pre`, `help_post`, `weight`, `is_display_amounts`, `options_per_line`, `is_active`, `is_required`, `active_on`, `expire_on`, `javascript`, `visibility_id`) VALUES (1, 1, 'contribution_amount', 'Contribution Amount', 'Text', 0, NULL, NULL, 1, 1, 1, 1, 1, NULL, NULL, NULL, 1)");
5afce5ad 1943 CRM_Core_DAO::executeQuery("INSERT INTO `civicrm_price_field_value` (`id`, `price_field_id`, `name`, `label`, `description`, `amount`, `count`, `max_value`, `weight`, `membership_type_id`, `membership_num_terms`, `is_default`, `is_active`, `financial_type_id`, `non_deductible_amount`) VALUES (1, 1, 'contribution_amount', 'Contribution Amount', NULL, '1', NULL, NULL, 1, NULL, NULL, 0, 1, 1, 0.00)");
b38530f2 1944 }
39b959db 1945
5a433c56 1946 /**
1947 * Recreate default membership types.
f8f6fc4e
EM
1948 *
1949 * @throws \API_Exception
5a433c56 1950 */
f8f6fc4e
EM
1951 public function restoreMembershipTypes(): void {
1952 MembershipType::delete(FALSE)->addWhere('id', '>', 0)->execute();
1953 $this->quickCleanup(['civicrm_membership_type']);
1954 $this->ensureMembershipPriceSetExists();
1955
1956 MembershipType::save(FALSE)
1957 ->setRecords(
1958 [
1959 [
1960 'name' => 'General',
1961 'description' => 'Regular annual membership.',
1962 'minimum_fee' => 100,
1963 'duration_unit' => 'year',
1964 'duration_interval' => 2,
1965 'period_type' => 'rolling',
1966 'relationship_type_id' => 7,
1967 'relationship_direction' => 'b_a',
1968 'visibility' => 'Public',
1969 'is_active' => 1,
1970 'weight' => 1,
1971 ],
1972 [
1973 'name' => 'Student',
1974 'description' => 'Discount membership for full-time students.',
1975 'minimum_fee' => 50,
1976 'duration_unit' => 1,
1977 'duration_interval' => 'year',
1978 'period_type' => 'rolling',
1979 'visibility' => 'Public',
1980 ],
1981 [
1982 'name' => 'Lifetime',
1983 'description' => 'Lifetime membership.',
1984 'minimum_fee' => 1200.00,
1985 'duration_unit' => 1,
1986 'duration_interval' => 'lifetime',
1987 'period_type' => 'rolling',
1988 'relationship_type_id' => 7,
1989 'relationship_direction' => 'b_a',
1990 'visibility' => 'Admin',
1991 ],
1992 ]
1993 )
1994 ->setDefaults([
1995 'domain_id' => 1,
1996 'member_of_contact_id' => 1,
1997 'financial_type_id' => 2,
1998 ]
1999 )->execute();
5a433c56 2000 }
2001
6a488035
TO
2002 /*
2003 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
2004 * Default behaviour is to also delete the entity
e16033b4 2005 * @param array $params
e4f46be0 2006 * Params array to check against.
e16033b4
TO
2007 * @param int $id
2008 * Id of the entity concerned.
2009 * @param string $entity
2010 * Name of entity concerned (e.g. membership).
2011 * @param bool $delete
2012 * Should the entity be deleted as part of this check.
2013 * @param string $errorText
2014 * Text to print on error.
6a488035 2015 */
39b959db 2016
4cbe18b8 2017 /**
c490a46a 2018 * @param array $params
100fef9d 2019 * @param int $id
4cbe18b8
EM
2020 * @param $entity
2021 * @param int $delete
2022 * @param string $errorText
4cbe18b8 2023 */
fc1fb038 2024 public function getAndCheck(array $params, int $id, $entity, int $delete = 1, string $errorText = ''): void {
6a488035 2025
7cf5bd55 2026 $result = $this->callAPISuccessGetSingle($entity, [
6a488035 2027 'id' => $id,
fc1fb038 2028 'return' => array_keys($params),
7cf5bd55 2029 ]);
6a488035
TO
2030
2031 if ($delete) {
7cf5bd55 2032 $this->callAPISuccess($entity, 'Delete', [
6a488035 2033 'id' => $id,
7cf5bd55 2034 ]);
6a488035 2035 }
7cf5bd55 2036 $dateFields = $keys = $dateTimeFields = [];
2037 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
6a488035
TO
2038 foreach ($fields['values'] as $field => $settings) {
2039 if (array_key_exists($field, $result)) {
e255b57a 2040 $keys[CRM_Utils_Array::value('name', $settings, $field)] = $field;
6a488035
TO
2041 }
2042 else {
e255b57a 2043 $keys[CRM_Utils_Array::value('name', $settings, $field)] = CRM_Utils_Array::value('name', $settings, $field);
6a488035 2044 }
849ac823 2045 $type = $settings['type'] ?? NULL;
fc1fb038 2046 if ($type === CRM_Utils_Type::T_DATE) {
afd404ea 2047 $dateFields[] = $settings['name'];
2048 // we should identify both real names & unique names as dates
fc1fb038 2049 if ($field !== $settings['name']) {
afd404ea 2050 $dateFields[] = $field;
2051 }
2052 }
fc1fb038 2053 if ($type === CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME) {
afd404ea 2054 $dateTimeFields[] = $settings['name'];
2055 // we should identify both real names & unique names as dates
fc1fb038 2056 if ($field !== $settings['name']) {
afd404ea 2057 $dateTimeFields[] = $field;
2058 }
6a488035
TO
2059 }
2060 }
2061
fc1fb038 2062 if (strtolower($entity) === 'contribution') {
6a488035
TO
2063 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
2064 // this is not returned in id format
2065 unset($params['payment_instrument_id']);
2066 $params['contribution_source'] = $params['source'];
2067 unset($params['source']);
2068 }
2069
2070 foreach ($params as $key => $value) {
fc1fb038 2071 if ($key === 'version' || strpos($key, 'api') === 0 || (!array_key_exists($key, $keys) || !array_key_exists($keys[$key], $result))) {
6a488035
TO
2072 continue;
2073 }
fc1fb038 2074 if (in_array($key, $dateFields, TRUE)) {
6a488035
TO
2075 $value = date('Y-m-d', strtotime($value));
2076 $result[$key] = date('Y-m-d', strtotime($result[$key]));
2077 }
fc1fb038 2078 if (in_array($key, $dateTimeFields, TRUE)) {
afd404ea 2079 $value = date('Y-m-d H:i:s', strtotime($value));
a72cec08 2080 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array::value($keys[$key], $result, CRM_Utils_Array::value($key, $result))));
afd404ea 2081 }
2082 $this->assertEquals($value, $result[$keys[$key]], $key . " GetandCheck function determines that for key {$key} value: $value doesn't match " . print_r($result[$keys[$key]], TRUE) . $errorText);
6a488035
TO
2083 }
2084 }
2085
2086 /**
eceb18cc 2087 * Get formatted values in the actual and expected result.
7cf5bd55 2088 *
e16033b4
TO
2089 * @param array $actual
2090 * Actual calculated values.
2091 * @param array $expected
2092 * Expected values.
6a488035 2093 */
00be9182 2094 public function checkArrayEquals(&$actual, &$expected) {
6a488035
TO
2095 self::unsetId($actual);
2096 self::unsetId($expected);
25115a3e 2097 $this->assertEquals($expected, $actual);
6a488035
TO
2098 }
2099
2100 /**
100fef9d 2101 * Unset the key 'id' from the array
7cf5bd55 2102 *
e16033b4
TO
2103 * @param array $unformattedArray
2104 * The array from which the 'id' has to be unset.
6a488035 2105 */
00be9182 2106 public static function unsetId(&$unformattedArray) {
7cf5bd55 2107 $formattedArray = [];
6a488035
TO
2108 if (array_key_exists('id', $unformattedArray)) {
2109 unset($unformattedArray['id']);
2110 }
a7488080 2111 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
6a488035 2112 foreach ($unformattedArray['values'] as $key => $value) {
6c6e6187 2113 if (is_array($value)) {
6a488035
TO
2114 foreach ($value as $k => $v) {
2115 if ($k == 'id') {
2116 unset($value[$k]);
2117 }
2118 }
2119 }
2120 elseif ($key == 'id') {
2121 $unformattedArray[$key];
2122 }
7cf5bd55 2123 $formattedArray = [$value];
6a488035
TO
2124 }
2125 $unformattedArray['values'] = $formattedArray;
2126 }
2127 }
2128
2129 /**
2130 * Helper to enable/disable custom directory support
2131 *
e16033b4
TO
2132 * @param array $customDirs
2133 * With members:.
6a488035
TO
2134 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2135 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2136 */
00be9182 2137 public function customDirectories($customDirs) {
6a488035
TO
2138 $config = CRM_Core_Config::singleton();
2139
2140 if (empty($customDirs['php_path']) || $customDirs['php_path'] === FALSE) {
2141 unset($config->customPHPPathDir);
2142 }
2143 elseif ($customDirs['php_path'] === TRUE) {
2144 $config->customPHPPathDir = dirname(dirname(__FILE__)) . '/custom_directories/php/';
2145 }
2146 else {
2147 $config->customPHPPathDir = $php_path;
2148 }
2149
2150 if (empty($customDirs['template_path']) || $customDirs['template_path'] === FALSE) {
2151 unset($config->customTemplateDir);
2152 }
2153 elseif ($customDirs['template_path'] === TRUE) {
2154 $config->customTemplateDir = dirname(dirname(__FILE__)) . '/custom_directories/templates/';
2155 }
2156 else {
2157 $config->customTemplateDir = $template_path;
2158 }
2159 }
2160
2161 /**
eceb18cc 2162 * Generate a temporary folder.
6a488035 2163 *
2a6da8d7 2164 * @param string $prefix
7cf5bd55 2165 *
a6c01b45 2166 * @return string
6a488035 2167 */
00be9182 2168 public function createTempDir($prefix = 'test-') {
6a488035
TO
2169 $tempDir = CRM_Utils_File::tempdir($prefix);
2170 $this->tempDirs[] = $tempDir;
2171 return $tempDir;
2172 }
2173
00be9182 2174 public function cleanTempDirs() {
6a488035
TO
2175 if (!is_array($this->tempDirs)) {
2176 // fix test errors where this is not set
2177 return;
2178 }
2179 foreach ($this->tempDirs as $tempDir) {
2180 if (is_dir($tempDir)) {
2181 CRM_Utils_File::cleanDir($tempDir, TRUE, FALSE);
2182 }
2183 }
2184 }
2185
2186 /**
eceb18cc 2187 * Temporarily replace the singleton extension with a different one.
7cf5bd55 2188 *
1e1fdcf6 2189 * @param \CRM_Extension_System $system
6a488035 2190 */
00be9182 2191 public function setExtensionSystem(CRM_Extension_System $system) {
6a488035
TO
2192 if ($this->origExtensionSystem == NULL) {
2193 $this->origExtensionSystem = CRM_Extension_System::singleton();
2194 }
2195 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2196 }
2197
00be9182 2198 public function unsetExtensionSystem() {
6a488035
TO
2199 if ($this->origExtensionSystem !== NULL) {
2200 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2201 $this->origExtensionSystem = NULL;
2202 }
2203 }
f17d75bb 2204
076d8c82
TO
2205 /**
2206 * Temporarily alter the settings-metadata to add a mock setting.
2207 *
2208 * WARNING: The setting metadata will disappear on the next cache-clear.
2209 *
2210 * @param $extras
7cf5bd55 2211 *
076d8c82
TO
2212 * @return void
2213 */
00be9182 2214 public function setMockSettingsMetaData($extras) {
5896d037
TO
2215 CRM_Utils_Hook::singleton()
2216 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2217 $metadata = array_merge($metadata, $extras);
2218 });
076d8c82 2219
1209c8a7
CW
2220 Civi::service('settings_manager')->flush();
2221
7cf5bd55 2222 $fields = $this->callAPISuccess('setting', 'getfields', []);
076d8c82
TO
2223 foreach ($extras as $key => $spec) {
2224 $this->assertNotEmpty($spec['title']);
2225 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2226 }
2227 }
2228
4cbe18b8 2229 /**
100fef9d 2230 * @param string $name
4cbe18b8 2231 */
00be9182 2232 public function financialAccountDelete($name) {
f17d75bb
PN
2233 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2234 $financialAccount->name = $name;
5896d037 2235 if ($financialAccount->find(TRUE)) {
f17d75bb
PN
2236 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2237 $entityFinancialType->financial_account_id = $financialAccount->id;
2238 $entityFinancialType->delete();
2239 $financialAccount->delete();
2240 }
2241 }
fb32de45 2242
48e399ac
EM
2243 /**
2244 * Set up an acl allowing contact to see 2 specified groups
c206647d 2245 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
48e399ac 2246 *
c206647d 2247 * You need to have pre-created these groups & created the user e.g
48e399ac
EM
2248 * $this->createLoggedInUser();
2249 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2250 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
ea3ddccf 2251 *
2252 * @param bool $isProfile
48e399ac 2253 */
181f536c 2254 public function setupACL($isProfile = FALSE) {
aaac0e0b
EM
2255 global $_REQUEST;
2256 $_REQUEST = $this->_params;
108ff21a 2257
7cf5bd55 2258 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
2259 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
e5720c45
SL
2260 $ov = new CRM_Core_DAO_OptionValue();
2261 $ov->option_group_id = $optionGroupID;
2262 $ov->value = 55;
2263 if ($ov->find(TRUE)) {
2264 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2265 }
7cf5bd55 2266 $optionValue = $this->callAPISuccess('option_value', 'create', [
5896d037 2267 'option_group_id' => $optionGroupID,
48e399ac
EM
2268 'label' => 'pick me',
2269 'value' => 55,
7cf5bd55 2270 ]);
48e399ac 2271
48e399ac
EM
2272 CRM_Core_DAO::executeQuery("
2273 TRUNCATE civicrm_acl_cache
2274 ");
2275
2276 CRM_Core_DAO::executeQuery("
2277 TRUNCATE civicrm_acl_contact_cache
2278 ");
2279
48e399ac
EM
2280 CRM_Core_DAO::executeQuery("
2281 INSERT INTO civicrm_acl_entity_role (
181f536c 2282 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2283 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
48e399ac
EM
2284 ");
2285
181f536c 2286 if ($isProfile) {
2287 CRM_Core_DAO::executeQuery("
2288 INSERT INTO civicrm_acl (
2289 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2290 )
2291 VALUES (
2292 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2293 );
2294 ");
2295 }
2296 else {
2297 CRM_Core_DAO::executeQuery("
2298 INSERT INTO civicrm_acl (
2299 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2300 )
2301 VALUES (
2302 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2303 );
2304 ");
2305
2306 CRM_Core_DAO::executeQuery("
2307 INSERT INTO civicrm_acl (
2308 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2309 )
2310 VALUES (
2311 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2312 );
2313 ");
181f536c 2314 }
48e399ac 2315
48e399ac 2316 $this->_loggedInUser = CRM_Core_Session::singleton()->get('userID');
7cf5bd55 2317 $this->callAPISuccess('group_contact', 'create', [
48e399ac
EM
2318 'group_id' => $this->_permissionedGroup,
2319 'contact_id' => $this->_loggedInUser,
7cf5bd55 2320 ]);
08a2ea5e 2321
2322 if (!$isProfile) {
08a2ea5e 2323 CRM_ACL_BAO_Cache::resetCache();
08a2ea5e 2324 }
48e399ac
EM
2325 }
2326
cab024d4 2327 /**
100fef9d 2328 * Alter default price set so that the field numbers are not all 1 (hiding errors)
cab024d4 2329 */
00be9182 2330 public function offsetDefaultPriceSet() {
7cf5bd55 2331 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
cab024d4 2332 $firstID = $contributionPriceSet['id'];
7cf5bd55 2333 $this->callAPISuccess('price_set', 'create', [
92915c55
TO
2334 'id' => $contributionPriceSet['id'],
2335 'is_active' => 0,
2336 'name' => 'old',
7cf5bd55 2337 ]);
cab024d4
EM
2338 unset($contributionPriceSet['id']);
2339 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
7cf5bd55 2340 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
92915c55 2341 'price_set_id' => $firstID,
7cf5bd55 2342 'options' => ['limit' => 1],
2343 ]);
cab024d4
EM
2344 unset($priceField['id']);
2345 $priceField['price_set_id'] = $newPriceSet['id'];
2346 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
7cf5bd55 2347 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
92915c55
TO
2348 'price_set_id' => $firstID,
2349 'sequential' => 1,
7cf5bd55 2350 'options' => ['limit' => 1],
2351 ]);
cab024d4
EM
2352
2353 unset($priceFieldValue['id']);
2354 //create some padding to use up ids
2355 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2356 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
7cf5bd55 2357 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
cab024d4
EM
2358 }
2359
4aef704e 2360 /**
eceb18cc 2361 * Create an instance of the paypal processor.
7cf5bd55 2362 *
4aef704e 2363 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2364 * this parent class & we don't have a structure for that yet
2365 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
e4f46be0 2366 * & the best protection against that is the functions this class affords
7cf5bd55 2367 *
1e1fdcf6 2368 * @param array $params
7cf5bd55 2369 *
79d7553f 2370 * @return int $result['id'] payment processor id
4aef704e 2371 */
7cf5bd55 2372 public function paymentProcessorCreate($params = []) {
2373 $params = array_merge([
39b959db
SL
2374 'name' => 'demo',
2375 'domain_id' => CRM_Core_Config::domainID(),
2376 'payment_processor_type_id' => 'PayPal',
2377 'is_active' => 1,
2378 'is_default' => 0,
2379 'is_test' => 1,
2380 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2381 'password' => '1183377788',
2382 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2383 'url_site' => 'https://www.sandbox.paypal.com/',
2384 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2385 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2386 'class_name' => 'Payment_PayPalImpl',
2387 'billing_mode' => 3,
2388 'financial_type_id' => 1,
2389 'financial_account_id' => 12,
2390 // Credit card = 1 so can pass 'by accident'.
2391 'payment_instrument_id' => 'Debit Card',
7cf5bd55 2392 ], $params);
5896d037 2393 if (!is_numeric($params['payment_processor_type_id'])) {
4aef704e 2394 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2395 //here
7cf5bd55 2396 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
4aef704e 2397 'name' => $params['payment_processor_type_id'],
2398 'return' => 'id',
7cf5bd55 2399 ], 'integer');
4aef704e 2400 }
2401 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2402 return $result['id'];
2403 }
a9ac877b 2404
df731f74
EM
2405 /**
2406 * Get the rendered contents from a form.
2407 *
2408 * @param string $formName
2409 *
2410 * @return false|string
2411 */
2412 protected function getRenderedFormContents(string $formName) {
2413 $form = $this->getFormObject($formName);
2414 $form->buildForm();
2415 ob_start();
2416 $form->controller->_actions['display']->perform($form, 'display');
2417 return ob_get_clean();
2418 }
2419
0dbefed3 2420 /**
eceb18cc 2421 * Set up initial recurring payment allowing subsequent IPN payments.
9f68fe61
MW
2422 *
2423 * @param array $recurParams (Optional)
2424 * @param array $contributionParams (Optional)
0dbefed3 2425 */
b17107c3 2426 public function setupRecurringPaymentProcessorTransaction(array $recurParams = [], array $contributionParams = []): void {
a64d6127 2427 $this->ids['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
9f68fe61 2428 $contributionParams = array_merge([
39b959db
SL
2429 'total_amount' => '200',
2430 'invoice_id' => $this->_invoiceID,
2431 'financial_type_id' => 'Donation',
39b959db
SL
2432 'contact_id' => $this->_contactID,
2433 'contribution_page_id' => $this->_contributionPageID,
2434 'payment_processor_id' => $this->_paymentProcessorID,
f03241be 2435 'receive_date' => '2019-07-25 07:34:23',
39b959db 2436 'skipCleanMoney' => TRUE,
a64d6127 2437 'amount_level' => 'expensive',
2438 'campaign_id' => $this->ids['campaign'][0],
2ca321fa 2439 'source' => 'Online Contribution: Page name',
39b959db 2440 ], $contributionParams);
7cf5bd55 2441 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
0dbefed3
EM
2442 'contact_id' => $this->_contactID,
2443 'amount' => 1000,
2444 'sequential' => 1,
2445 'installments' => 5,
2446 'frequency_unit' => 'Month',
2447 'frequency_interval' => 1,
2448 'invoice_id' => $this->_invoiceID,
2449 'contribution_status_id' => 2,
481312d9 2450 'payment_processor_id' => $this->_paymentProcessorID,
2451 // processor provided ID - use contact ID as proxy.
2452 'processor_id' => $this->_contactID,
0dea0c7c 2453 'api.Order.create' => $contributionParams,
2454 ], $recurParams))['values'][0];
0dbefed3 2455 $this->_contributionRecurID = $contributionRecur['id'];
0dea0c7c 2456 $this->_contributionID = $contributionRecur['api.Order.create']['id'];
7aeb7f06 2457 $this->ids['Contribution'][0] = $this->_contributionID;
0dbefed3 2458 }
a86d27fc 2459
a9ac877b 2460 /**
100fef9d 2461 * We don't have a good way to set up a recurring contribution with a membership so let's just do one then alter it
b3c283b4
MW
2462 *
2463 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
7aeb7f06 2464 *
f34913f4 2465 * @throws \API_Exception
a9ac877b 2466 */
f34913f4 2467 public function setupMembershipRecurringPaymentProcessorTransaction($params = []): void {
7cf5bd55 2468 $membershipParams = $recurParams = [];
b3c283b4
MW
2469 if (!empty($params['duration_unit'])) {
2470 $membershipParams['duration_unit'] = $params['duration_unit'];
2471 }
2472 if (!empty($params['frequency_unit'])) {
2473 $recurParams['frequency_unit'] = $params['frequency_unit'];
2474 }
2475
2476 $this->ids['membership_type'] = $this->membershipTypeCreate($membershipParams);
69140e67 2477 //create a contribution so our membership & contribution don't both have id = 1
0dea0c7c 2478 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
7cf5bd55 2479 $this->contributionCreate([
b6b59c64 2480 'contact_id' => $this->_contactID,
2481 'is_test' => 1,
2482 'financial_type_id' => 1,
2483 'invoice_id' => 'abcd',
2484 'trxn_id' => 345,
f03241be 2485 'receive_date' => '2019-07-25 07:34:23',
7cf5bd55 2486 ]);
b6b59c64 2487 }
69140e67 2488
0dea0c7c 2489 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2490 'line_items' => [
2491 [
2492 'line_item' => [
2493 [
0dea0c7c 2494 'label' => 'General',
2495 'qty' => 1,
2496 'unit_price' => 200,
2497 'line_total' => 200,
2498 'financial_type_id' => 1,
f34913f4 2499 'membership_type_id' => $this->ids['membership_type'],
0dea0c7c 2500 ],
2501 ],
f34913f4
EM
2502 'params' => [
2503 'contact_id' => $this->_contactID,
2504 'membership_type_id' => $this->ids['membership_type'],
2505 'source' => 'Payment',
2506 ],
0dea0c7c 2507 ],
2508 ],
7cf5bd55 2509 ]);
f34913f4
EM
2510 $this->ids['membership'] = LineItem::get()
2511 ->addWhere('contribution_id', '=', $this->ids['Contribution'][0])
2512 ->addWhere('entity_table', '=', 'civicrm_membership')
2513 ->addSelect('entity_id')
2514 ->execute()->first()['entity_id'];
a9ac877b 2515 }
6a488035 2516
4cbe18b8
EM
2517 /**
2518 * @param $message
2519 *
2520 * @throws Exception
a9ac877b 2521 */
00be9182 2522 public function CiviUnitTestCase_fatalErrorHandler($message) {
a9ac877b
EM
2523 throw new Exception("{$message['message']}: {$message['code']}");
2524 }
4aef704e 2525
d67f1f28 2526 /**
eceb18cc 2527 * Wrap the entire test case in a transaction.
d67f1f28
TO
2528 *
2529 * Only subsequent DB statements will be wrapped in TX -- this cannot
2530 * retroactively wrap old DB statements. Therefore, it makes sense to
2531 * call this at the beginning of setUp().
2532 *
2533 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2534 * this option does not work with, e.g., custom-data.
2535 *
2536 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2537 * if TRUNCATE or ALTER is called while using a transaction.
2538 *
e16033b4
TO
2539 * @param bool $nest
2540 * Whether to use nesting or reference-counting.
d67f1f28 2541 */
00be9182 2542 public function useTransaction($nest = TRUE) {
d67f1f28
TO
2543 if (!$this->tx) {
2544 $this->tx = new CRM_Core_Transaction($nest);
2545 $this->tx->rollback();
2546 }
2547 }
a335f6b2 2548
b3c30fda 2549 /**
54957108 2550 * Assert the attachment exists.
2551 *
2552 * @param bool $exists
b3c30fda
CW
2553 * @param array $apiResult
2554 */
2555 protected function assertAttachmentExistence($exists, $apiResult) {
2556 $fileId = $apiResult['id'];
2557 $this->assertTrue(is_numeric($fileId));
2558 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
7cf5bd55 2559 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2560 1 => [$fileId, 'Int'],
2561 ]);
2562 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2563 1 => [$fileId, 'Int'],
2564 ]);
b3c30fda
CW
2565 }
2566
299c1530 2567 /**
2568 * Assert 2 sql strings are the same, ignoring double spaces.
2569 *
2570 * @param string $expectedSQL
2571 * @param string $actualSQL
2572 * @param string $message
2573 */
2574 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2575 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2576 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2577 $this->assertEquals($expected, $actual, $message);
2578 }
2579
c039f658 2580 /**
2581 * Create a price set for an event.
2582 *
2583 * @param int $feeTotal
601c7a24 2584 * @param int $minAmt
f660d2ad 2585 * @param string $type
c039f658 2586 *
2d6f64f4 2587 * @param array $options
2588 *
c039f658 2589 * @return int
2590 * Price Set ID.
74531938 2591 * @throws \CRM_Core_Exception
c039f658 2592 */
2d6f64f4 2593 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
c039f658 2594 // creating price set, price field
2595 $paramsSet['title'] = 'Price Set';
2596 $paramsSet['name'] = CRM_Utils_String::titleToVar('Price Set');
2597 $paramsSet['is_active'] = FALSE;
2598 $paramsSet['extends'] = 1;
601c7a24 2599 $paramsSet['min_amount'] = $minAmt;
c039f658 2600
f660d2ad 2601 $priceSet = CRM_Price_BAO_PriceSet::create($paramsSet);
2602 $this->_ids['price_set'] = $priceSet->id;
c039f658 2603
7cf5bd55 2604 $paramsField = [
c039f658 2605 'label' => 'Price Field',
2606 'name' => CRM_Utils_String::titleToVar('Price Field'),
f660d2ad 2607 'html_type' => $type,
c039f658 2608 'price' => $feeTotal,
7cf5bd55 2609 'option_label' => ['1' => 'Price Field'],
2610 'option_value' => ['1' => $feeTotal],
2611 'option_name' => ['1' => $feeTotal],
2612 'option_weight' => ['1' => 1],
2613 'option_amount' => ['1' => 1],
c039f658 2614 'is_display_amounts' => 1,
2615 'weight' => 1,
2616 'options_per_line' => 1,
7cf5bd55 2617 'is_active' => ['1' => 1],
f660d2ad 2618 'price_set_id' => $this->_ids['price_set'],
c039f658 2619 'is_enter_qty' => 1,
8484a5f0 2620 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
7cf5bd55 2621 ];
f660d2ad 2622 if ($type === 'Radio') {
2d6f64f4 2623 foreach ($options as $index => $option) {
2624 $paramsField['is_enter_qty'] = 0;
2625 $optionID = $index + 2;
2626 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2627 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2628 }
2629
f660d2ad 2630 }
74531938 2631 $this->callAPISuccess('PriceField', 'create', $paramsField);
7cf5bd55 2632 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids['price_set']]);
f660d2ad 2633 $this->_ids['price_field'] = array_keys($fields['values']);
7cf5bd55 2634 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids['price_field'][0]]);
f660d2ad 2635 $this->_ids['price_field_value'] = array_keys($fieldValues['values']);
c039f658 2636
f660d2ad 2637 return $this->_ids['price_set'];
c039f658 2638 }
2639
481312d9 2640 /**
2641 * Add a profile to a contribution page.
2642 *
2643 * @param string $name
2644 * @param int $contributionPageID
5d6cf648 2645 * @param string $module
481312d9 2646 */
5d6cf648
JM
2647 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2648 $params = [
481312d9 2649 'uf_group_id' => $name,
5d6cf648 2650 'module' => $module,
481312d9 2651 'entity_table' => 'civicrm_contribution_page',
2652 'entity_id' => $contributionPageID,
2653 'weight' => 1,
5d6cf648
JM
2654 ];
2655 if ($module !== 'CiviContribute') {
2656 $params['module_data'] = [$module => []];
2657 }
2658 $this->callAPISuccess('UFJoin', 'create', $params);
481312d9 2659 }
2660
db62fd2b
PN
2661 /**
2662 * Add participant with contribution
2663 *
2664 * @return array
f3e6da5e 2665 *
2666 * @throws \CRM_Core_Exception
db62fd2b 2667 */
5266bd48 2668 protected function createPartiallyPaidParticipantOrder() {
2669 $orderParams = $this->getParticipantOrderParams();
2670 $orderParams['api.Payment.create'] = ['total_amount' => 150];
f3e6da5e 2671 return $this->callAPISuccess('Order', 'create', $orderParams);
db62fd2b
PN
2672 }
2673
73c0e107 2674 /**
822de62f 2675 * Create price set that includes one price field with two option values.
73c0e107
PN
2676 *
2677 * @param string $component
2678 * @param int $componentId
39b959db 2679 * @param array $priceFieldOptions
73c0e107 2680 *
822de62f 2681 * @return array - the result of API3 PriceFieldValue.get for the new PriceField
73c0e107 2682 */
7cf5bd55 2683 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
5c3d600f
PN
2684 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2685 $paramsSet['name'] = CRM_Utils_String::titleToVar($paramsSet['title']);
73c0e107 2686 $paramsSet['is_active'] = TRUE;
faba82fc 2687 $paramsSet['financial_type_id'] = 'Event Fee';
73c0e107
PN
2688 $paramsSet['extends'] = 1;
2689 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
4df23f2a
EM
2690 if ($componentId) {
2691 CRM_Price_BAO_PriceSet::addTo('civicrm_' . $component, $componentId, $priceSet['id']);
2692 }
7cf5bd55 2693 $paramsField = array_merge([
73c0e107
PN
2694 'label' => 'Price Field',
2695 'name' => CRM_Utils_String::titleToVar('Price Field'),
2696 'html_type' => 'CheckBox',
7cf5bd55 2697 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2698 'option_value' => ['1' => 100, '2' => 200],
2699 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2700 'option_weight' => ['1' => 1, '2' => 2],
2701 'option_amount' => ['1' => 100, '2' => 200],
73c0e107
PN
2702 'is_display_amounts' => 1,
2703 'weight' => 1,
2704 'options_per_line' => 1,
7cf5bd55 2705 'is_active' => ['1' => 1, '2' => 1],
73c0e107
PN
2706 'price_set_id' => $priceSet['id'],
2707 'is_enter_qty' => 1,
8484a5f0 2708 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
7cf5bd55 2709 ], $priceFieldOptions);
c91b1cc3 2710
73c0e107 2711 $priceField = CRM_Price_BAO_PriceField::create($paramsField);
7cf5bd55 2712 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id]);
73c0e107
PN
2713 }
2714
96ea99c8
EM
2715 /**
2716 * Replace the template with a test-oriented template designed to show all the variables.
2717 *
2718 * @param string $templateName
2719 * @param string $input
2720 * @param string $type
2721 */
2722 protected function swapMessageTemplateForInput(string $templateName, string $input, string $type = 'html'): void {
2723 CRM_Core_DAO::executeQuery(
2724 "UPDATE civicrm_msg_template
2725 SET msg_{$type} = %1
2726 WHERE workflow_name = '{$templateName}'
2727 AND is_default = 1", [1 => [$input, 'String']]
2728 );
2729 }
2730
b80f2ad1
E
2731 /**
2732 * Replace the template with a test-oriented template designed to show all the variables.
2733 *
2734 * @param string $templateName
7a2ee417 2735 * @param string $type
b80f2ad1 2736 */
42b37f89 2737 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html'): void {
7a2ee417 2738 $testTemplate = file_get_contents(__DIR__ . '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
b80f2ad1 2739 CRM_Core_DAO::executeQuery(
fa839a68 2740 "UPDATE civicrm_msg_template
2741 SET msg_{$type} = %1
2742 WHERE workflow_name = '{$templateName}'
2743 AND is_default = 1", [1 => [$testTemplate, 'String']]
b80f2ad1
E
2744 );
2745 }
2746
2747 /**
2748 * Reinstate the default template.
2749 *
2750 * @param string $templateName
7a2ee417 2751 * @param string $type
b80f2ad1 2752 */
7a2ee417 2753 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
b80f2ad1
E
2754 CRM_Core_DAO::executeQuery(
2755 "UPDATE civicrm_option_group og
2756 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2757 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2758 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
7a2ee417 2759 SET m.msg_{$type} = m2.msg_{$type}
b80f2ad1
E
2760 WHERE og.name = 'msg_tpl_workflow_contribution'
2761 AND ov.name = '{$templateName}'
2762 AND m.is_default = 1"
2763 );
2764 }
2765
8d35246a
EM
2766 /**
2767 * Flush statics relating to financial type.
2768 */
2769 protected function flushFinancialTypeStatics() {
2770 if (isset(\Civi::$statics['CRM_Financial_BAO_FinancialType'])) {
2771 unset(\Civi::$statics['CRM_Financial_BAO_FinancialType']);
2772 }
4954339a
EM
2773 if (isset(\Civi::$statics['CRM_Contribute_PseudoConstant'])) {
2774 unset(\Civi::$statics['CRM_Contribute_PseudoConstant']);
2775 }
8d35246a 2776 CRM_Contribute_PseudoConstant::flush('financialType');
4954339a
EM
2777 CRM_Contribute_PseudoConstant::flush('membershipType');
2778 // Pseudoconstants may be saved to the cache table.
2779 CRM_Core_DAO::executeQuery("TRUNCATE civicrm_cache");
7cf5bd55 2780 CRM_Financial_BAO_FinancialType::$_statusACLFt = [];
8d35246a
EM
2781 CRM_Financial_BAO_FinancialType::$_availableFinancialTypes = NULL;
2782 }
2783
2784 /**
2785 * Set the permissions to the supplied array.
2786 *
2787 * @param array $permissions
2788 */
2789 protected function setPermissions($permissions) {
2790 CRM_Core_Config::singleton()->userPermissionClass->permissions = $permissions;
2791 $this->flushFinancialTypeStatics();
2792 }
2793
bf722049 2794 /**
2795 * @param array $params
2796 * @param $context
2797 */
2798 public function _checkFinancialRecords($params, $context) {
7cf5bd55 2799 $entityParams = [
bf722049 2800 'entity_id' => $params['id'],
2801 'entity_table' => 'civicrm_contribution',
7cf5bd55 2802 ];
53561d09
EM
2803 $contribution = $this->callAPISuccess('Contribution', 'getsingle', [
2804 'id' => $params['id'],
2805 'return' => ['total_amount', 'fee_amount', 'net_amount'],
2806 ]);
bf722049 2807 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
0babe712 2808 if ($context === 'pending') {
bf722049 2809 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
2810 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2811 return;
2812 }
2813 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
7cf5bd55 2814 $trxnParams = [
bf722049 2815 'id' => $trxn['financial_trxn_id'],
7cf5bd55 2816 ];
0babe712 2817 if ($context !== 'online' && $context !== 'payLater') {
7cf5bd55 2818 $compareParams = [
bf722049 2819 'to_financial_account_id' => 6,
2f3d0664 2820 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2821 'status_id' => 1,
7cf5bd55 2822 ];
bf722049 2823 }
0babe712 2824 if ($context === 'feeAmount') {
bf722049 2825 $compareParams['fee_amount'] = 50;
2826 }
53561d09 2827 elseif ($context === 'online') {
7cf5bd55 2828 $compareParams = [
bf722049 2829 'to_financial_account_id' => 12,
2f3d0664 2830 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2831 'status_id' => 1,
f69a9ac3 2832 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, 1),
7cf5bd55 2833 ];
bf722049 2834 }
2835 elseif ($context == 'payLater') {
7cf5bd55 2836 $compareParams = [
bf722049 2837 'to_financial_account_id' => 7,
2f3d0664 2838 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2839 'status_id' => 2,
7cf5bd55 2840 ];
bf722049 2841 }
2842 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
7cf5bd55 2843 $entityParams = [
bf722049 2844 'financial_trxn_id' => $trxn['financial_trxn_id'],
2845 'entity_table' => 'civicrm_financial_item',
7cf5bd55 2846 ];
bf722049 2847 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
7cf5bd55 2848 $fitemParams = [
bf722049 2849 'id' => $entityTrxn['entity_id'],
7cf5bd55 2850 ];
2851 $compareParams = [
2f3d0664 2852 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2853 'status_id' => 1,
1a1a2f4f 2854 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
7cf5bd55 2855 ];
0babe712 2856 if ($context === 'payLater') {
7cf5bd55 2857 $compareParams = [
2f3d0664 2858 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2859 'status_id' => 3,
1a1a2f4f 2860 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
7cf5bd55 2861 ];
bf722049 2862 }
2863 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2864 if ($context == 'feeAmount') {
7cf5bd55 2865 $maxParams = [
bf722049 2866 'entity_id' => $params['id'],
2867 'entity_table' => 'civicrm_contribution',
7cf5bd55 2868 ];
bf722049 2869 $maxTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($maxParams, TRUE));
7cf5bd55 2870 $trxnParams = [
bf722049 2871 'id' => $maxTrxn['financial_trxn_id'],
7cf5bd55 2872 ];
2873 $compareParams = [
bf722049 2874 'to_financial_account_id' => 5,
2875 'from_financial_account_id' => 6,
2876 'total_amount' => 50,
2877 'status_id' => 1,
7cf5bd55 2878 ];
bf722049 2879 $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['id'], 'DESC');
2880 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
7cf5bd55 2881 $fitemParams = [
bf722049 2882 'entity_id' => $trxnId['financialTrxnId'],
2883 'entity_table' => 'civicrm_financial_trxn',
7cf5bd55 2884 ];
2885 $compareParams = [
2f3d0664 2886 'amount' => 50.00,
bf722049 2887 'status_id' => 1,
2888 'financial_account_id' => 5,
7cf5bd55 2889 ];
bf722049 2890 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2891 }
2892 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2893 // line should be copied into all the functions that call this function & evaluated there
2894 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2895 // when calling completeTransaction or repeatTransaction.
7cf5bd55 2896 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
bf722049 2897 }
2898
8484a5f0 2899 /**
2900 * Return financial type id on basis of name
2901 *
2902 * @param string $name Financial type m/c name
2903 *
2904 * @return int
2905 */
2906 public function getFinancialTypeId($name) {
2907 return CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2908 }
2909
204beda3 2910 /**
2911 * Cleanup function for contents of $this->ids.
2912 *
2913 * This is a best effort cleanup to use in tear downs etc.
2914 *
2915 * It will not fail if the data has already been removed (some tests may do
2916 * their own cleanup).
2917 */
2918 protected function cleanUpSetUpIDs() {
2919 foreach ($this->setupIDs as $entity => $id) {
2920 try {
7cf5bd55 2921 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
204beda3 2922 }
2923 catch (CiviCRM_API3_Exception $e) {
2924 // This is a best-effort cleanup function, ignore.
2925 }
2926 }
2927 }
2928
adbc354b
PN
2929 /**
2930 * Create Financial Type.
2931 *
2932 * @param array $params
2933 *
2934 * @return array
2935 */
7cf5bd55 2936 protected function createFinancialType($params = []) {
adbc354b 2937 $params = array_merge($params,
7cf5bd55 2938 [
adbc354b
PN
2939 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
2940 'is_active' => 1,
7cf5bd55 2941 ]
adbc354b
PN
2942 );
2943 return $this->callAPISuccess('FinancialType', 'create', $params);
2944 }
2945
e1c5a855 2946 /**
2947 * Create Payment Instrument.
2948 *
2949 * @param array $params
2950 * @param string $financialAccountName
2951 *
2952 * @return int
2953 */
7cf5bd55 2954 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
2955 $params = array_merge([
e1c5a855 2956 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
2957 'option_group_id' => 'payment_instrument',
2958 'is_active' => 1,
7cf5bd55 2959 ], $params);
897ff8c5 2960 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
e1c5a855 2961
2962 $relationTypeID = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
2963
2964 $financialAccountParams = [
2965 'entity_table' => 'civicrm_option_value',
897ff8c5 2966 'entity_id' => $newPaymentInstrument,
e1c5a855 2967 'account_relationship' => $relationTypeID,
2968 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
2969 ];
2970 CRM_Financial_BAO_FinancialTypeAccount::add($financialAccountParams);
2971
2972 return CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
2973 }
2974
ec5da26a 2975 /**
2976 * Enable Tax and Invoicing
b086dc3a 2977 *
2978 * @param array $params
ec5da26a 2979 */
0be69b5c 2980 protected function enableTaxAndInvoicing(array $params = []): void {
ec5da26a 2981 // Enable component contribute setting
2982 $contributeSetting = array_merge($params,
7cf5bd55 2983 [
ec5da26a 2984 'invoicing' => 1,
2985 'invoice_prefix' => 'INV_',
0be69b5c
EM
2986 'invoice_due_date' => 10,
2987 'invoice_due_date_period' => 'days',
2988 'invoice_notes' => '',
2989 'invoice_is_email_pdf' => 1,
ec5da26a 2990 'tax_term' => 'Sales Tax',
2991 'tax_display_settings' => 'Inclusive',
7cf5bd55 2992 ]
ec5da26a 2993 );
0be69b5c
EM
2994 foreach ($contributeSetting as $setting => $value) {
2995 Civi::settings()->set($setting, $value);
2996 }
ec5da26a 2997 }
2998
9fbf312f 2999 /**
3000 * Enable Tax and Invoicing
3001 */
0fba5cf8 3002 protected function disableTaxAndInvoicing(): void {
7aa78908 3003 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3004 foreach ($accounts as $account) {
3005 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3006 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3007 }
3008
f436577a 3009 if (!empty(\Civi::$statics['CRM_Core_PseudoConstant']) && isset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3010 unset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates']);
3011 }
0fba5cf8
EM
3012 Civi::settings()->set('invoice_is_email_pdf', FALSE);
3013 Civi::settings()->set('invoicing', FALSE);
9fbf312f 3014 }
3015
7a1f3919 3016 /**
28d44c71 3017 * Add Sales Tax Account for the financial type.
7a1f3919
PN
3018 *
3019 * @param int $financialTypeId
3020 *
28d44c71 3021 * @param array $accountParams
3022 *
3023 * @return CRM_Financial_DAO_EntityFinancialAccount
3024 * @throws \CRM_Core_Exception
7a1f3919 3025 */
28d44c71 3026 protected function addTaxAccountToFinancialType(int $financialTypeId, $accountParams = []) {
3027 $params = array_merge([
7a1f3919
PN
3028 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3029 'financial_account_type_id' => key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3030 'is_deductible' => 1,
3031 'is_tax' => 1,
3032 'tax_rate' => 10,
3033 'is_active' => 1,
28d44c71 3034 ], $accountParams);
7a1f3919 3035 $account = CRM_Financial_BAO_FinancialAccount::add($params);
7cf5bd55 3036 $entityParams = [
7a1f3919 3037 'entity_table' => 'civicrm_financial_type',
7a1f3919 3038 'entity_id' => $financialTypeId,
5b3543ce 3039 'account_relationship' => key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
7cf5bd55 3040 ];
5b3543ce 3041
f436577a 3042 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
28d44c71 3043 \Civi::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = $params['tax_rate'];
f436577a 3044
5b3543ce
JM
3045 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3046 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3047 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3048 $dao->copyValues($entityParams);
3049 $dao->find();
3050 if ($dao->fetch()) {
3051 $entityParams['id'] = $dao->id;
3052 }
3053 $entityParams['financial_account_id'] = $account->id;
3054
7a1f3919
PN
3055 return CRM_Financial_BAO_FinancialTypeAccount::add($entityParams);
3056 }
3057
65e172a3 3058 /**
3059 * Create price set with contribution test for test setup.
3060 *
3061 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3062 * on parent class at some point (fn is not in 4.4).
3063 *
3064 * @param $entity
3065 * @param array $params
3066 */
7cf5bd55 3067 public function createPriceSetWithPage($entity = NULL, $params = []) {
3068 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3069 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
49f03de9 3070 'title' => 'Test Contribution Page',
65e172a3 3071 'financial_type_id' => 1,
3072 'currency' => 'NZD',
3073 'goal_amount' => 50,
3074 'is_pay_later' => 1,
3075 'is_monetary' => TRUE,
3076 'is_email_receipt' => FALSE,
7cf5bd55 3077 ]);
3078 $priceSet = $this->callAPISuccess('price_set', 'create', [
65e172a3 3079 'is_quick_config' => 0,
3080 'extends' => 'CiviMember',
3081 'financial_type_id' => 1,
3082 'title' => 'my Page',
7cf5bd55 3083 ]);
65e172a3 3084 $priceSetID = $priceSet['id'];
3085
3086 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
7cf5bd55 3087 $priceField = $this->callAPISuccess('price_field', 'create', [
65e172a3 3088 'price_set_id' => $priceSetID,
3089 'label' => 'Goat Breed',
3090 'html_type' => 'Radio',
7cf5bd55 3091 ]);
3092 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3093 'price_set_id' => $priceSetID,
3094 'price_field_id' => $priceField['id'],
3095 'label' => 'Long Haired Goat',
3096 'amount' => 20,
3097 'financial_type_id' => 'Donation',
3098 'membership_type_id' => $membershipTypeID,
3099 'membership_num_terms' => 1,
7cf5bd55 3100 ]);
3101 $this->_ids['price_field_value'] = [$priceFieldValue['id']];
3102 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3103 'price_set_id' => $priceSetID,
3104 'price_field_id' => $priceField['id'],
3105 'label' => 'Shoe-eating Goat',
3106 'amount' => 10,
3107 'financial_type_id' => 'Donation',
3108 'membership_type_id' => $membershipTypeID,
3109 'membership_num_terms' => 2,
7cf5bd55 3110 ]);
65e172a3 3111 $this->_ids['price_field_value'][] = $priceFieldValue['id'];
3112
7cf5bd55 3113 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3114 'price_set_id' => $priceSetID,
3115 'price_field_id' => $priceField['id'],
3116 'label' => 'Shoe-eating Goat',
3117 'amount' => 10,
3118 'financial_type_id' => 'Donation',
7cf5bd55 3119 ]);
65e172a3 3120 $this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];
3121
3122 $this->_ids['price_set'] = $priceSetID;
3123 $this->_ids['contribution_page'] = $contributionPageResult['id'];
7cf5bd55 3124 $this->_ids['price_field'] = [$priceField['id']];
65e172a3 3125
3126 $this->_ids['membership_type'] = $membershipTypeID;
3127 }
3128
3c9d67b0 3129 /**
3130 * Only specified contact returned.
7cf5bd55 3131 *
3c9d67b0 3132 * @implements CRM_Utils_Hook::aclWhereClause
7cf5bd55 3133 *
3c9d67b0 3134 * @param $type
3135 * @param $tables
3136 * @param $whereTables
3137 * @param $contactID
3138 * @param $where
3139 */
3140 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3141 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts) . ")";
3142 }
3143
88ebed7c
JP
3144 /**
3145 * @implements CRM_Utils_Hook::selectWhereClause
3146 *
3147 * @param string $entity
3148 * @param array $clauses
3149 */
3150 public function selectWhereClauseHook($entity, &$clauses) {
3151 if ($entity == 'Event') {
3152 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3153 }
3154 }
3155
29a59599
JP
3156 /**
3157 * An implementation of hook_civicrm_post used with all our test cases.
3158 *
3159 * @param $op
3160 * @param string $objectName
3161 * @param int $objectId
3162 * @param $objectRef
3163 */
3164 public function onPost($op, $objectName, $objectId, &$objectRef) {
3165 if ($op == 'create' && $objectName == 'Individual') {
3166 CRM_Core_DAO::executeQuery(
3167 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
7cf5bd55 3168 [
3169 1 => [$objectId, 'Integer'],
3170 ]
29a59599
JP
3171 );
3172 }
3173
3174 if ($op == 'edit' && $objectName == 'Participant') {
7cf5bd55 3175 $params = [
3176 1 => [$objectId, 'Integer'],
3177 ];
29a59599
JP
3178 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3179 CRM_Core_DAO::executeQuery($query, $params);
3180 }
3181 }
3182
f8df7165 3183 /**
3184 * Instantiate form object.
3185 *
3186 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3187 *
3188 * @param string $class
3189 * Name of form class.
3190 *
4715d267 3191 * @param array $formValues
3192 *
3193 * @param string $pageName
3194 *
8a5abba8
EM
3195 * @param array $searchFormValues
3196 * Values for the search form if the form is a task eg.
3197 * for selected ids 6 & 8:
3198 * [
3199 * 'radio_ts' => 'ts_sel',
3200 * 'task' => CRM_Member_Task::PDF_LETTER,
3201 * 'mark_x_6' => 1,
3202 * 'mark_x_8' => 1,
3203 * ]
3204 *
f8df7165 3205 * @return \CRM_Core_Form
3206 */
8a5abba8 3207 public function getFormObject($class, $formValues = [], $pageName = '', $searchFormValues = []) {
6986e4f1 3208 $_POST = $formValues;
9b9335ed 3209 /* @var CRM_Core_Form $form */
f8df7165 3210 $form = new $class();
3211 $_SERVER['REQUEST_METHOD'] = 'GET';
6986e4f1 3212 switch ($class) {
3213 case 'CRM_Event_Cart_Form_Checkout_Payment':
3214 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3215 $form->controller = new CRM_Event_Cart_Controller_Checkout();
3216 break;
df731f74 3217
d026b890
EM
3218 case 'CRM_Event_Form_Registration_Confirm':
3219 $form->controller = new CRM_Event_Controller_Registration();
3220 break;
d452dfe6 3221
839fd3f4 3222 case 'CRM_Contact_Import_Form_DataSource':
6e78138c 3223 case 'CRM_Contact_Import_Form_MapField':
b4d97fa3 3224 case 'CRM_Contact_Import_Form_Preview':
839fd3f4 3225 $form->controller = new CRM_Contact_Import_Controller();
6e78138c
EM
3226 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3227 // The submitted values should be set on one or the other of the forms in the flow.
3228 // For test simplicity we set on all rather than figuring out which ones go where....
3229 $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $formValues;
3230 $_SESSION['_' . $form->controller->_name . '_container']['values']['MapField'] = $formValues;
b4d97fa3 3231 $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues;
6e78138c 3232 return $form;
d026b890 3233
4b58c5c4
EM
3234 case 'CRM_Contribute_Import_Form_DataSource':
3235 case 'CRM_Contribute_Import_Form_MapField':
3236 case 'CRM_Contribute_Import_Form_Preview':
3237 $form->controller = new CRM_Contribute_Import_Controller();
3238 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3239 // The submitted values should be set on one or the other of the forms in the flow.
3240 // For test simplicity we set on all rather than figuring out which ones go where....
3241 $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $formValues;
3242 $_SESSION['_' . $form->controller->_name . '_container']['values']['MapField'] = $formValues;
3243 $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues;
3244 return $form;
3245
22f90136
EM
3246 case 'CRM_Member_Import_Form_DataSource':
3247 case 'CRM_Member_Import_Form_MapField':
3248 case 'CRM_Member_Import_Form_Preview':
3249 $form->controller = new CRM_Member_Import_Controller();
3250 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3251 // The submitted values should be set on one or the other of the forms in the flow.
3252 // For test simplicity we set on all rather than figuring out which ones go where....
3253 $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $formValues;
3254 $_SESSION['_' . $form->controller->_name . '_container']['values']['MapField'] = $formValues;
3255 $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues;
3256 return $form;
3257
99308da4
EM
3258 case 'CRM_Event_Import_Form_DataSource':
3259 case 'CRM_Event_Import_Form_MapField':
3260 case 'CRM_Event_Import_Form_Preview':
3261 $form->controller = new CRM_Event_Import_Controller();
3262 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3263 // The submitted values should be set on one or the other of the forms in the flow.
3264 // For test simplicity we set on all rather than figuring out which ones go where....
3265 $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $formValues;
3266 $_SESSION['_' . $form->controller->_name . '_container']['values']['MapField'] = $formValues;
3267 $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues;
3268 return $form;
3269
7eebbdaa
EM
3270 case 'CRM_Activity_Import_Form_DataSource':
3271 case 'CRM_Activity_Import_Form_MapField':
3272 case 'CRM_Activity_Import_Form_Preview':
3273 $form->controller = new CRM_Activity_Import_Controller();
3274 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3275 // The submitted values should be set on one or the other of the forms in the flow.
3276 // For test simplicity we set on all rather than figuring out which ones go where....
3277 $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $formValues;
3278 $_SESSION['_' . $form->controller->_name . '_container']['values']['MapField'] = $formValues;
3279 $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues;
3280 return $form;
3281
86b783d8
EM
3282 case 'CRM_Custom_Import_Form_DataSource':
3283 case 'CRM_Custom_Import_Form_MapField':
3284 case 'CRM_Custom_Import_Form_Preview':
3285 $form->controller = new CRM_Custom_Import_Controller();
3286 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3287 // The submitted values should be set on one or the other of the forms in the flow.
3288 // For test simplicity we set on all rather than figuring out which ones go where....
3289 $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $formValues;
3290 $_SESSION['_' . $form->controller->_name . '_container']['values']['MapField'] = $formValues;
3291 $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues;
3292 return $form;
3293
df731f74
EM
3294 case strpos($class, '_Form_') !== FALSE:
3295 $form->controller = new CRM_Core_Controller_Simple($class, $pageName);
3296 break;
6986e4f1 3297
3298 default:
3299 $form->controller = new CRM_Core_Controller();
3300 }
3301 if (!$pageName) {
9b9335ed 3302 $pageName = $form->getName();
6986e4f1 3303 }
4715d267 3304 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3305 $_SESSION['_' . $form->controller->_name . '_container']['values'][$pageName] = $formValues;
8a5abba8
EM
3306 if ($searchFormValues) {
3307 $_SESSION['_' . $form->controller->_name . '_container']['values']['Search'] = $searchFormValues;
3308 }
f336046d
EM
3309 if (isset($formValues['_qf_button_name'])) {
3310 $_SESSION['_' . $form->controller->_name . '_container']['_qf_button_name'] = $formValues['_qf_button_name'];
3311 }
f8df7165 3312 return $form;
3313 }
3314
83644f47 3315 /**
3316 * Get possible thousand separators.
3317 *
3318 * @return array
3319 */
3320 public function getThousandSeparators() {
7cf5bd55 3321 return [['.'], [',']];
83644f47 3322 }
3323
ebebd629 3324 /**
3325 * Get the boolean options as a provider.
3326 *
3327 * @return array
3328 */
3329 public function getBooleanDataProvider() {
3330 return [[TRUE], [FALSE]];
3331 }
3332
83644f47 3333 /**
3334 * Set the separators for thousands and decimal points.
3335 *
34e322db 3336 * Note that this only covers some common scenarios.
3337 *
3338 * It does not cater for a situation where the thousand separator is a [space]
3339 * Latter is the Norwegian localization. At least some tests need to
3340 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3341 * to provide broader coverage.
dff93dda 3342 *
83644f47 3343 * @param string $thousandSeparator
3344 */
3345 protected function setCurrencySeparators($thousandSeparator) {
3346 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
34e322db 3347 Civi::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ? '.' : ','));
83644f47 3348 }
3349
dff93dda
JJ
3350 /**
3351 * Sets the thousand separator.
3352 *
3353 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3354 *
3355 * @param $thousandSeparator
3356 */
3357 protected function setMonetaryThousandSeparator($thousandSeparator) {
3358 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3359 }
3360
3361 /**
3362 * Sets the decimal separator.
3363 *
3364 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3365 *
80da79bb 3366 * @param $decimalPoint
dff93dda
JJ
3367 */
3368 protected function setMonetaryDecimalPoint($decimalPoint) {
3369 Civi::settings()->set('monetaryDecimalPoint', $decimalPoint);
3370 }
3371
3372 /**
3373 * Sets the default currency.
3374 *
3375 * @param $currency
3376 */
3377 protected function setDefaultCurrency($currency) {
3378 Civi::settings()->set('defaultCurrency', $currency);
3379 }
3380
83644f47 3381 /**
3382 * Format money as it would be input.
3383 *
3384 * @param string $amount
3385 *
3386 * @return string
3387 */
3388 protected function formatMoneyInput($amount) {
3389 return CRM_Utils_Money::format($amount, NULL, '%a');
3390 }
3391
3ca4bd1b 3392 /**
3393 * Get the contribution object.
3394 *
3395 * @param int $contributionID
3396 *
3397 * @return \CRM_Contribute_BAO_Contribution
3398 */
3399 protected function getContributionObject($contributionID) {
3400 $contributionObj = new CRM_Contribute_BAO_Contribution();
3401 $contributionObj->id = $contributionID;
3402 $contributionObj->find(TRUE);
3403 return $contributionObj;
3404 }
3405
df3320dc 3406 /**
3407 * Enable multilingual.
3408 */
3409 public function enableMultilingual() {
7cf5bd55 3410 $this->callAPISuccess('Setting', 'create', [
df3320dc 3411 'lcMessages' => 'en_US',
7cf5bd55 3412 'languageLimit' => [
df3320dc 3413 'en_US' => 1,
7cf5bd55 3414 ],
3415 ]);
df3320dc 3416
3417 CRM_Core_I18n_Schema::makeMultilingual('en_US');
3418
3419 global $dbLocale;
3420 $dbLocale = '_en_US';
3421 }
3422
51c566a3
SL
3423 /**
3424 * Setup or clean up SMS tests
7cf5bd55 3425 *
51c566a3
SL
3426 * @param bool $teardown
3427 *
3428 * @throws \CiviCRM_API3_Exception
3429 */
3430 public function setupForSmsTests($teardown = FALSE) {
3431 require_once 'CiviTest/CiviTestSMSProvider.php';
3432
3433 // Option value params for CiviTestSMSProvider
3434 $groupID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
7cf5bd55 3435 $params = [
51c566a3
SL
3436 'option_group_id' => $groupID,
3437 'label' => 'unittestSMS',
3438 'value' => 'unit.test.sms',
7cf5bd55 3439 'name' => 'CiviTestSMSProvider',
51c566a3 3440 'is_default' => 1,
7cf5bd55 3441 'is_active' => 1,
3442 'version' => 3,
3443 ];
51c566a3
SL
3444
3445 if ($teardown) {
3446 // Test completed, delete provider
3447 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
7cf5bd55 3448 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
51c566a3
SL
3449 return;
3450 }
3451
3452 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3453 // in CRM/Sms/Provider.php even though it is not an extension.
3454 return civicrm_api3('option_value', 'create', $params);
3455 }
3456
68989e71 3457 /**
3458 * Start capturing browser output.
3459 *
3460 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3461 */
3462 protected function startCapturingOutput() {
3463 ob_start();
3464 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3465 }
3466
3467 /**
3468 * Stop capturing browser output and return as a csv.
3469 *
3470 * @param bool $isFirstRowHeaders
3471 *
3472 * @return \League\Csv\Reader
3473 *
3474 * @throws \League\Csv\Exception
3475 */
3476 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3477 $output = ob_get_flush();
3478 $stream = fopen('php://memory', 'r+');
3479 fwrite($stream, $output);
3480 rewind($stream);
1fc9f4a7 3481 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
68989e71 3482 $csv = Reader::createFromString($output);
3483 if ($isFirstRowHeaders) {
3484 $csv->setHeaderOffset(0);
3485 }
3486 ob_clean();
3487 return $csv;
3488 }
3489
91786f44 3490 /**
3491 * Rename various labels to not match the names.
3492 *
3493 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3494 * code that expects it to.
3495 */
3496 protected function renameLabels() {
3497 $replacements = ['Pending', 'Refunded'];
3498 foreach ($replacements as $name) {
3499 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3500 }
3501 }
3502
3503 /**
3504 * Undo any label renaming.
3505 */
3506 protected function resetLabels() {
3507 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3508 }
3509
5266bd48 3510 /**
3511 * Get parameters to set up a multi-line participant order.
3512 *
3513 * @return array
3514 * @throws \CRM_Core_Exception
3515 */
3516 protected function getParticipantOrderParams(): array {
5266bd48 3517 $event = $this->eventCreate();
3518 $this->_eventId = $event['id'];
3519 $eventParams = [
3520 'id' => $this->_eventId,
3521 'financial_type_id' => 4,
3522 'is_monetary' => 1,
3523 ];
3524 $this->callAPISuccess('event', 'create', $eventParams);
3525 $priceFields = $this->createPriceSet('event', $this->_eventId);
5266bd48 3526 $orderParams = [
3527 'total_amount' => 300,
3528 'currency' => 'USD',
e59aadea 3529 'contact_id' => $this->individualCreate(),
5266bd48 3530 'financial_type_id' => 4,
3531 'contribution_status_id' => 'Pending',
5266bd48 3532 ];
3533 foreach ($priceFields['values'] as $key => $priceField) {
3534 $orderParams['line_items'][] = [
3535 'line_item' => [
3536 [
3537 'price_field_id' => $priceField['price_field_id'],
3538 'price_field_value_id' => $priceField['id'],
3539 'label' => $priceField['label'],
3540 'field_title' => $priceField['label'],
3541 'qty' => 1,
3542 'unit_price' => $priceField['amount'],
3543 'line_total' => $priceField['amount'],
3544 'financial_type_id' => $priceField['financial_type_id'],
3545 'entity_table' => 'civicrm_participant',
3546 ],
3547 ],
e59aadea
EM
3548 'params' => [
3549 'financial_type_id' => 4,
3550 'event_id' => $this->_eventId,
3551 'role_id' => 1,
3552 'status_id' => 14,
3553 'fee_currency' => 'USD',
3554 'contact_id' => $this->individualCreate(),
3555 ],
5266bd48 3556 ];
3557 }
3558 return $orderParams;
3559 }
3560
3d4f6f65 3561 /**
3562 * @param $payments
3563 *
3564 * @throws \CRM_Core_Exception
3565 */
33ed891b 3566 protected function validatePayments($payments): void {
3d4f6f65 3567 foreach ($payments as $payment) {
4804f442 3568 $balance = CRM_Contribute_BAO_Contribution::getContributionBalance($payment['contribution_id']);
3569 if ($balance < 0 && $balance + $payment['total_amount'] === 0.0) {
3570 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3571 // This is a pretty rough way at guessing which payment is the overpayment - but
3572 // for the test suite it should be enough.
3573 continue;
3574 }
3d4f6f65 3575 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3576 'financial_trxn_id' => $payment['id'],
3577 'entity_table' => 'civicrm_financial_item',
3578 'return' => ['amount'],
3579 ])['values'];
3580 $itemTotal = 0;
3581 foreach ($items as $item) {
3582 $itemTotal += $item['amount'];
3583 }
3584 $this->assertEquals($payment['total_amount'], $itemTotal);
3585 }
3586 }
3587
3588 /**
3589 * Validate all created payments.
3590 *
3591 * @throws \CRM_Core_Exception
3592 */
0babe712 3593 protected function validateAllPayments(): void {
53561d09
EM
3594 $payments = $this->callAPISuccess('Payment', 'get', [
3595 'return' => ['total_amount', 'tax_amount'],
3596 'options' => ['limit' => 0],
3597 ])['values'];
3d4f6f65 3598 $this->validatePayments($payments);
3599 }
3600
7aeb7f06 3601 /**
3602 * Validate all created contributions.
3603 *
26076348 3604 * @throws \API_Exception
7aeb7f06 3605 */
33ed891b 3606 protected function validateAllContributions(): void {
26076348 3607 $contributions = Contribution::get(FALSE)->setSelect(['total_amount', 'tax_amount'])->execute();
7aeb7f06 3608 foreach ($contributions as $contribution) {
53561d09
EM
3609 $lineItems = $this->callAPISuccess('LineItem', 'get', [
3610 'contribution_id' => $contribution['id'],
95556e15 3611 'return' => ['tax_amount', 'line_total', 'entity_table', 'entity_id', 'qty'],
53561d09 3612 ])['values'];
7aeb7f06 3613 $total = 0;
58315149 3614 $taxTotal = 0;
96e48e2b
EM
3615 $memberships = [];
3616 $participants = [];
7aeb7f06 3617 foreach ($lineItems as $lineItem) {
3618 $total += $lineItem['line_total'];
58315149 3619 $taxTotal += (float) ($lineItem['tax_amount'] ?? 0);
96e48e2b
EM
3620 if ($lineItem['entity_table'] === 'civicrm_membership') {
3621 $memberships[] = $lineItem['entity_id'];
3622 }
95556e15
EM
3623 if ($lineItem['entity_table'] === 'civicrm_participant' && $lineItem['qty'] > 0) {
3624 $participants[$lineItem['entity_id']] = $lineItem['entity_id'];
96e48e2b
EM
3625 }
3626 }
3627 $membershipPayments = $this->callAPISuccess('MembershipPayment', 'get', ['contribution_id' => $contribution['id'], 'return' => 'membership_id'])['values'];
3628 $participantPayments = $this->callAPISuccess('ParticipantPayment', 'get', ['contribution_id' => $contribution['id'], 'return' => 'participant_id'])['values'];
3629 $this->assertCount(count($memberships), $membershipPayments);
95556e15 3630 $this->assertCount(count($participants), $participantPayments);
96e48e2b
EM
3631 foreach ($membershipPayments as $payment) {
3632 $this->assertContains($payment['membership_id'], $memberships);
7aeb7f06 3633 }
95556e15
EM
3634 foreach ($participantPayments as $payment) {
3635 $this->assertContains($payment['participant_id'], $participants);
3636 }
58315149 3637 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ?? 0));
53c8b1be 3638 $this->assertEquals($total + $taxTotal, $contribution['total_amount']);
7aeb7f06 3639 }
3640 }
3641
d943e78d 3642 /**
62ee752a 3643 * @return array|int
62ee752a
JG
3644 */
3645 protected function createRuleGroup() {
0babe712 3646 return $this->callAPISuccess('RuleGroup', 'create', [
62ee752a
JG
3647 'contact_type' => 'Individual',
3648 'threshold' => 8,
3649 'used' => 'General',
3650 'name' => 'TestRule',
3651 'title' => 'TestRule',
3652 'is_reserved' => 0,
3653 ]);
62ee752a
JG
3654 }
3655
3656 /**
d943e78d 3657 * Generic create test.
3658 *
3659 * @param int $version
3660 *
3661 * @throws \CRM_Core_Exception
3662 */
0babe712 3663 protected function basicCreateTest(int $version): void {
d943e78d 3664 $this->_apiversion = $version;
3665 $result = $this->callAPIAndDocument($this->_entity, 'create', $this->params, __FUNCTION__, __FILE__);
3666 $this->assertEquals(1, $result['count']);
3667 $this->assertNotNull($result['values'][$result['id']]['id']);
3668 $this->getAndCheck($this->params, $result['id'], $this->_entity);
3669 }
3670
3671 /**
3672 * Generic delete test.
3673 *
3674 * @param int $version
3675 *
3676 * @throws \CRM_Core_Exception
3677 */
0babe712 3678 protected function basicDeleteTest(int $version): void {
d943e78d 3679 $this->_apiversion = $version;
3680 $result = $this->callAPISuccess($this->_entity, 'create', $this->params);
3681 $deleteParams = ['id' => $result['id']];
3682 $this->callAPIAndDocument($this->_entity, 'delete', $deleteParams, __FUNCTION__, __FILE__);
3683 $checkDeleted = $this->callAPISuccess($this->_entity, 'get', []);
3684 $this->assertEquals(0, $checkDeleted['count']);
3685 }
3686
7580050c 3687 /**
3688 * Create and return a case object for the given Client ID.
3689 *
3690 * @param int $clientId
3691 * @param int $loggedInUser
3692 * Omit or pass NULL to use the same as clientId
3693 * @param array $extra
3694 * Optional specific parameters such as start_date
3695 *
3696 * @return CRM_Case_BAO_Case
3697 */
76e84f47 3698 public function createCase($clientId, $loggedInUser = NULL, $extra = []) {
7580050c 3699 if (empty($loggedInUser)) {
3700 // backwards compatibility - but it's more typical that the creator is a different person than the client
3701 $loggedInUser = $clientId;
3702 }
76e84f47 3703 $caseParams = array_merge([
7580050c 3704 'activity_subject' => 'Case Subject',
3705 'client_id' => $clientId,
3706 'case_type_id' => 1,
3707 'status_id' => 1,
3708 'case_type' => 'housing_support',
3709 'subject' => 'Case Subject',
aa7dd30f
EM
3710 'start_date' => date('Y-m-d'),
3711 'start_date_time' => date('YmdHis'),
7580050c 3712 'medium_id' => 2,
3713 'activity_details' => '',
76e84f47 3714 ], $extra);
7580050c 3715 $form = new CRM_Case_Form_Case();
f04b3586 3716 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3717 }
3718
3719 /**
3720 * Validate that all location entities have exactly one primary.
3721 *
3722 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3723 */
0babe712 3724 public function assertLocationValidity(): void {
f04b3586 3725 $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT COUNT(*) FROM
3726
3727(SELECT a1.contact_id
3728FROM civicrm_address a1
3729 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3730 AND a1.contact_id = a2.contact_id
3731WHERE
3732 a1.is_primary = 1
3733 AND a2.id IS NOT NULL
3734 AND a1.contact_id IS NOT NULL
3735UNION
3736SELECT a1.contact_id
3737FROM civicrm_address a1
3738 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3739 AND a1.contact_id = a2.contact_id
3740WHERE a1.is_primary = 0
3741 AND a2.id IS NULL
3742 AND a1.contact_id IS NOT NULL
3743
3744UNION
3745
3746SELECT a1.contact_id
3747FROM civicrm_email a1
3748 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3749 AND a1.contact_id = a2.contact_id
3750WHERE
3751 a1.is_primary = 1
3752 AND a2.id IS NOT NULL
3753 AND a1.contact_id IS NOT NULL
3754UNION
3755SELECT a1.contact_id
3756FROM civicrm_email a1
3757 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3758 AND a1.contact_id = a2.contact_id
3759WHERE a1.is_primary = 0
3760 AND a2.id IS NULL
3761 AND a1.contact_id IS NOT NULL
3762
3763UNION
3764
3765SELECT a1.contact_id
3766FROM civicrm_phone a1
3767 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3768 AND a1.contact_id = a2.contact_id
3769WHERE
3770 a1.is_primary = 1
3771 AND a2.id IS NOT NULL
3772 AND a1.contact_id IS NOT NULL
3773UNION
3774SELECT a1.contact_id
3775FROM civicrm_phone a1
3776 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3777 AND a1.contact_id = a2.contact_id
3778WHERE a1.is_primary = 0
3779 AND a2.id IS NULL
3780 AND a1.contact_id IS NOT NULL
3781
3782UNION
3783
3784SELECT a1.contact_id
3785FROM civicrm_im a1
3786 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3787 AND a1.contact_id = a2.contact_id
3788WHERE
3789 a1.is_primary = 1
3790 AND a2.id IS NOT NULL
3791 AND a1.contact_id IS NOT NULL
3792UNION
3793SELECT a1.contact_id
3794FROM civicrm_im a1
3795 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3796 AND a1.contact_id = a2.contact_id
3797WHERE a1.is_primary = 0
3798 AND a2.id IS NULL
3799 AND a1.contact_id IS NOT NULL
3800
3801UNION
3802
3803SELECT a1.contact_id
3804FROM civicrm_openid a1
3805 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3806 AND a1.contact_id = a2.contact_id
3807WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3808UNION
3809
3810SELECT a1.contact_id
3811FROM civicrm_openid a1
3812 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3813 AND a1.contact_id = a2.contact_id
3814WHERE
3815 a1.is_primary = 1
3816 AND a2.id IS NOT NULL
3817 AND a1.contact_id IS NOT NULL
3818UNION
3819SELECT a1.contact_id
3820FROM civicrm_openid a1
3821 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3822 AND a1.contact_id = a2.contact_id
3823WHERE a1.is_primary = 0
3824 AND a2.id IS NULL
3825 AND a1.contact_id IS NOT NULL) as primary_descrepancies
3826 '));
7580050c 3827 }
3828
e7aa5b6b 3829 /**
3830 * Ensure the specified mysql mode/s are activated.
3831 *
3832 * @param array $modes
3833 */
3834 protected function ensureMySQLMode(array $modes): void {
3835 $currentModes = array_fill_keys(CRM_Utils_SQL::getSqlModes(), 1);
3836 $currentModes = array_merge($currentModes, array_fill_keys($modes, 1));
3837 CRM_Core_DAO::executeQuery("SET GLOBAL sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
5dc24de0 3838 CRM_Core_DAO::executeQuery("SET sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
e7aa5b6b 3839 }
3840
9203ed4b
EM
3841 /**
3842 * Delete any extraneous relationship types.
3843 *
3844 * @throws \API_Exception
3845 * @throws \Civi\API\Exception\UnauthorizedException
3846 */
3847 protected function deleteNonDefaultRelationshipTypes(): void {
3848 RelationshipType::delete(FALSE)->addWhere('name_a_b', 'NOT IN', [
3849 'Child of',
3850 'Spouse of',
3851 'Partner of',
3852 'Sibling of',
3853 'Employee of',
3854 'Volunteer for',
3855 'Head of Household for',
3856 'Household Member of',
3857 'Case Coordinator is',
3858 'Supervised by',
3859 ])->execute();
3860 }
3861
8a475c3d
EM
3862 /**
3863 * Delete any existing custom data groups.
8a475c3d
EM
3864 */
3865 protected function cleanupCustomGroups(): void {
d706c149
EM
3866 try {
3867 CustomField::get(FALSE)->setSelect(['option_group_id', 'custom_group_id'])
3868 ->addChain('delete_options', OptionGroup::delete()
3869 ->addWhere('id', '=', '$option_group_id')
3870 )
3871 ->addChain('delete_fields', CustomField::delete()
3872 ->addWhere('id', '=', '$id')
3873 )->execute();
8a475c3d 3874
d706c149
EM
3875 CustomGroup::delete(FALSE)->addWhere('id', '>', 0)->execute();
3876 }
3877 catch (API_Exception $e) {
3878 $this->fail('failed to cleanup custom groups ' . $e->getMessage());
3879 }
8a475c3d
EM
3880 }
3881
f8f6fc4e
EM
3882 /**
3883 * Ensure the default price set & field exist for memberships.
3884 */
3885 protected function ensureMembershipPriceSetExists(): void {
3886 CRM_Core_DAO::executeQuery("INSERT INTO civicrm_price_set (`id`, `name`, `title`, `extends`)
3887 VALUES (2, 'default_membership_type_amount', 'Membership Amount', 3)
3888 ON DUPLICATE KEY UPDATE `name` = 'default_membership_type_amount', title = 'Membership Amount';
3889 ");
3890 CRM_Core_DAO::executeQuery("INSERT INTO civicrm_price_field
3891 (`id`, `name`, `price_set_id`, `label`, `html_type`)
3892 VALUES (2, 1, 2, 'Membership Amount', 'Radio')
3893 ON DUPLICATE KEY UPDATE `name` = '1', price_set_id = 1, label = 'Membership Amount', html_type = 'Radio'
3894 ");
3895 }
3896
aa7dd30f
EM
3897 /**
3898 * Add an address block to the current domain.
3899 *
3900 * @noinspection PhpUnhandledExceptionInspection
3901 */
3902 protected function addLocationBlockToDomain(): void {
3903 $contactID = CRM_Core_BAO_Domain::getDomain()->contact_id;
3904 Phone::create()
3905 ->setValues(['phone' => 123, 'contact_id' => $contactID])
3906 ->execute()
3907 ->first()['id'];
3908 Address::create()->setValues([
3909 'street_address' => '10 Downing Street',
3910 'city' => 'London',
3911 'contact_id' => $contactID,
3912 ])->execute()->first();
3913 }
3914
a86d27fc 3915}