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