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