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