Merge pull request #20612 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 */
00be9182 1989 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
6a488035 1990
7cf5bd55 1991 $result = $this->callAPISuccessGetSingle($entity, [
6a488035 1992 'id' => $id,
7cf5bd55 1993 ]);
6a488035
TO
1994
1995 if ($delete) {
7cf5bd55 1996 $this->callAPISuccess($entity, 'Delete', [
6a488035 1997 'id' => $id,
7cf5bd55 1998 ]);
6a488035 1999 }
7cf5bd55 2000 $dateFields = $keys = $dateTimeFields = [];
2001 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
6a488035
TO
2002 foreach ($fields['values'] as $field => $settings) {
2003 if (array_key_exists($field, $result)) {
e255b57a 2004 $keys[CRM_Utils_Array::value('name', $settings, $field)] = $field;
6a488035
TO
2005 }
2006 else {
e255b57a 2007 $keys[CRM_Utils_Array::value('name', $settings, $field)] = CRM_Utils_Array::value('name', $settings, $field);
6a488035 2008 }
849ac823 2009 $type = $settings['type'] ?? NULL;
afd404ea 2010 if ($type == CRM_Utils_Type::T_DATE) {
2011 $dateFields[] = $settings['name'];
2012 // we should identify both real names & unique names as dates
5896d037 2013 if ($field != $settings['name']) {
afd404ea 2014 $dateFields[] = $field;
2015 }
2016 }
5896d037 2017 if ($type == CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME) {
afd404ea 2018 $dateTimeFields[] = $settings['name'];
2019 // we should identify both real names & unique names as dates
5896d037 2020 if ($field != $settings['name']) {
afd404ea 2021 $dateTimeFields[] = $field;
2022 }
6a488035
TO
2023 }
2024 }
2025
2026 if (strtolower($entity) == 'contribution') {
2027 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
2028 // this is not returned in id format
2029 unset($params['payment_instrument_id']);
2030 $params['contribution_source'] = $params['source'];
2031 unset($params['source']);
2032 }
2033
2034 foreach ($params as $key => $value) {
b76b3590 2035 if ($key == 'version' || substr($key, 0, 3) == 'api' || (!array_key_exists($key, $keys) || !array_key_exists($keys[$key], $result))) {
6a488035
TO
2036 continue;
2037 }
2038 if (in_array($key, $dateFields)) {
2039 $value = date('Y-m-d', strtotime($value));
2040 $result[$key] = date('Y-m-d', strtotime($result[$key]));
2041 }
afd404ea 2042 if (in_array($key, $dateTimeFields)) {
2043 $value = date('Y-m-d H:i:s', strtotime($value));
a72cec08 2044 $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 2045 }
2046 $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
2047 }
2048 }
2049
2050 /**
eceb18cc 2051 * Get formatted values in the actual and expected result.
7cf5bd55 2052 *
e16033b4
TO
2053 * @param array $actual
2054 * Actual calculated values.
2055 * @param array $expected
2056 * Expected values.
6a488035 2057 */
00be9182 2058 public function checkArrayEquals(&$actual, &$expected) {
6a488035
TO
2059 self::unsetId($actual);
2060 self::unsetId($expected);
25115a3e 2061 $this->assertEquals($expected, $actual);
6a488035
TO
2062 }
2063
2064 /**
100fef9d 2065 * Unset the key 'id' from the array
7cf5bd55 2066 *
e16033b4
TO
2067 * @param array $unformattedArray
2068 * The array from which the 'id' has to be unset.
6a488035 2069 */
00be9182 2070 public static function unsetId(&$unformattedArray) {
7cf5bd55 2071 $formattedArray = [];
6a488035
TO
2072 if (array_key_exists('id', $unformattedArray)) {
2073 unset($unformattedArray['id']);
2074 }
a7488080 2075 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
6a488035 2076 foreach ($unformattedArray['values'] as $key => $value) {
6c6e6187 2077 if (is_array($value)) {
6a488035
TO
2078 foreach ($value as $k => $v) {
2079 if ($k == 'id') {
2080 unset($value[$k]);
2081 }
2082 }
2083 }
2084 elseif ($key == 'id') {
2085 $unformattedArray[$key];
2086 }
7cf5bd55 2087 $formattedArray = [$value];
6a488035
TO
2088 }
2089 $unformattedArray['values'] = $formattedArray;
2090 }
2091 }
2092
2093 /**
2094 * Helper to enable/disable custom directory support
2095 *
e16033b4
TO
2096 * @param array $customDirs
2097 * With members:.
6a488035
TO
2098 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2099 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2100 */
00be9182 2101 public function customDirectories($customDirs) {
6a488035
TO
2102 $config = CRM_Core_Config::singleton();
2103
2104 if (empty($customDirs['php_path']) || $customDirs['php_path'] === FALSE) {
2105 unset($config->customPHPPathDir);
2106 }
2107 elseif ($customDirs['php_path'] === TRUE) {
2108 $config->customPHPPathDir = dirname(dirname(__FILE__)) . '/custom_directories/php/';
2109 }
2110 else {
2111 $config->customPHPPathDir = $php_path;
2112 }
2113
2114 if (empty($customDirs['template_path']) || $customDirs['template_path'] === FALSE) {
2115 unset($config->customTemplateDir);
2116 }
2117 elseif ($customDirs['template_path'] === TRUE) {
2118 $config->customTemplateDir = dirname(dirname(__FILE__)) . '/custom_directories/templates/';
2119 }
2120 else {
2121 $config->customTemplateDir = $template_path;
2122 }
2123 }
2124
2125 /**
eceb18cc 2126 * Generate a temporary folder.
6a488035 2127 *
2a6da8d7 2128 * @param string $prefix
7cf5bd55 2129 *
a6c01b45 2130 * @return string
6a488035 2131 */
00be9182 2132 public function createTempDir($prefix = 'test-') {
6a488035
TO
2133 $tempDir = CRM_Utils_File::tempdir($prefix);
2134 $this->tempDirs[] = $tempDir;
2135 return $tempDir;
2136 }
2137
00be9182 2138 public function cleanTempDirs() {
6a488035
TO
2139 if (!is_array($this->tempDirs)) {
2140 // fix test errors where this is not set
2141 return;
2142 }
2143 foreach ($this->tempDirs as $tempDir) {
2144 if (is_dir($tempDir)) {
2145 CRM_Utils_File::cleanDir($tempDir, TRUE, FALSE);
2146 }
2147 }
2148 }
2149
2150 /**
eceb18cc 2151 * Temporarily replace the singleton extension with a different one.
7cf5bd55 2152 *
1e1fdcf6 2153 * @param \CRM_Extension_System $system
6a488035 2154 */
00be9182 2155 public function setExtensionSystem(CRM_Extension_System $system) {
6a488035
TO
2156 if ($this->origExtensionSystem == NULL) {
2157 $this->origExtensionSystem = CRM_Extension_System::singleton();
2158 }
2159 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2160 }
2161
00be9182 2162 public function unsetExtensionSystem() {
6a488035
TO
2163 if ($this->origExtensionSystem !== NULL) {
2164 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2165 $this->origExtensionSystem = NULL;
2166 }
2167 }
f17d75bb 2168
076d8c82
TO
2169 /**
2170 * Temporarily alter the settings-metadata to add a mock setting.
2171 *
2172 * WARNING: The setting metadata will disappear on the next cache-clear.
2173 *
2174 * @param $extras
7cf5bd55 2175 *
076d8c82
TO
2176 * @return void
2177 */
00be9182 2178 public function setMockSettingsMetaData($extras) {
5896d037
TO
2179 CRM_Utils_Hook::singleton()
2180 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2181 $metadata = array_merge($metadata, $extras);
2182 });
076d8c82 2183
1209c8a7
CW
2184 Civi::service('settings_manager')->flush();
2185
7cf5bd55 2186 $fields = $this->callAPISuccess('setting', 'getfields', []);
076d8c82
TO
2187 foreach ($extras as $key => $spec) {
2188 $this->assertNotEmpty($spec['title']);
2189 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2190 }
2191 }
2192
4cbe18b8 2193 /**
100fef9d 2194 * @param string $name
4cbe18b8 2195 */
00be9182 2196 public function financialAccountDelete($name) {
f17d75bb
PN
2197 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2198 $financialAccount->name = $name;
5896d037 2199 if ($financialAccount->find(TRUE)) {
f17d75bb
PN
2200 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2201 $entityFinancialType->financial_account_id = $financialAccount->id;
2202 $entityFinancialType->delete();
2203 $financialAccount->delete();
2204 }
2205 }
fb32de45 2206
2b7d3f8a 2207 /**
2208 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2209 * (NB unclear if this is still required)
2210 */
00be9182 2211 public function _sethtmlGlobals() {
7cf5bd55 2212 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2213 'required' => [
2b7d3f8a 2214 'html_quickform_rule_required',
21dfd5f5 2215 'HTML/QuickForm/Rule/Required.php',
7cf5bd55 2216 ],
2217 'maxlength' => [
2b7d3f8a 2218 'html_quickform_rule_range',
21dfd5f5 2219 'HTML/QuickForm/Rule/Range.php',
7cf5bd55 2220 ],
2221 'minlength' => [
2b7d3f8a 2222 'html_quickform_rule_range',
21dfd5f5 2223 'HTML/QuickForm/Rule/Range.php',
7cf5bd55 2224 ],
2225 'rangelength' => [
2b7d3f8a 2226 'html_quickform_rule_range',
21dfd5f5 2227 'HTML/QuickForm/Rule/Range.php',
7cf5bd55 2228 ],
2229 'email' => [
2b7d3f8a 2230 'html_quickform_rule_email',
21dfd5f5 2231 'HTML/QuickForm/Rule/Email.php',
7cf5bd55 2232 ],
2233 'regex' => [
2b7d3f8a 2234 'html_quickform_rule_regex',
21dfd5f5 2235 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2236 ],
2237 'lettersonly' => [
2b7d3f8a 2238 'html_quickform_rule_regex',
21dfd5f5 2239 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2240 ],
2241 'alphanumeric' => [
2b7d3f8a 2242 'html_quickform_rule_regex',
21dfd5f5 2243 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2244 ],
2245 'numeric' => [
2b7d3f8a 2246 'html_quickform_rule_regex',
21dfd5f5 2247 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2248 ],
2249 'nopunctuation' => [
2b7d3f8a 2250 'html_quickform_rule_regex',
21dfd5f5 2251 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2252 ],
2253 'nonzero' => [
2b7d3f8a 2254 'html_quickform_rule_regex',
21dfd5f5 2255 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2256 ],
2257 'callback' => [
2b7d3f8a 2258 'html_quickform_rule_callback',
21dfd5f5 2259 'HTML/QuickForm/Rule/Callback.php',
7cf5bd55 2260 ],
2261 'compare' => [
2b7d3f8a 2262 'html_quickform_rule_compare',
21dfd5f5 2263 'HTML/QuickForm/Rule/Compare.php',
7cf5bd55 2264 ],
2265 ];
2b7d3f8a 2266 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
7cf5bd55 2267 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2268 'group' => [
2b7d3f8a 2269 'HTML/QuickForm/group.php',
21dfd5f5 2270 'HTML_QuickForm_group',
7cf5bd55 2271 ],
2272 'hidden' => [
2b7d3f8a 2273 'HTML/QuickForm/hidden.php',
21dfd5f5 2274 'HTML_QuickForm_hidden',
7cf5bd55 2275 ],
2276 'reset' => [
2b7d3f8a 2277 'HTML/QuickForm/reset.php',
21dfd5f5 2278 'HTML_QuickForm_reset',
7cf5bd55 2279 ],
2280 'checkbox' => [
2b7d3f8a 2281 'HTML/QuickForm/checkbox.php',
21dfd5f5 2282 'HTML_QuickForm_checkbox',
7cf5bd55 2283 ],
2284 'file' => [
2b7d3f8a 2285 'HTML/QuickForm/file.php',
21dfd5f5 2286 'HTML_QuickForm_file',
7cf5bd55 2287 ],
2288 'image' => [
2b7d3f8a 2289 'HTML/QuickForm/image.php',
21dfd5f5 2290 'HTML_QuickForm_image',
7cf5bd55 2291 ],
2292 'password' => [
2b7d3f8a 2293 'HTML/QuickForm/password.php',
21dfd5f5 2294 'HTML_QuickForm_password',
7cf5bd55 2295 ],
2296 'radio' => [
2b7d3f8a 2297 'HTML/QuickForm/radio.php',
21dfd5f5 2298 'HTML_QuickForm_radio',
7cf5bd55 2299 ],
2300 'button' => [
2b7d3f8a 2301 'HTML/QuickForm/button.php',
21dfd5f5 2302 'HTML_QuickForm_button',
7cf5bd55 2303 ],
2304 'submit' => [
2b7d3f8a 2305 'HTML/QuickForm/submit.php',
21dfd5f5 2306 'HTML_QuickForm_submit',
7cf5bd55 2307 ],
2308 'select' => [
2b7d3f8a 2309 'HTML/QuickForm/select.php',
21dfd5f5 2310 'HTML_QuickForm_select',
7cf5bd55 2311 ],
2312 'hiddenselect' => [
2b7d3f8a 2313 'HTML/QuickForm/hiddenselect.php',
21dfd5f5 2314 'HTML_QuickForm_hiddenselect',
7cf5bd55 2315 ],
2316 'text' => [
2b7d3f8a 2317 'HTML/QuickForm/text.php',
21dfd5f5 2318 'HTML_QuickForm_text',
7cf5bd55 2319 ],
2320 'textarea' => [
2b7d3f8a 2321 'HTML/QuickForm/textarea.php',
21dfd5f5 2322 'HTML_QuickForm_textarea',
7cf5bd55 2323 ],
2324 'fckeditor' => [
2b7d3f8a 2325 'HTML/QuickForm/fckeditor.php',
21dfd5f5 2326 'HTML_QuickForm_FCKEditor',
7cf5bd55 2327 ],
2328 'tinymce' => [
2b7d3f8a 2329 'HTML/QuickForm/tinymce.php',
21dfd5f5 2330 'HTML_QuickForm_TinyMCE',
7cf5bd55 2331 ],
2332 'dojoeditor' => [
2b7d3f8a 2333 'HTML/QuickForm/dojoeditor.php',
21dfd5f5 2334 'HTML_QuickForm_dojoeditor',
7cf5bd55 2335 ],
2336 'link' => [
2b7d3f8a 2337 'HTML/QuickForm/link.php',
21dfd5f5 2338 'HTML_QuickForm_link',
7cf5bd55 2339 ],
2340 'advcheckbox' => [
2b7d3f8a 2341 'HTML/QuickForm/advcheckbox.php',
21dfd5f5 2342 'HTML_QuickForm_advcheckbox',
7cf5bd55 2343 ],
2344 'date' => [
2b7d3f8a 2345 'HTML/QuickForm/date.php',
21dfd5f5 2346 'HTML_QuickForm_date',
7cf5bd55 2347 ],
2348 'static' => [
2b7d3f8a 2349 'HTML/QuickForm/static.php',
21dfd5f5 2350 'HTML_QuickForm_static',
7cf5bd55 2351 ],
2352 'header' => [
2b7d3f8a 2353 'HTML/QuickForm/header.php',
21dfd5f5 2354 'HTML_QuickForm_header',
7cf5bd55 2355 ],
2356 'html' => [
2b7d3f8a 2357 'HTML/QuickForm/html.php',
21dfd5f5 2358 'HTML_QuickForm_html',
7cf5bd55 2359 ],
2360 'hierselect' => [
2b7d3f8a 2361 'HTML/QuickForm/hierselect.php',
21dfd5f5 2362 'HTML_QuickForm_hierselect',
7cf5bd55 2363 ],
2364 'autocomplete' => [
2b7d3f8a 2365 'HTML/QuickForm/autocomplete.php',
21dfd5f5 2366 'HTML_QuickForm_autocomplete',
7cf5bd55 2367 ],
2368 'xbutton' => [
2b7d3f8a 2369 'HTML/QuickForm/xbutton.php',
21dfd5f5 2370 'HTML_QuickForm_xbutton',
7cf5bd55 2371 ],
2372 'advmultiselect' => [
2b7d3f8a 2373 'HTML/QuickForm/advmultiselect.php',
21dfd5f5 2374 'HTML_QuickForm_advmultiselect',
7cf5bd55 2375 ],
2376 ];
2b7d3f8a 2377 }
2378
48e399ac
EM
2379 /**
2380 * Set up an acl allowing contact to see 2 specified groups
c206647d 2381 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
48e399ac 2382 *
c206647d 2383 * You need to have pre-created these groups & created the user e.g
48e399ac
EM
2384 * $this->createLoggedInUser();
2385 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2386 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
ea3ddccf 2387 *
2388 * @param bool $isProfile
48e399ac 2389 */
181f536c 2390 public function setupACL($isProfile = FALSE) {
aaac0e0b
EM
2391 global $_REQUEST;
2392 $_REQUEST = $this->_params;
108ff21a 2393
7cf5bd55 2394 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
2395 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
e5720c45
SL
2396 $ov = new CRM_Core_DAO_OptionValue();
2397 $ov->option_group_id = $optionGroupID;
2398 $ov->value = 55;
2399 if ($ov->find(TRUE)) {
2400 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2401 }
7cf5bd55 2402 $optionValue = $this->callAPISuccess('option_value', 'create', [
5896d037 2403 'option_group_id' => $optionGroupID,
48e399ac
EM
2404 'label' => 'pick me',
2405 'value' => 55,
7cf5bd55 2406 ]);
48e399ac 2407
48e399ac
EM
2408 CRM_Core_DAO::executeQuery("
2409 TRUNCATE civicrm_acl_cache
2410 ");
2411
2412 CRM_Core_DAO::executeQuery("
2413 TRUNCATE civicrm_acl_contact_cache
2414 ");
2415
48e399ac
EM
2416 CRM_Core_DAO::executeQuery("
2417 INSERT INTO civicrm_acl_entity_role (
181f536c 2418 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2419 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
48e399ac
EM
2420 ");
2421
181f536c 2422 if ($isProfile) {
2423 CRM_Core_DAO::executeQuery("
2424 INSERT INTO civicrm_acl (
2425 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2426 )
2427 VALUES (
2428 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2429 );
2430 ");
2431 }
2432 else {
2433 CRM_Core_DAO::executeQuery("
2434 INSERT INTO civicrm_acl (
2435 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2436 )
2437 VALUES (
2438 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2439 );
2440 ");
2441
2442 CRM_Core_DAO::executeQuery("
2443 INSERT INTO civicrm_acl (
2444 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2445 )
2446 VALUES (
2447 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2448 );
2449 ");
181f536c 2450 }
48e399ac 2451
48e399ac 2452 $this->_loggedInUser = CRM_Core_Session::singleton()->get('userID');
7cf5bd55 2453 $this->callAPISuccess('group_contact', 'create', [
48e399ac
EM
2454 'group_id' => $this->_permissionedGroup,
2455 'contact_id' => $this->_loggedInUser,
7cf5bd55 2456 ]);
08a2ea5e 2457
2458 if (!$isProfile) {
2459 //flush cache
2460 CRM_ACL_BAO_Cache::resetCache();
340c24cc 2461 CRM_ACL_API::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
08a2ea5e 2462 }
48e399ac
EM
2463 }
2464
cab024d4 2465 /**
100fef9d 2466 * Alter default price set so that the field numbers are not all 1 (hiding errors)
cab024d4 2467 */
00be9182 2468 public function offsetDefaultPriceSet() {
7cf5bd55 2469 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
cab024d4 2470 $firstID = $contributionPriceSet['id'];
7cf5bd55 2471 $this->callAPISuccess('price_set', 'create', [
92915c55
TO
2472 'id' => $contributionPriceSet['id'],
2473 'is_active' => 0,
2474 'name' => 'old',
7cf5bd55 2475 ]);
cab024d4
EM
2476 unset($contributionPriceSet['id']);
2477 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
7cf5bd55 2478 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
92915c55 2479 'price_set_id' => $firstID,
7cf5bd55 2480 'options' => ['limit' => 1],
2481 ]);
cab024d4
EM
2482 unset($priceField['id']);
2483 $priceField['price_set_id'] = $newPriceSet['id'];
2484 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
7cf5bd55 2485 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
92915c55
TO
2486 'price_set_id' => $firstID,
2487 'sequential' => 1,
7cf5bd55 2488 'options' => ['limit' => 1],
2489 ]);
cab024d4
EM
2490
2491 unset($priceFieldValue['id']);
2492 //create some padding to use up ids
2493 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2494 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
7cf5bd55 2495 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
cab024d4
EM
2496 }
2497
4aef704e 2498 /**
eceb18cc 2499 * Create an instance of the paypal processor.
7cf5bd55 2500 *
4aef704e 2501 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2502 * this parent class & we don't have a structure for that yet
2503 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
e4f46be0 2504 * & the best protection against that is the functions this class affords
7cf5bd55 2505 *
1e1fdcf6 2506 * @param array $params
7cf5bd55 2507 *
79d7553f 2508 * @return int $result['id'] payment processor id
4aef704e 2509 */
7cf5bd55 2510 public function paymentProcessorCreate($params = []) {
2511 $params = array_merge([
39b959db
SL
2512 'name' => 'demo',
2513 'domain_id' => CRM_Core_Config::domainID(),
2514 'payment_processor_type_id' => 'PayPal',
2515 'is_active' => 1,
2516 'is_default' => 0,
2517 'is_test' => 1,
2518 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2519 'password' => '1183377788',
2520 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2521 'url_site' => 'https://www.sandbox.paypal.com/',
2522 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2523 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2524 'class_name' => 'Payment_PayPalImpl',
2525 'billing_mode' => 3,
2526 'financial_type_id' => 1,
2527 'financial_account_id' => 12,
2528 // Credit card = 1 so can pass 'by accident'.
2529 'payment_instrument_id' => 'Debit Card',
7cf5bd55 2530 ], $params);
5896d037 2531 if (!is_numeric($params['payment_processor_type_id'])) {
4aef704e 2532 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2533 //here
7cf5bd55 2534 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
4aef704e 2535 'name' => $params['payment_processor_type_id'],
2536 'return' => 'id',
7cf5bd55 2537 ], 'integer');
4aef704e 2538 }
2539 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2540 return $result['id'];
2541 }
a9ac877b 2542
0dbefed3 2543 /**
eceb18cc 2544 * Set up initial recurring payment allowing subsequent IPN payments.
9f68fe61
MW
2545 *
2546 * @param array $recurParams (Optional)
2547 * @param array $contributionParams (Optional)
7aeb7f06 2548 *
2549 * @throws \CRM_Core_Exception
0dbefed3 2550 */
9f68fe61 2551 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
a64d6127 2552 $this->ids['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
9f68fe61 2553 $contributionParams = array_merge([
39b959db
SL
2554 'total_amount' => '200',
2555 'invoice_id' => $this->_invoiceID,
2556 'financial_type_id' => 'Donation',
2557 'contribution_status_id' => 'Pending',
2558 'contact_id' => $this->_contactID,
2559 'contribution_page_id' => $this->_contributionPageID,
2560 'payment_processor_id' => $this->_paymentProcessorID,
2561 'is_test' => 0,
f03241be 2562 'receive_date' => '2019-07-25 07:34:23',
39b959db 2563 'skipCleanMoney' => TRUE,
a64d6127 2564 'amount_level' => 'expensive',
2565 'campaign_id' => $this->ids['campaign'][0],
2ca321fa 2566 'source' => 'Online Contribution: Page name',
39b959db 2567 ], $contributionParams);
7cf5bd55 2568 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
0dbefed3
EM
2569 'contact_id' => $this->_contactID,
2570 'amount' => 1000,
2571 'sequential' => 1,
2572 'installments' => 5,
2573 'frequency_unit' => 'Month',
2574 'frequency_interval' => 1,
2575 'invoice_id' => $this->_invoiceID,
2576 'contribution_status_id' => 2,
481312d9 2577 'payment_processor_id' => $this->_paymentProcessorID,
2578 // processor provided ID - use contact ID as proxy.
2579 'processor_id' => $this->_contactID,
0dea0c7c 2580 'api.Order.create' => $contributionParams,
2581 ], $recurParams))['values'][0];
0dbefed3 2582 $this->_contributionRecurID = $contributionRecur['id'];
0dea0c7c 2583 $this->_contributionID = $contributionRecur['api.Order.create']['id'];
7aeb7f06 2584 $this->ids['Contribution'][0] = $this->_contributionID;
0dbefed3 2585 }
a86d27fc 2586
a9ac877b 2587 /**
100fef9d 2588 * 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
2589 *
2590 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
7aeb7f06 2591 *
2592 * @throws \CRM_Core_Exception
a9ac877b 2593 */
7cf5bd55 2594 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2595 $membershipParams = $recurParams = [];
b3c283b4
MW
2596 if (!empty($params['duration_unit'])) {
2597 $membershipParams['duration_unit'] = $params['duration_unit'];
2598 }
2599 if (!empty($params['frequency_unit'])) {
2600 $recurParams['frequency_unit'] = $params['frequency_unit'];
2601 }
2602
2603 $this->ids['membership_type'] = $this->membershipTypeCreate($membershipParams);
69140e67 2604 //create a contribution so our membership & contribution don't both have id = 1
0dea0c7c 2605 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
7cf5bd55 2606 $this->contributionCreate([
b6b59c64 2607 'contact_id' => $this->_contactID,
2608 'is_test' => 1,
2609 'financial_type_id' => 1,
2610 'invoice_id' => 'abcd',
2611 'trxn_id' => 345,
f03241be 2612 'receive_date' => '2019-07-25 07:34:23',
7cf5bd55 2613 ]);
b6b59c64 2614 }
69140e67 2615
0dea0c7c 2616 $this->ids['membership'] = $this->callAPISuccess('Membership', 'create', [
a9ac877b
EM
2617 'contact_id' => $this->_contactID,
2618 'membership_type_id' => $this->ids['membership_type'],
a9ac877b 2619 'format.only_id' => TRUE,
f03241be 2620 'source' => 'Payment',
0dea0c7c 2621 'skipLineItem' => TRUE,
7cf5bd55 2622 ]);
0dea0c7c 2623 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2624 'line_items' => [
2625 [
2626 'line_item' => [
2627 [
2628 'entity_table' => 'civicrm_membership',
2629 'entity_id' => $this->ids['membership'],
2630 'label' => 'General',
2631 'qty' => 1,
2632 'unit_price' => 200,
2633 'line_total' => 200,
2634 'financial_type_id' => 1,
2635 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2636 'return' => 'id',
2637 'label' => 'Membership Amount',
2638 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2639 ]),
2640 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2641 'return' => 'id',
2642 'label' => 'General',
2643 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2644 ]),
2645 ],
2646 ],
2647 ],
2648 ],
7cf5bd55 2649 ]);
0dea0c7c 2650 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids['membership'], 'contribution_recur_id' => $this->_contributionRecurID]);
a9ac877b 2651 }
6a488035 2652
4cbe18b8
EM
2653 /**
2654 * @param $message
2655 *
2656 * @throws Exception
a9ac877b 2657 */
00be9182 2658 public function CiviUnitTestCase_fatalErrorHandler($message) {
a9ac877b
EM
2659 throw new Exception("{$message['message']}: {$message['code']}");
2660 }
4aef704e 2661
d67f1f28 2662 /**
eceb18cc 2663 * Wrap the entire test case in a transaction.
d67f1f28
TO
2664 *
2665 * Only subsequent DB statements will be wrapped in TX -- this cannot
2666 * retroactively wrap old DB statements. Therefore, it makes sense to
2667 * call this at the beginning of setUp().
2668 *
2669 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2670 * this option does not work with, e.g., custom-data.
2671 *
2672 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2673 * if TRUNCATE or ALTER is called while using a transaction.
2674 *
e16033b4
TO
2675 * @param bool $nest
2676 * Whether to use nesting or reference-counting.
d67f1f28 2677 */
00be9182 2678 public function useTransaction($nest = TRUE) {
d67f1f28
TO
2679 if (!$this->tx) {
2680 $this->tx = new CRM_Core_Transaction($nest);
2681 $this->tx->rollback();
2682 }
2683 }
a335f6b2 2684
b3c30fda 2685 /**
54957108 2686 * Assert the attachment exists.
2687 *
2688 * @param bool $exists
b3c30fda
CW
2689 * @param array $apiResult
2690 */
2691 protected function assertAttachmentExistence($exists, $apiResult) {
2692 $fileId = $apiResult['id'];
2693 $this->assertTrue(is_numeric($fileId));
2694 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
7cf5bd55 2695 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2696 1 => [$fileId, 'Int'],
2697 ]);
2698 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2699 1 => [$fileId, 'Int'],
2700 ]);
b3c30fda
CW
2701 }
2702
299c1530 2703 /**
2704 * Assert 2 sql strings are the same, ignoring double spaces.
2705 *
2706 * @param string $expectedSQL
2707 * @param string $actualSQL
2708 * @param string $message
2709 */
2710 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2711 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2712 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2713 $this->assertEquals($expected, $actual, $message);
2714 }
2715
c039f658 2716 /**
2717 * Create a price set for an event.
2718 *
2719 * @param int $feeTotal
601c7a24 2720 * @param int $minAmt
f660d2ad 2721 * @param string $type
c039f658 2722 *
2d6f64f4 2723 * @param array $options
2724 *
c039f658 2725 * @return int
2726 * Price Set ID.
74531938 2727 * @throws \CRM_Core_Exception
c039f658 2728 */
2d6f64f4 2729 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
c039f658 2730 // creating price set, price field
2731 $paramsSet['title'] = 'Price Set';
2732 $paramsSet['name'] = CRM_Utils_String::titleToVar('Price Set');
2733 $paramsSet['is_active'] = FALSE;
2734 $paramsSet['extends'] = 1;
601c7a24 2735 $paramsSet['min_amount'] = $minAmt;
c039f658 2736
f660d2ad 2737 $priceSet = CRM_Price_BAO_PriceSet::create($paramsSet);
2738 $this->_ids['price_set'] = $priceSet->id;
c039f658 2739
7cf5bd55 2740 $paramsField = [
c039f658 2741 'label' => 'Price Field',
2742 'name' => CRM_Utils_String::titleToVar('Price Field'),
f660d2ad 2743 'html_type' => $type,
c039f658 2744 'price' => $feeTotal,
7cf5bd55 2745 'option_label' => ['1' => 'Price Field'],
2746 'option_value' => ['1' => $feeTotal],
2747 'option_name' => ['1' => $feeTotal],
2748 'option_weight' => ['1' => 1],
2749 'option_amount' => ['1' => 1],
c039f658 2750 'is_display_amounts' => 1,
2751 'weight' => 1,
2752 'options_per_line' => 1,
7cf5bd55 2753 'is_active' => ['1' => 1],
f660d2ad 2754 'price_set_id' => $this->_ids['price_set'],
c039f658 2755 'is_enter_qty' => 1,
8484a5f0 2756 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
7cf5bd55 2757 ];
f660d2ad 2758 if ($type === 'Radio') {
2d6f64f4 2759 foreach ($options as $index => $option) {
2760 $paramsField['is_enter_qty'] = 0;
2761 $optionID = $index + 2;
2762 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2763 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2764 }
2765
f660d2ad 2766 }
74531938 2767 $this->callAPISuccess('PriceField', 'create', $paramsField);
7cf5bd55 2768 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids['price_set']]);
f660d2ad 2769 $this->_ids['price_field'] = array_keys($fields['values']);
7cf5bd55 2770 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids['price_field'][0]]);
f660d2ad 2771 $this->_ids['price_field_value'] = array_keys($fieldValues['values']);
c039f658 2772
f660d2ad 2773 return $this->_ids['price_set'];
c039f658 2774 }
2775
481312d9 2776 /**
2777 * Add a profile to a contribution page.
2778 *
2779 * @param string $name
2780 * @param int $contributionPageID
5d6cf648 2781 * @param string $module
481312d9 2782 */
5d6cf648
JM
2783 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2784 $params = [
481312d9 2785 'uf_group_id' => $name,
5d6cf648 2786 'module' => $module,
481312d9 2787 'entity_table' => 'civicrm_contribution_page',
2788 'entity_id' => $contributionPageID,
2789 'weight' => 1,
5d6cf648
JM
2790 ];
2791 if ($module !== 'CiviContribute') {
2792 $params['module_data'] = [$module => []];
2793 }
2794 $this->callAPISuccess('UFJoin', 'create', $params);
481312d9 2795 }
2796
db62fd2b
PN
2797 /**
2798 * Add participant with contribution
2799 *
2800 * @return array
f3e6da5e 2801 *
2802 * @throws \CRM_Core_Exception
db62fd2b 2803 */
5266bd48 2804 protected function createPartiallyPaidParticipantOrder() {
2805 $orderParams = $this->getParticipantOrderParams();
2806 $orderParams['api.Payment.create'] = ['total_amount' => 150];
f3e6da5e 2807 return $this->callAPISuccess('Order', 'create', $orderParams);
db62fd2b
PN
2808 }
2809
73c0e107
PN
2810 /**
2811 * Create price set
2812 *
2813 * @param string $component
2814 * @param int $componentId
39b959db 2815 * @param array $priceFieldOptions
73c0e107
PN
2816 *
2817 * @return array
2818 */
7cf5bd55 2819 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
5c3d600f
PN
2820 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2821 $paramsSet['name'] = CRM_Utils_String::titleToVar($paramsSet['title']);
73c0e107 2822 $paramsSet['is_active'] = TRUE;
faba82fc 2823 $paramsSet['financial_type_id'] = 'Event Fee';
73c0e107
PN
2824 $paramsSet['extends'] = 1;
2825 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2826 $priceSetId = $priceSet['id'];
2827 //Checking for priceset added in the table.
2828 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2829 'id', $paramsSet['title'], 'Check DB for created priceset'
2830 );
7cf5bd55 2831 $paramsField = array_merge([
73c0e107
PN
2832 'label' => 'Price Field',
2833 'name' => CRM_Utils_String::titleToVar('Price Field'),
2834 'html_type' => 'CheckBox',
7cf5bd55 2835 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2836 'option_value' => ['1' => 100, '2' => 200],
2837 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2838 'option_weight' => ['1' => 1, '2' => 2],
2839 'option_amount' => ['1' => 100, '2' => 200],
73c0e107
PN
2840 'is_display_amounts' => 1,
2841 'weight' => 1,
2842 'options_per_line' => 1,
7cf5bd55 2843 'is_active' => ['1' => 1, '2' => 1],
73c0e107
PN
2844 'price_set_id' => $priceSet['id'],
2845 'is_enter_qty' => 1,
8484a5f0 2846 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
7cf5bd55 2847 ], $priceFieldOptions);
c91b1cc3 2848
73c0e107
PN
2849 $priceField = CRM_Price_BAO_PriceField::create($paramsField);
2850 if ($componentId) {
2851 CRM_Price_BAO_PriceSet::addTo('civicrm_' . $component, $componentId, $priceSetId);
2852 }
7cf5bd55 2853 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id]);
73c0e107
PN
2854 }
2855
b80f2ad1
E
2856 /**
2857 * Replace the template with a test-oriented template designed to show all the variables.
2858 *
2859 * @param string $templateName
7a2ee417 2860 * @param string $type
b80f2ad1 2861 */
7a2ee417 2862 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2863 $testTemplate = file_get_contents(__DIR__ . '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
b80f2ad1 2864 CRM_Core_DAO::executeQuery(
fa839a68 2865 "UPDATE civicrm_msg_template
2866 SET msg_{$type} = %1
2867 WHERE workflow_name = '{$templateName}'
2868 AND is_default = 1", [1 => [$testTemplate, 'String']]
b80f2ad1
E
2869 );
2870 }
2871
2872 /**
2873 * Reinstate the default template.
2874 *
2875 * @param string $templateName
7a2ee417 2876 * @param string $type
b80f2ad1 2877 */
7a2ee417 2878 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
b80f2ad1
E
2879 CRM_Core_DAO::executeQuery(
2880 "UPDATE civicrm_option_group og
2881 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2882 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2883 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
7a2ee417 2884 SET m.msg_{$type} = m2.msg_{$type}
b80f2ad1
E
2885 WHERE og.name = 'msg_tpl_workflow_contribution'
2886 AND ov.name = '{$templateName}'
2887 AND m.is_default = 1"
2888 );
2889 }
2890
8d35246a
EM
2891 /**
2892 * Flush statics relating to financial type.
2893 */
2894 protected function flushFinancialTypeStatics() {
2895 if (isset(\Civi::$statics['CRM_Financial_BAO_FinancialType'])) {
2896 unset(\Civi::$statics['CRM_Financial_BAO_FinancialType']);
2897 }
4954339a
EM
2898 if (isset(\Civi::$statics['CRM_Contribute_PseudoConstant'])) {
2899 unset(\Civi::$statics['CRM_Contribute_PseudoConstant']);
2900 }
8d35246a 2901 CRM_Contribute_PseudoConstant::flush('financialType');
4954339a
EM
2902 CRM_Contribute_PseudoConstant::flush('membershipType');
2903 // Pseudoconstants may be saved to the cache table.
2904 CRM_Core_DAO::executeQuery("TRUNCATE civicrm_cache");
7cf5bd55 2905 CRM_Financial_BAO_FinancialType::$_statusACLFt = [];
8d35246a
EM
2906 CRM_Financial_BAO_FinancialType::$_availableFinancialTypes = NULL;
2907 }
2908
2909 /**
2910 * Set the permissions to the supplied array.
2911 *
2912 * @param array $permissions
2913 */
2914 protected function setPermissions($permissions) {
2915 CRM_Core_Config::singleton()->userPermissionClass->permissions = $permissions;
2916 $this->flushFinancialTypeStatics();
2917 }
2918
bf722049 2919 /**
2920 * @param array $params
2921 * @param $context
2922 */
2923 public function _checkFinancialRecords($params, $context) {
7cf5bd55 2924 $entityParams = [
bf722049 2925 'entity_id' => $params['id'],
2926 'entity_table' => 'civicrm_contribution',
7cf5bd55 2927 ];
2928 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
bf722049 2929 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2930 if ($context == 'pending') {
2931 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
2932 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2933 return;
2934 }
2935 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
7cf5bd55 2936 $trxnParams = [
bf722049 2937 'id' => $trxn['financial_trxn_id'],
7cf5bd55 2938 ];
bf722049 2939 if ($context != 'online' && $context != 'payLater') {
7cf5bd55 2940 $compareParams = [
bf722049 2941 'to_financial_account_id' => 6,
2f3d0664 2942 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2943 'status_id' => 1,
7cf5bd55 2944 ];
bf722049 2945 }
2946 if ($context == 'feeAmount') {
2947 $compareParams['fee_amount'] = 50;
2948 }
2949 elseif ($context == 'online') {
7cf5bd55 2950 $compareParams = [
bf722049 2951 'to_financial_account_id' => 12,
2f3d0664 2952 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2953 'status_id' => 1,
f69a9ac3 2954 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, 1),
7cf5bd55 2955 ];
bf722049 2956 }
2957 elseif ($context == 'payLater') {
7cf5bd55 2958 $compareParams = [
bf722049 2959 'to_financial_account_id' => 7,
2f3d0664 2960 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2961 'status_id' => 2,
7cf5bd55 2962 ];
bf722049 2963 }
2964 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
7cf5bd55 2965 $entityParams = [
bf722049 2966 'financial_trxn_id' => $trxn['financial_trxn_id'],
2967 'entity_table' => 'civicrm_financial_item',
7cf5bd55 2968 ];
bf722049 2969 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
7cf5bd55 2970 $fitemParams = [
bf722049 2971 'id' => $entityTrxn['entity_id'],
7cf5bd55 2972 ];
2973 $compareParams = [
2f3d0664 2974 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2975 'status_id' => 1,
1a1a2f4f 2976 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
7cf5bd55 2977 ];
bf722049 2978 if ($context == 'payLater') {
7cf5bd55 2979 $compareParams = [
2f3d0664 2980 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
bf722049 2981 'status_id' => 3,
1a1a2f4f 2982 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
7cf5bd55 2983 ];
bf722049 2984 }
2985 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2986 if ($context == 'feeAmount') {
7cf5bd55 2987 $maxParams = [
bf722049 2988 'entity_id' => $params['id'],
2989 'entity_table' => 'civicrm_contribution',
7cf5bd55 2990 ];
bf722049 2991 $maxTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($maxParams, TRUE));
7cf5bd55 2992 $trxnParams = [
bf722049 2993 'id' => $maxTrxn['financial_trxn_id'],
7cf5bd55 2994 ];
2995 $compareParams = [
bf722049 2996 'to_financial_account_id' => 5,
2997 'from_financial_account_id' => 6,
2998 'total_amount' => 50,
2999 'status_id' => 1,
7cf5bd55 3000 ];
bf722049 3001 $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['id'], 'DESC');
3002 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
7cf5bd55 3003 $fitemParams = [
bf722049 3004 'entity_id' => $trxnId['financialTrxnId'],
3005 'entity_table' => 'civicrm_financial_trxn',
7cf5bd55 3006 ];
3007 $compareParams = [
2f3d0664 3008 'amount' => 50.00,
bf722049 3009 'status_id' => 1,
3010 'financial_account_id' => 5,
7cf5bd55 3011 ];
bf722049 3012 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
3013 }
3014 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
3015 // line should be copied into all the functions that call this function & evaluated there
3016 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
3017 // when calling completeTransaction or repeatTransaction.
7cf5bd55 3018 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
bf722049 3019 }
3020
8484a5f0 3021 /**
3022 * Return financial type id on basis of name
3023 *
3024 * @param string $name Financial type m/c name
3025 *
3026 * @return int
3027 */
3028 public function getFinancialTypeId($name) {
3029 return CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
3030 }
3031
204beda3 3032 /**
3033 * Cleanup function for contents of $this->ids.
3034 *
3035 * This is a best effort cleanup to use in tear downs etc.
3036 *
3037 * It will not fail if the data has already been removed (some tests may do
3038 * their own cleanup).
3039 */
3040 protected function cleanUpSetUpIDs() {
3041 foreach ($this->setupIDs as $entity => $id) {
3042 try {
7cf5bd55 3043 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
204beda3 3044 }
3045 catch (CiviCRM_API3_Exception $e) {
3046 // This is a best-effort cleanup function, ignore.
3047 }
3048 }
3049 }
3050
adbc354b
PN
3051 /**
3052 * Create Financial Type.
3053 *
3054 * @param array $params
3055 *
3056 * @return array
3057 */
7cf5bd55 3058 protected function createFinancialType($params = []) {
adbc354b 3059 $params = array_merge($params,
7cf5bd55 3060 [
adbc354b
PN
3061 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
3062 'is_active' => 1,
7cf5bd55 3063 ]
adbc354b
PN
3064 );
3065 return $this->callAPISuccess('FinancialType', 'create', $params);
3066 }
3067
e1c5a855 3068 /**
3069 * Create Payment Instrument.
3070 *
3071 * @param array $params
3072 * @param string $financialAccountName
3073 *
3074 * @return int
3075 */
7cf5bd55 3076 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3077 $params = array_merge([
e1c5a855 3078 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3079 'option_group_id' => 'payment_instrument',
3080 'is_active' => 1,
7cf5bd55 3081 ], $params);
897ff8c5 3082 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
e1c5a855 3083
3084 $relationTypeID = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3085
3086 $financialAccountParams = [
3087 'entity_table' => 'civicrm_option_value',
897ff8c5 3088 'entity_id' => $newPaymentInstrument,
e1c5a855 3089 'account_relationship' => $relationTypeID,
3090 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3091 ];
3092 CRM_Financial_BAO_FinancialTypeAccount::add($financialAccountParams);
3093
3094 return CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3095 }
3096
ec5da26a 3097 /**
3098 * Enable Tax and Invoicing
b086dc3a 3099 *
3100 * @param array $params
3101 *
3102 * @return \Civi\Core\SettingsBag
ec5da26a 3103 */
7cf5bd55 3104 protected function enableTaxAndInvoicing($params = []) {
ec5da26a 3105 // Enable component contribute setting
3106 $contributeSetting = array_merge($params,
7cf5bd55 3107 [
ec5da26a 3108 'invoicing' => 1,
3109 'invoice_prefix' => 'INV_',
ec5da26a 3110 'due_date' => 10,
3111 'due_date_period' => 'days',
3112 'notes' => '',
3113 'is_email_pdf' => 1,
3114 'tax_term' => 'Sales Tax',
3115 'tax_display_settings' => 'Inclusive',
7cf5bd55 3116 ]
ec5da26a 3117 );
3118 return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
3119 }
3120
9fbf312f 3121 /**
3122 * Enable Tax and Invoicing
7aa78908 3123 *
3124 * @throws \CRM_Core_Exception
9fbf312f 3125 */
9db37f9f 3126 protected function disableTaxAndInvoicing(): \Civi\Core\SettingsBag {
7aa78908 3127 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3128 foreach ($accounts as $account) {
3129 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3130 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3131 }
3132
f436577a 3133 if (!empty(\Civi::$statics['CRM_Core_PseudoConstant']) && isset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3134 unset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates']);
3135 }
7aa78908 3136 return Civi::settings()->set('invoicing', FALSE);
9fbf312f 3137 }
3138
7a1f3919 3139 /**
28d44c71 3140 * Add Sales Tax Account for the financial type.
7a1f3919
PN
3141 *
3142 * @param int $financialTypeId
3143 *
28d44c71 3144 * @param array $accountParams
3145 *
3146 * @return CRM_Financial_DAO_EntityFinancialAccount
3147 * @throws \CRM_Core_Exception
7a1f3919 3148 */
28d44c71 3149 protected function addTaxAccountToFinancialType(int $financialTypeId, $accountParams = []) {
3150 $params = array_merge([
7a1f3919
PN
3151 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3152 'financial_account_type_id' => key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3153 'is_deductible' => 1,
3154 'is_tax' => 1,
3155 'tax_rate' => 10,
3156 'is_active' => 1,
28d44c71 3157 ], $accountParams);
7a1f3919 3158 $account = CRM_Financial_BAO_FinancialAccount::add($params);
7cf5bd55 3159 $entityParams = [
7a1f3919 3160 'entity_table' => 'civicrm_financial_type',
7a1f3919 3161 'entity_id' => $financialTypeId,
5b3543ce 3162 'account_relationship' => key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
7cf5bd55 3163 ];
5b3543ce 3164
f436577a 3165 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
28d44c71 3166 \Civi::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = $params['tax_rate'];
f436577a 3167
5b3543ce
JM
3168 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3169 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3170 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3171 $dao->copyValues($entityParams);
3172 $dao->find();
3173 if ($dao->fetch()) {
3174 $entityParams['id'] = $dao->id;
3175 }
3176 $entityParams['financial_account_id'] = $account->id;
3177
7a1f3919
PN
3178 return CRM_Financial_BAO_FinancialTypeAccount::add($entityParams);
3179 }
3180
65e172a3 3181 /**
3182 * Create price set with contribution test for test setup.
3183 *
3184 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3185 * on parent class at some point (fn is not in 4.4).
3186 *
3187 * @param $entity
3188 * @param array $params
3189 */
7cf5bd55 3190 public function createPriceSetWithPage($entity = NULL, $params = []) {
3191 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3192 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
65e172a3 3193 'title' => "Test Contribution Page",
3194 'financial_type_id' => 1,
3195 'currency' => 'NZD',
3196 'goal_amount' => 50,
3197 'is_pay_later' => 1,
3198 'is_monetary' => TRUE,
3199 'is_email_receipt' => FALSE,
7cf5bd55 3200 ]);
3201 $priceSet = $this->callAPISuccess('price_set', 'create', [
65e172a3 3202 'is_quick_config' => 0,
3203 'extends' => 'CiviMember',
3204 'financial_type_id' => 1,
3205 'title' => 'my Page',
7cf5bd55 3206 ]);
65e172a3 3207 $priceSetID = $priceSet['id'];
3208
3209 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
7cf5bd55 3210 $priceField = $this->callAPISuccess('price_field', 'create', [
65e172a3 3211 'price_set_id' => $priceSetID,
3212 'label' => 'Goat Breed',
3213 'html_type' => 'Radio',
7cf5bd55 3214 ]);
3215 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3216 'price_set_id' => $priceSetID,
3217 'price_field_id' => $priceField['id'],
3218 'label' => 'Long Haired Goat',
3219 'amount' => 20,
3220 'financial_type_id' => 'Donation',
3221 'membership_type_id' => $membershipTypeID,
3222 'membership_num_terms' => 1,
7cf5bd55 3223 ]);
3224 $this->_ids['price_field_value'] = [$priceFieldValue['id']];
3225 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3226 'price_set_id' => $priceSetID,
3227 'price_field_id' => $priceField['id'],
3228 'label' => 'Shoe-eating Goat',
3229 'amount' => 10,
3230 'financial_type_id' => 'Donation',
3231 'membership_type_id' => $membershipTypeID,
3232 'membership_num_terms' => 2,
7cf5bd55 3233 ]);
65e172a3 3234 $this->_ids['price_field_value'][] = $priceFieldValue['id'];
3235
7cf5bd55 3236 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3237 'price_set_id' => $priceSetID,
3238 'price_field_id' => $priceField['id'],
3239 'label' => 'Shoe-eating Goat',
3240 'amount' => 10,
3241 'financial_type_id' => 'Donation',
7cf5bd55 3242 ]);
65e172a3 3243 $this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];
3244
3245 $this->_ids['price_set'] = $priceSetID;
3246 $this->_ids['contribution_page'] = $contributionPageResult['id'];
7cf5bd55 3247 $this->_ids['price_field'] = [$priceField['id']];
65e172a3 3248
3249 $this->_ids['membership_type'] = $membershipTypeID;
3250 }
3251
3c9d67b0 3252 /**
3253 * Only specified contact returned.
7cf5bd55 3254 *
3c9d67b0 3255 * @implements CRM_Utils_Hook::aclWhereClause
7cf5bd55 3256 *
3c9d67b0 3257 * @param $type
3258 * @param $tables
3259 * @param $whereTables
3260 * @param $contactID
3261 * @param $where
3262 */
3263 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3264 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts) . ")";
3265 }
3266
88ebed7c
JP
3267 /**
3268 * @implements CRM_Utils_Hook::selectWhereClause
3269 *
3270 * @param string $entity
3271 * @param array $clauses
3272 */
3273 public function selectWhereClauseHook($entity, &$clauses) {
3274 if ($entity == 'Event') {
3275 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3276 }
3277 }
3278
29a59599
JP
3279 /**
3280 * An implementation of hook_civicrm_post used with all our test cases.
3281 *
3282 * @param $op
3283 * @param string $objectName
3284 * @param int $objectId
3285 * @param $objectRef
3286 */
3287 public function onPost($op, $objectName, $objectId, &$objectRef) {
3288 if ($op == 'create' && $objectName == 'Individual') {
3289 CRM_Core_DAO::executeQuery(
3290 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
7cf5bd55 3291 [
3292 1 => [$objectId, 'Integer'],
3293 ]
29a59599
JP
3294 );
3295 }
3296
3297 if ($op == 'edit' && $objectName == 'Participant') {
7cf5bd55 3298 $params = [
3299 1 => [$objectId, 'Integer'],
3300 ];
29a59599
JP
3301 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3302 CRM_Core_DAO::executeQuery($query, $params);
3303 }
3304 }
3305
f8df7165 3306 /**
3307 * Instantiate form object.
3308 *
3309 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3310 *
3311 * @param string $class
3312 * Name of form class.
3313 *
4715d267 3314 * @param array $formValues
3315 *
3316 * @param string $pageName
3317 *
f8df7165 3318 * @return \CRM_Core_Form
4715d267 3319 * @throws \CRM_Core_Exception
f8df7165 3320 */
4715d267 3321 public function getFormObject($class, $formValues = [], $pageName = '') {
6986e4f1 3322 $_POST = $formValues;
9b9335ed 3323 /* @var CRM_Core_Form $form */
f8df7165 3324 $form = new $class();
3325 $_SERVER['REQUEST_METHOD'] = 'GET';
6986e4f1 3326 switch ($class) {
3327 case 'CRM_Event_Cart_Form_Checkout_Payment':
3328 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3329 $form->controller = new CRM_Event_Cart_Controller_Checkout();
3330 break;
3331
3332 default:
3333 $form->controller = new CRM_Core_Controller();
3334 }
3335 if (!$pageName) {
9b9335ed 3336 $pageName = $form->getName();
6986e4f1 3337 }
4715d267 3338 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3339 $_SESSION['_' . $form->controller->_name . '_container']['values'][$pageName] = $formValues;
f8df7165 3340 return $form;
3341 }
3342
83644f47 3343 /**
3344 * Get possible thousand separators.
3345 *
3346 * @return array
3347 */
3348 public function getThousandSeparators() {
7cf5bd55 3349 return [['.'], [',']];
83644f47 3350 }
3351
ebebd629 3352 /**
3353 * Get the boolean options as a provider.
3354 *
3355 * @return array
3356 */
3357 public function getBooleanDataProvider() {
3358 return [[TRUE], [FALSE]];
3359 }
3360
83644f47 3361 /**
3362 * Set the separators for thousands and decimal points.
3363 *
34e322db 3364 * Note that this only covers some common scenarios.
3365 *
3366 * It does not cater for a situation where the thousand separator is a [space]
3367 * Latter is the Norwegian localization. At least some tests need to
3368 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3369 * to provide broader coverage.
dff93dda 3370 *
83644f47 3371 * @param string $thousandSeparator
3372 */
3373 protected function setCurrencySeparators($thousandSeparator) {
3374 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
34e322db 3375 Civi::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ? '.' : ','));
83644f47 3376 }
3377
dff93dda
JJ
3378 /**
3379 * Sets the thousand separator.
3380 *
3381 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3382 *
3383 * @param $thousandSeparator
3384 */
3385 protected function setMonetaryThousandSeparator($thousandSeparator) {
3386 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3387 }
3388
3389 /**
3390 * Sets the decimal separator.
3391 *
3392 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3393 *
80da79bb 3394 * @param $decimalPoint
dff93dda
JJ
3395 */
3396 protected function setMonetaryDecimalPoint($decimalPoint) {
3397 Civi::settings()->set('monetaryDecimalPoint', $decimalPoint);
3398 }
3399
3400 /**
3401 * Sets the default currency.
3402 *
3403 * @param $currency
3404 */
3405 protected function setDefaultCurrency($currency) {
3406 Civi::settings()->set('defaultCurrency', $currency);
3407 }
3408
83644f47 3409 /**
3410 * Format money as it would be input.
3411 *
3412 * @param string $amount
3413 *
3414 * @return string
3415 */
3416 protected function formatMoneyInput($amount) {
3417 return CRM_Utils_Money::format($amount, NULL, '%a');
3418 }
3419
3ca4bd1b 3420 /**
3421 * Get the contribution object.
3422 *
3423 * @param int $contributionID
3424 *
3425 * @return \CRM_Contribute_BAO_Contribution
3426 */
3427 protected function getContributionObject($contributionID) {
3428 $contributionObj = new CRM_Contribute_BAO_Contribution();
3429 $contributionObj->id = $contributionID;
3430 $contributionObj->find(TRUE);
3431 return $contributionObj;
3432 }
3433
df3320dc 3434 /**
3435 * Enable multilingual.
3436 */
3437 public function enableMultilingual() {
7cf5bd55 3438 $this->callAPISuccess('Setting', 'create', [
df3320dc 3439 'lcMessages' => 'en_US',
7cf5bd55 3440 'languageLimit' => [
df3320dc 3441 'en_US' => 1,
7cf5bd55 3442 ],
3443 ]);
df3320dc 3444
3445 CRM_Core_I18n_Schema::makeMultilingual('en_US');
3446
3447 global $dbLocale;
3448 $dbLocale = '_en_US';
3449 }
3450
51c566a3
SL
3451 /**
3452 * Setup or clean up SMS tests
7cf5bd55 3453 *
51c566a3
SL
3454 * @param bool $teardown
3455 *
3456 * @throws \CiviCRM_API3_Exception
3457 */
3458 public function setupForSmsTests($teardown = FALSE) {
3459 require_once 'CiviTest/CiviTestSMSProvider.php';
3460
3461 // Option value params for CiviTestSMSProvider
3462 $groupID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
7cf5bd55 3463 $params = [
51c566a3
SL
3464 'option_group_id' => $groupID,
3465 'label' => 'unittestSMS',
3466 'value' => 'unit.test.sms',
7cf5bd55 3467 'name' => 'CiviTestSMSProvider',
51c566a3 3468 'is_default' => 1,
7cf5bd55 3469 'is_active' => 1,
3470 'version' => 3,
3471 ];
51c566a3
SL
3472
3473 if ($teardown) {
3474 // Test completed, delete provider
3475 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
7cf5bd55 3476 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
51c566a3
SL
3477 return;
3478 }
3479
3480 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3481 // in CRM/Sms/Provider.php even though it is not an extension.
3482 return civicrm_api3('option_value', 'create', $params);
3483 }
3484
68989e71 3485 /**
3486 * Start capturing browser output.
3487 *
3488 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3489 */
3490 protected function startCapturingOutput() {
3491 ob_start();
3492 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3493 }
3494
3495 /**
3496 * Stop capturing browser output and return as a csv.
3497 *
3498 * @param bool $isFirstRowHeaders
3499 *
3500 * @return \League\Csv\Reader
3501 *
3502 * @throws \League\Csv\Exception
3503 */
3504 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3505 $output = ob_get_flush();
3506 $stream = fopen('php://memory', 'r+');
3507 fwrite($stream, $output);
3508 rewind($stream);
1fc9f4a7 3509 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
68989e71 3510 $csv = Reader::createFromString($output);
3511 if ($isFirstRowHeaders) {
3512 $csv->setHeaderOffset(0);
3513 }
3514 ob_clean();
3515 return $csv;
3516 }
3517
91786f44 3518 /**
3519 * Rename various labels to not match the names.
3520 *
3521 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3522 * code that expects it to.
3523 */
3524 protected function renameLabels() {
3525 $replacements = ['Pending', 'Refunded'];
3526 foreach ($replacements as $name) {
3527 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3528 }
3529 }
3530
3531 /**
3532 * Undo any label renaming.
3533 */
3534 protected function resetLabels() {
3535 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3536 }
3537
5266bd48 3538 /**
3539 * Get parameters to set up a multi-line participant order.
3540 *
3541 * @return array
3542 * @throws \CRM_Core_Exception
3543 */
3544 protected function getParticipantOrderParams(): array {
3545 $this->_contactId = $this->individualCreate();
3546 $event = $this->eventCreate();
3547 $this->_eventId = $event['id'];
3548 $eventParams = [
3549 'id' => $this->_eventId,
3550 'financial_type_id' => 4,
3551 'is_monetary' => 1,
3552 ];
3553 $this->callAPISuccess('event', 'create', $eventParams);
3554 $priceFields = $this->createPriceSet('event', $this->_eventId);
3555 $participantParams = [
3556 'financial_type_id' => 4,
3557 'event_id' => $this->_eventId,
3558 'role_id' => 1,
3559 'status_id' => 14,
3560 'fee_currency' => 'USD',
3561 'contact_id' => $this->_contactId,
3562 ];
3563 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3564 $orderParams = [
3565 'total_amount' => 300,
3566 'currency' => 'USD',
3567 'contact_id' => $this->_contactId,
3568 'financial_type_id' => 4,
3569 'contribution_status_id' => 'Pending',
3570 'contribution_mode' => 'participant',
3571 'participant_id' => $participant['id'],
3572 ];
3573 foreach ($priceFields['values'] as $key => $priceField) {
3574 $orderParams['line_items'][] = [
3575 'line_item' => [
3576 [
3577 'price_field_id' => $priceField['price_field_id'],
3578 'price_field_value_id' => $priceField['id'],
3579 'label' => $priceField['label'],
3580 'field_title' => $priceField['label'],
3581 'qty' => 1,
3582 'unit_price' => $priceField['amount'],
3583 'line_total' => $priceField['amount'],
3584 'financial_type_id' => $priceField['financial_type_id'],
3585 'entity_table' => 'civicrm_participant',
3586 ],
3587 ],
3588 'params' => $participant,
3589 ];
3590 }
3591 return $orderParams;
3592 }
3593
3d4f6f65 3594 /**
3595 * @param $payments
3596 *
3597 * @throws \CRM_Core_Exception
3598 */
33ed891b 3599 protected function validatePayments($payments): void {
3d4f6f65 3600 foreach ($payments as $payment) {
4804f442 3601 $balance = CRM_Contribute_BAO_Contribution::getContributionBalance($payment['contribution_id']);
3602 if ($balance < 0 && $balance + $payment['total_amount'] === 0.0) {
3603 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3604 // This is a pretty rough way at guessing which payment is the overpayment - but
3605 // for the test suite it should be enough.
3606 continue;
3607 }
3d4f6f65 3608 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3609 'financial_trxn_id' => $payment['id'],
3610 'entity_table' => 'civicrm_financial_item',
3611 'return' => ['amount'],
3612 ])['values'];
3613 $itemTotal = 0;
3614 foreach ($items as $item) {
3615 $itemTotal += $item['amount'];
3616 }
3617 $this->assertEquals($payment['total_amount'], $itemTotal);
3618 }
3619 }
3620
3621 /**
3622 * Validate all created payments.
3623 *
3624 * @throws \CRM_Core_Exception
3625 */
3626 protected function validateAllPayments() {
3627 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3628 $this->validatePayments($payments);
3629 }
3630
7aeb7f06 3631 /**
3632 * Validate all created contributions.
3633 *
3634 * @throws \CRM_Core_Exception
3635 */
33ed891b 3636 protected function validateAllContributions(): void {
58315149 3637 $contributions = $this->callAPISuccess('Contribution', 'get', ['return' => ['tax_amount', 'total_amount']])['values'];
7aeb7f06 3638 foreach ($contributions as $contribution) {
3639 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3640 $total = 0;
58315149 3641 $taxTotal = 0;
7aeb7f06 3642 foreach ($lineItems as $lineItem) {
3643 $total += $lineItem['line_total'];
58315149 3644 $taxTotal += (float) ($lineItem['tax_amount'] ?? 0);
7aeb7f06 3645 }
58315149 3646 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ?? 0));
53c8b1be 3647 $this->assertEquals($total + $taxTotal, $contribution['total_amount']);
7aeb7f06 3648 }
3649 }
3650
d943e78d 3651 /**
62ee752a
JG
3652 * @return array|int
3653 * @throws \CRM_Core_Exception
3654 */
3655 protected function createRuleGroup() {
3656 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3657 'contact_type' => 'Individual',
3658 'threshold' => 8,
3659 'used' => 'General',
3660 'name' => 'TestRule',
3661 'title' => 'TestRule',
3662 'is_reserved' => 0,
3663 ]);
3664 return $ruleGroup;
3665 }
3666
3667 /**
d943e78d 3668 * Generic create test.
3669 *
3670 * @param int $version
3671 *
3672 * @throws \CRM_Core_Exception
3673 */
3674 protected function basicCreateTest(int $version) {
3675 $this->_apiversion = $version;
3676 $result = $this->callAPIAndDocument($this->_entity, 'create', $this->params, __FUNCTION__, __FILE__);
3677 $this->assertEquals(1, $result['count']);
3678 $this->assertNotNull($result['values'][$result['id']]['id']);
3679 $this->getAndCheck($this->params, $result['id'], $this->_entity);
3680 }
3681
3682 /**
3683 * Generic delete test.
3684 *
3685 * @param int $version
3686 *
3687 * @throws \CRM_Core_Exception
3688 */
3689 protected function basicDeleteTest($version) {
3690 $this->_apiversion = $version;
3691 $result = $this->callAPISuccess($this->_entity, 'create', $this->params);
3692 $deleteParams = ['id' => $result['id']];
3693 $this->callAPIAndDocument($this->_entity, 'delete', $deleteParams, __FUNCTION__, __FILE__);
3694 $checkDeleted = $this->callAPISuccess($this->_entity, 'get', []);
3695 $this->assertEquals(0, $checkDeleted['count']);
3696 }
3697
7580050c 3698 /**
3699 * Create and return a case object for the given Client ID.
3700 *
3701 * @param int $clientId
3702 * @param int $loggedInUser
3703 * Omit or pass NULL to use the same as clientId
3704 * @param array $extra
3705 * Optional specific parameters such as start_date
3706 *
3707 * @return CRM_Case_BAO_Case
3708 */
76e84f47 3709 public function createCase($clientId, $loggedInUser = NULL, $extra = []) {
7580050c 3710 if (empty($loggedInUser)) {
3711 // backwards compatibility - but it's more typical that the creator is a different person than the client
3712 $loggedInUser = $clientId;
3713 }
76e84f47 3714 $caseParams = array_merge([
7580050c 3715 'activity_subject' => 'Case Subject',
3716 'client_id' => $clientId,
3717 'case_type_id' => 1,
3718 'status_id' => 1,
3719 'case_type' => 'housing_support',
3720 'subject' => 'Case Subject',
76e84f47 3721 'start_date' => date("Y-m-d"),
3722 'start_date_time' => date("YmdHis"),
7580050c 3723 'medium_id' => 2,
3724 'activity_details' => '',
76e84f47 3725 ], $extra);
7580050c 3726 $form = new CRM_Case_Form_Case();
f04b3586 3727 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3728 }
3729
3730 /**
3731 * Validate that all location entities have exactly one primary.
3732 *
3733 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3734 */
3735 public function assertLocationValidity() {
3736 $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT COUNT(*) FROM
3737
3738(SELECT a1.contact_id
3739FROM civicrm_address a1
3740 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3741 AND a1.contact_id = a2.contact_id
3742WHERE
3743 a1.is_primary = 1
3744 AND a2.id IS NOT NULL
3745 AND a1.contact_id IS NOT NULL
3746UNION
3747SELECT a1.contact_id
3748FROM civicrm_address a1
3749 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3750 AND a1.contact_id = a2.contact_id
3751WHERE a1.is_primary = 0
3752 AND a2.id IS NULL
3753 AND a1.contact_id IS NOT NULL
3754
3755UNION
3756
3757SELECT a1.contact_id
3758FROM civicrm_email a1
3759 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3760 AND a1.contact_id = a2.contact_id
3761WHERE
3762 a1.is_primary = 1
3763 AND a2.id IS NOT NULL
3764 AND a1.contact_id IS NOT NULL
3765UNION
3766SELECT a1.contact_id
3767FROM civicrm_email a1
3768 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3769 AND a1.contact_id = a2.contact_id
3770WHERE a1.is_primary = 0
3771 AND a2.id IS NULL
3772 AND a1.contact_id IS NOT NULL
3773
3774UNION
3775
3776SELECT a1.contact_id
3777FROM civicrm_phone a1
3778 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3779 AND a1.contact_id = a2.contact_id
3780WHERE
3781 a1.is_primary = 1
3782 AND a2.id IS NOT NULL
3783 AND a1.contact_id IS NOT NULL
3784UNION
3785SELECT a1.contact_id
3786FROM civicrm_phone a1
3787 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3788 AND a1.contact_id = a2.contact_id
3789WHERE a1.is_primary = 0
3790 AND a2.id IS NULL
3791 AND a1.contact_id IS NOT NULL
3792
3793UNION
3794
3795SELECT a1.contact_id
3796FROM civicrm_im a1
3797 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3798 AND a1.contact_id = a2.contact_id
3799WHERE
3800 a1.is_primary = 1
3801 AND a2.id IS NOT NULL
3802 AND a1.contact_id IS NOT NULL
3803UNION
3804SELECT a1.contact_id
3805FROM civicrm_im a1
3806 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3807 AND a1.contact_id = a2.contact_id
3808WHERE a1.is_primary = 0
3809 AND a2.id IS NULL
3810 AND a1.contact_id IS NOT NULL
3811
3812UNION
3813
3814SELECT a1.contact_id
3815FROM civicrm_openid a1
3816 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3817 AND a1.contact_id = a2.contact_id
3818WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3819UNION
3820
3821SELECT a1.contact_id
3822FROM civicrm_openid a1
3823 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3824 AND a1.contact_id = a2.contact_id
3825WHERE
3826 a1.is_primary = 1
3827 AND a2.id IS NOT NULL
3828 AND a1.contact_id IS NOT NULL
3829UNION
3830SELECT a1.contact_id
3831FROM civicrm_openid a1
3832 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3833 AND a1.contact_id = a2.contact_id
3834WHERE a1.is_primary = 0
3835 AND a2.id IS NULL
3836 AND a1.contact_id IS NOT NULL) as primary_descrepancies
3837 '));
7580050c 3838 }
3839
e7aa5b6b 3840 /**
3841 * Ensure the specified mysql mode/s are activated.
3842 *
3843 * @param array $modes
3844 */
3845 protected function ensureMySQLMode(array $modes): void {
3846 $currentModes = array_fill_keys(CRM_Utils_SQL::getSqlModes(), 1);
3847 $currentModes = array_merge($currentModes, array_fill_keys($modes, 1));
3848 CRM_Core_DAO::executeQuery("SET GLOBAL sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
5dc24de0 3849 CRM_Core_DAO::executeQuery("SET sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
e7aa5b6b 3850 }
3851
9203ed4b
EM
3852 /**
3853 * Delete any extraneous relationship types.
3854 *
3855 * @throws \API_Exception
3856 * @throws \Civi\API\Exception\UnauthorizedException
3857 */
3858 protected function deleteNonDefaultRelationshipTypes(): void {
3859 RelationshipType::delete(FALSE)->addWhere('name_a_b', 'NOT IN', [
3860 'Child of',
3861 'Spouse of',
3862 'Partner of',
3863 'Sibling of',
3864 'Employee of',
3865 'Volunteer for',
3866 'Head of Household for',
3867 'Household Member of',
3868 'Case Coordinator is',
3869 'Supervised by',
3870 ])->execute();
3871 }
3872
a86d27fc 3873}