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