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