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