Merge pull request #16372 from colemanw/api4Magic
[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
dcadbba4 168 /**
8d475ce9 169 * PHPUnit Mock Method to use.
dcadbba4
SL
170 *
171 * @var string
172 */
173 public $mockMethod = 'getMock';
174
6a488035 175 /**
eceb18cc 176 * Constructor.
6a488035
TO
177 *
178 * Because we are overriding the parent class constructor, we
179 * need to show the same arguments as exist in the constructor of
180 * PHPUnit_Framework_TestCase, since
181 * PHPUnit_Framework_TestSuite::createTest() creates a
182 * ReflectionClass of the Test class and checks the constructor
183 * of that class to decide how to set up the test.
184 *
e16033b4
TO
185 * @param string $name
186 * @param array $data
187 * @param string $dataName
6a488035 188 */
7cf5bd55 189 public function __construct($name = NULL, array $data = [], $dataName = '') {
6a488035
TO
190 parent::__construct($name, $data, $dataName);
191
192 // we need full error reporting
193 error_reporting(E_ALL & ~E_NOTICE);
194
f3e16511 195 self::$_dbName = self::getDBName();
6a488035 196
6a488035
TO
197 // also load the class loader
198 require_once 'CRM/Core/ClassLoader.php';
199 CRM_Core_ClassLoader::singleton()->register();
a130e045
DG
200 if (function_exists('_civix_phpunit_setUp')) {
201 // FIXME: loosen coupling
6a488035
TO
202 _civix_phpunit_setUp();
203 }
a6439b6a
SL
204 if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '5', '>=')) {
205 $this->mockMethod = 'createMock';
206 }
207 elseif (class_exists('PHPUnit\Runner\Version') && version_compare(PHPUnit\Runner\Version::id(), '6', '>=')) {
dcadbba4
SL
208 $this->mockMethod = 'createMock';
209 }
6a488035
TO
210 }
211
f0be539a
EM
212 /**
213 * Override to run the test and assert its state.
7cf5bd55 214 *
f0be539a
EM
215 * @return mixed
216 * @throws \Exception
217 * @throws \PHPUnit_Framework_IncompleteTest
218 * @throws \PHPUnit_Framework_SkippedTest
219 */
2e90bf37
TO
220 protected function runTest() {
221 try {
222 return parent::runTest();
5896d037
TO
223 }
224 catch (PEAR_Exception $e) {
2e90bf37
TO
225 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
226 throw new Exception(\CRM_Core_Error::formatTextException($e), $e->getCode());
227 }
228 }
229
4cbe18b8
EM
230 /**
231 * @return bool
232 */
00be9182 233 public function requireDBReset() {
6a488035
TO
234 return $this->DBResetRequired;
235 }
236
4cbe18b8
EM
237 /**
238 * @return string
239 */
00be9182 240 public static function getDBName() {
f3e16511 241 static $dbName = NULL;
649d6dd0 242 if ($dbName === NULL) {
f3e16511
TO
243 require_once "DB.php";
244 $dsninfo = DB::parseDSN(CIVICRM_DSN);
245 $dbName = $dsninfo['database'];
246 }
6a488035
TO
247 return $dbName;
248 }
249
250 /**
eceb18cc 251 * Create database connection for this instance.
6a488035
TO
252 *
253 * Initialize the test database if it hasn't been initialized
254 *
6a488035
TO
255 */
256 protected function getConnection() {
6a488035
TO
257 if (!self::$dbInit) {
258 $dbName = self::getDBName();
259
260 // install test database
261 echo PHP_EOL . "Installing {$dbName} database" . PHP_EOL;
262
99117745 263 static::_populateDB(FALSE, $this);
6a488035
TO
264
265 self::$dbInit = TRUE;
266 }
bc560b0a 267
6a488035
TO
268 }
269
270 /**
eceb18cc 271 * Required implementation of abstract method.
6a488035
TO
272 */
273 protected function getDataSet() {
274 }
275
99117745
TO
276 /**
277 * @param bool $perClass
278 * @param null $object
7cf5bd55 279 *
a6c01b45
CW
280 * @return bool
281 * TRUE if the populate logic runs; FALSE if it is skipped
99117745
TO
282 */
283 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
8582a9fc 284 if (CIVICRM_UF !== 'UnitTests') {
cdc44255 285 throw new \RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
8582a9fc 286 }
6a488035
TO
287
288 if ($perClass || $object == NULL) {
289 $dbreset = TRUE;
290 }
291 else {
292 $dbreset = $object->requireDBReset();
293 }
294
295 if (self::$populateOnce || !$dbreset) {
99117745 296 return FALSE;
6a488035
TO
297 }
298 self::$populateOnce = NULL;
299
728bbd5b 300 Civi\Test::data()->populate();
be709432
TO
301
302 return TRUE;
6a488035
TO
303 }
304
305 public static function setUpBeforeClass() {
99117745 306 static::_populateDB(TRUE);
6a488035
TO
307
308 // also set this global hack
7cf5bd55 309 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
6a488035
TO
310 }
311
312 /**
eceb18cc 313 * Common setup functions for all unit tests.
6a488035
TO
314 */
315 protected function setUp() {
4db17da0
TO
316 $session = CRM_Core_Session::singleton();
317 $session->set('userID', NULL);
318
2d932085
CW
319 $this->_apiversion = 3;
320
39b959db
SL
321 // REVERT
322 $this->errorScope = CRM_Core_TemporaryErrorScope::useException();
6a488035
TO
323 // Use a temporary file for STDIN
324 $GLOBALS['stdin'] = tmpfile();
325 if ($GLOBALS['stdin'] === FALSE) {
326 echo "Couldn't open temporary file\n";
327 exit(1);
328 }
329
330 // Get and save a connection to the database
331 $this->_dbconn = $this->getConnection();
332
333 // reload database before each test
334 // $this->_populateDB();
335
336 // "initialize" CiviCRM to avoid problems when running single tests
337 // FIXME: look at it closer in second stage
338
c0a1f187
TO
339 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
340 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
6a488035
TO
341
342 // disable any left-over test extensions
343 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
344
345 // reset all the caches
346 CRM_Utils_System::flushCache();
347
c0a1f187 348 // initialize the object once db is loaded
7cf5bd55 349 \Civi::$statics = [];
39b959db
SL
350 // ugh, performance
351 $config = CRM_Core_Config::singleton(TRUE, TRUE);
c0a1f187
TO
352
353 // when running unit tests, use mockup user framework
edbcbd96 354 $this->hookClass = CRM_Utils_Hook::singleton();
c0a1f187 355
26684821
TO
356 // Make sure the DB connection is setup properly
357 $config->userSystem->setMySQLTimeZone();
a8de1c22 358 $env = new CRM_Utils_Check_Component_Env();
26684821
TO
359 CRM_Utils_Check::singleton()->assertValid($env->checkMysqlTime());
360
6a488035 361 // clear permissions stub to not check permissions
6a488035
TO
362 $config->userPermissionClass->permissions = NULL;
363
364 //flush component settings
365 CRM_Core_Component::getEnabledComponents(TRUE);
366
88a90442 367 $_REQUEST = $_GET = $_POST = [];
c3d574fc
EM
368 error_reporting(E_ALL);
369
91786f44 370 $this->renameLabels();
d58b453e 371 $this->_sethtmlGlobals();
6a488035
TO
372 }
373
0b6f58fa 374 /**
eceb18cc 375 * Read everything from the datasets directory and insert into the db.
0b6f58fa
ARW
376 */
377 public function loadAllFixtures() {
378 $fixturesDir = __DIR__ . '/../../fixtures';
379
8fee8f1c 380 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
0b6f58fa 381
b01b546f
TO
382 $jsonFiles = glob($fixturesDir . '/*.json');
383 foreach ($jsonFiles as $jsonFixture) {
384 $json = json_decode(file_get_contents($jsonFixture));
385 foreach ($json as $tableName => $vars) {
8fee8f1c 386 if ($tableName === 'civicrm_contact') {
387 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');
388 }
389 else {
390 CRM_Core_DAO::executeQuery("TRUNCATE $tableName");
391 }
392 foreach ($vars as $entity) {
393 $keys = $values = [];
394 foreach ($entity as $key => $value) {
395 $keys[] = $key;
396 $values[] = is_numeric($value) ? $value : "'{$value}'";
397 }
398 CRM_Core_DAO::executeQuery("
399 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
400 );
401 }
402
403 }
0b6f58fa
ARW
404 }
405
8fee8f1c 406 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
0b6f58fa
ARW
407 }
408
08d620bc 409 /**
410 * Load the data that used to be handled by the discontinued dbunit class.
411 *
412 * This could do with further tidy up - the initial priority is simply to get rid of
413 * the dbunity package which is no longer supported.
414 *
415 * @param string $fileName
416 */
417 protected function loadXMLDataSet($fileName) {
418 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
419 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
420 foreach ($xml as $tableName => $element) {
421 if (!empty($element)) {
422 foreach ($element as $row) {
423 $keys = $values = [];
424 if (isset($row['@attributes'])) {
425 foreach ($row['@attributes'] as $key => $value) {
426 $keys[] = $key;
427 $values[] = is_numeric($value) ? $value : "'{$value}'";
428 }
429 }
1d291c60 430 elseif (!empty($row)) {
08d620bc 431 // cos we copied it & it is inconsistent....
432 foreach ($row as $key => $value) {
433 $keys[] = $key;
434 $values[] = is_numeric($value) ? $value : "'{$value}'";
435 }
436 }
437
1d291c60 438 if (!empty($values)) {
439 CRM_Core_DAO::executeQuery("
08d620bc 440 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
1d291c60 441 );
442 }
08d620bc 443 }
444 }
445 }
446 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
447 }
448
5767aa07 449 /**
eceb18cc 450 * Create default domain contacts for the two domains added during test class.
5767aa07
AN
451 * database population.
452 */
453 public function createDomainContacts() {
1ca22999 454 $this->organizationCreate();
7cf5bd55 455 $this->organizationCreate(['organization_name' => 'Second Domain']);
5767aa07
AN
456 }
457
6a488035 458 /**
eceb18cc 459 * Common teardown functions for all unit tests.
6a488035
TO
460 */
461 protected function tearDown() {
2d932085 462 $this->_apiversion = 3;
91786f44 463 $this->resetLabels();
2d932085 464
6a488035 465 error_reporting(E_ALL & ~E_NOTICE);
6c466b51 466 CRM_Utils_Hook::singleton()->reset();
36a29dc0
TO
467 if ($this->hookClass) {
468 $this->hookClass->reset();
469 }
cbe41575 470 CRM_Core_Session::singleton()->reset(1);
d67f1f28
TO
471
472 if ($this->tx) {
473 $this->tx->rollback()->commit();
474 $this->tx = NULL;
475
476 CRM_Core_Transaction::forceRollbackIfEnabled();
477 \Civi\Core\Transaction\Manager::singleton(TRUE);
5896d037
TO
478 }
479 else {
d67f1f28
TO
480 CRM_Core_Transaction::forceRollbackIfEnabled();
481 \Civi\Core\Transaction\Manager::singleton(TRUE);
482
7cf5bd55 483 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match'];
d67f1f28 484 $this->quickCleanup($tablesToTruncate);
a335f6b2 485 $this->createDomainContacts();
d67f1f28
TO
486 }
487
6a488035
TO
488 $this->cleanTempDirs();
489 $this->unsetExtensionSystem();
a80606eb 490 $this->assertEquals([], CRM_Core_DAO::$_nullArray);
491 $this->assertEquals(NULL, CRM_Core_DAO::$_nullObject);
6a488035
TO
492 }
493
888dab1e
TO
494 /**
495 * Create a batch of external API calls which can
496 * be executed concurrently.
497 *
498 * @code
499 * $calls = $this->createExternalAPI()
500 * ->addCall('Contact', 'get', ...)
501 * ->addCall('Contact', 'get', ...)
502 * ...
503 * ->run()
504 * ->getResults();
505 * @endcode
506 *
507 * @return \Civi\API\ExternalBatch
508 * @throws PHPUnit_Framework_SkippedTestError
509 */
510 public function createExternalAPI() {
511 global $civicrm_root;
7cf5bd55 512 $defaultParams = [
888dab1e
TO
513 'version' => $this->_apiversion,
514 'debug' => 1,
7cf5bd55 515 ];
888dab1e
TO
516
517 $calls = new \Civi\API\ExternalBatch($defaultParams);
888dab1e
TO
518
519 if (!$calls->isSupported()) {
520 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
521 }
522
523 return $calls;
524 }
525
fbda92d3 526 /**
527 * Create required data based on $this->entity & $this->params
528 * This is just a way to set up the test data for delete & get functions
529 * so the distinction between set
530 * up & tested functions is clearer
531 *
a6c01b45
CW
532 * @return array
533 * api Result
fbda92d3 534 */
5896d037 535 public function createTestEntity() {
fbda92d3 536 return $entity = $this->callAPISuccess($this->entity, 'create', $this->params);
537 }
538
4cbe18b8 539 /**
100fef9d 540 * @param int $contactTypeId
4cbe18b8
EM
541 *
542 * @throws Exception
543 */
00be9182 544 public function contactTypeDelete($contactTypeId) {
6a488035
TO
545 $result = CRM_Contact_BAO_ContactType::del($contactTypeId);
546 if (!$result) {
547 throw new Exception('Could not delete contact type');
548 }
549 }
550
4cbe18b8
EM
551 /**
552 * @param array $params
553 *
554 * @return mixed
555 */
7cf5bd55 556 public function membershipTypeCreate($params = []) {
6a488035
TO
557 CRM_Member_PseudoConstant::flush('membershipType');
558 CRM_Core_Config::clearDBCache();
204beda3 559 $this->setupIDs['contact'] = $memberOfOrganization = $this->organizationCreate();
7cf5bd55 560 $params = array_merge([
6a488035
TO
561 'name' => 'General',
562 'duration_unit' => 'year',
563 'duration_interval' => 1,
564 'period_type' => 'rolling',
0090e3d2 565 'member_of_contact_id' => $memberOfOrganization,
6a488035 566 'domain_id' => 1,
4303ea89 567 'financial_type_id' => 2,
6a488035 568 'is_active' => 1,
6a488035 569 'sequential' => 1,
eacf220c 570 'visibility' => 'Public',
7cf5bd55 571 ], $params);
75638074 572
573 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
6a488035 574
6a488035
TO
575 CRM_Member_PseudoConstant::flush('membershipType');
576 CRM_Utils_Cache::singleton()->flush();
6a488035
TO
577
578 return $result['id'];
579 }
580
4cbe18b8 581 /**
c6ede1be 582 * Create membership.
583 *
c490a46a 584 * @param array $params
4cbe18b8 585 *
c6ede1be 586 * @return int
587 * @throws \CRM_Core_Exception
4cbe18b8 588 */
00be9182 589 public function contactMembershipCreate($params) {
7cf5bd55 590 $params = array_merge([
6a488035
TO
591 'join_date' => '2007-01-21',
592 'start_date' => '2007-01-21',
593 'end_date' => '2007-12-21',
594 'source' => 'Payment',
5d8b37be 595 'membership_type_id' => 'General',
7cf5bd55 596 ], $params);
5d8b37be 597 if (!is_numeric($params['membership_type_id'])) {
7cf5bd55 598 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
c6ede1be 599 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
7cf5bd55 600 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
6a488035
TO
601 }
602 }
603
f6722559 604 $result = $this->callAPISuccess('Membership', 'create', $params);
6a488035
TO
605 return $result['id'];
606 }
607
608 /**
eceb18cc 609 * Delete Membership Type.
6a488035 610 *
c490a46a 611 * @param array $params
6a488035 612 */
00be9182 613 public function membershipTypeDelete($params) {
cab024d4 614 $this->callAPISuccess('MembershipType', 'Delete', $params);
6a488035
TO
615 }
616
4cbe18b8 617 /**
100fef9d 618 * @param int $membershipID
4cbe18b8 619 */
00be9182 620 public function membershipDelete($membershipID) {
7cf5bd55 621 $deleteParams = ['id' => $membershipID];
f6722559 622 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
6a488035
TO
623 }
624
4cbe18b8
EM
625 /**
626 * @param string $name
627 *
628 * @return mixed
629 */
00be9182 630 public function membershipStatusCreate($name = 'test member status') {
6a488035
TO
631 $params['name'] = $name;
632 $params['start_event'] = 'start_date';
633 $params['end_event'] = 'end_date';
634 $params['is_current_member'] = 1;
635 $params['is_active'] = 1;
6a488035 636
f6722559 637 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
6a488035 638 CRM_Member_PseudoConstant::flush('membershipStatus');
6a488035
TO
639 return $result['id'];
640 }
641
4cbe18b8 642 /**
100fef9d 643 * @param int $membershipStatusID
4cbe18b8 644 */
00be9182 645 public function membershipStatusDelete($membershipStatusID) {
6a488035
TO
646 if (!$membershipStatusID) {
647 return;
648 }
7cf5bd55 649 $result = $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
6a488035
TO
650 }
651
17e615c7
MW
652 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
653 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
654 $renewedMembershipEndDate = new DateTime($membershipEndDate);
655 switch ($durationUnit) {
656 case 'year':
657 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
658 break;
659
660 case 'month':
661 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
662 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
663 $renewedMembershipEndDate->add(new DateInterval('P1D'));
664 $renewedMembershipEndDate->add(new DateInterval('P1M'));
665 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
666 break;
667 }
668 return $renewedMembershipEndDate->format('Y-m-d');
669 }
670
4cbe18b8 671 /**
b086dc3a 672 * Create a relationship type.
673 *
4cbe18b8
EM
674 * @param array $params
675 *
b086dc3a 676 * @return int
677 *
678 * @throws \CRM_Core_Exception
4cbe18b8 679 */
7cf5bd55 680 public function relationshipTypeCreate($params = []) {
681 $params = array_merge([
39b959db
SL
682 'name_a_b' => 'Relation 1 for relationship type create',
683 'name_b_a' => 'Relation 2 for relationship type create',
684 'contact_type_a' => 'Individual',
685 'contact_type_b' => 'Organization',
686 'is_reserved' => 1,
687 'is_active' => 1,
7cf5bd55 688 ], $params);
6a488035 689
f6722559 690 $result = $this->callAPISuccess('relationship_type', 'create', $params);
6a488035
TO
691 CRM_Core_PseudoConstant::flush('relationshipType');
692
693 return $result['id'];
694 }
695
696 /**
eceb18cc 697 * Delete Relatinship Type.
6a488035
TO
698 *
699 * @param int $relationshipTypeID
700 */
00be9182 701 public function relationshipTypeDelete($relationshipTypeID) {
6a488035 702 $params['id'] = $relationshipTypeID;
a60c0bc8
SL
703 $check = $this->callAPISuccess('relationship_type', 'get', $params);
704 if (!empty($check['count'])) {
705 $this->callAPISuccess('relationship_type', 'delete', $params);
706 }
6a488035
TO
707 }
708
4cbe18b8 709 /**
100fef9d 710 * @param array $params
4cbe18b8
EM
711 *
712 * @return mixed
b086dc3a 713 * @throws \CRM_Core_Exception
4cbe18b8 714 */
00be9182 715 public function paymentProcessorTypeCreate($params = NULL) {
6a488035 716 if (is_null($params)) {
7cf5bd55 717 $params = [
6a488035
TO
718 'name' => 'API_Test_PP',
719 'title' => 'API Test Payment Processor',
720 'class_name' => 'CRM_Core_Payment_APITest',
721 'billing_mode' => 'form',
722 'is_recur' => 0,
723 'is_reserved' => 1,
724 'is_active' => 1,
7cf5bd55 725 ];
6a488035 726 }
f6722559 727 $result = $this->callAPISuccess('payment_processor_type', 'create', $params);
6a488035 728
6a488035
TO
729 CRM_Core_PseudoConstant::flush('paymentProcessorType');
730
731 return $result['id'];
732 }
733
d6944518
EM
734 /**
735 * Create test Authorize.net instance.
736 *
737 * @param array $params
738 *
739 * @return mixed
740 */
7cf5bd55 741 public function paymentProcessorAuthorizeNetCreate($params = []) {
742 $params = array_merge([
d6944518
EM
743 'name' => 'Authorize',
744 'domain_id' => CRM_Core_Config::domainID(),
745 'payment_processor_type_id' => 'AuthNet',
746 'title' => 'AuthNet',
747 'is_active' => 1,
748 'is_default' => 0,
749 'is_test' => 1,
750 'is_recur' => 1,
751 'user_name' => '4y5BfuW7jm',
752 'password' => '4cAmW927n8uLf5J8',
753 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
754 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
755 'class_name' => 'Payment_AuthorizeNet',
756 'billing_mode' => 1,
7cf5bd55 757 ], $params);
d6944518 758
4ecbf127 759 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
d6944518
EM
760 return $result['id'];
761 }
762
6a488035 763 /**
eceb18cc 764 * Create Participant.
6a488035 765 *
e16033b4
TO
766 * @param array $params
767 * Array of contact id and event id values.
6a488035 768 *
a6c01b45
CW
769 * @return int
770 * $id of participant created
6a488035 771 */
0f57ba7a 772 public function participantCreate($params = []) {
5896d037 773 if (empty($params['contact_id'])) {
94692f2d 774 $params['contact_id'] = $this->individualCreate();
775 }
5896d037 776 if (empty($params['event_id'])) {
94692f2d 777 $event = $this->eventCreate();
778 $params['event_id'] = $event['id'];
779 }
7cf5bd55 780 $defaults = [
6a488035
TO
781 'status_id' => 2,
782 'role_id' => 1,
783 'register_date' => 20070219,
784 'source' => 'Wimbeldon',
785 'event_level' => 'Payment',
94692f2d 786 'debug' => 1,
7cf5bd55 787 ];
6a488035
TO
788
789 $params = array_merge($defaults, $params);
f6722559 790 $result = $this->callAPISuccess('Participant', 'create', $params);
6a488035
TO
791 return $result['id'];
792 }
793
794 /**
eceb18cc 795 * Create Payment Processor.
6a488035 796 *
8950ecdc 797 * @return int
798 * Id Payment Processor
6a488035 799 */
7cf5bd55 800 public function processorCreate($params = []) {
801 $processorParams = [
6a488035
TO
802 'domain_id' => 1,
803 'name' => 'Dummy',
8950ecdc 804 'payment_processor_type_id' => 'Dummy',
6a488035 805 'financial_account_id' => 12,
a8215a8d 806 'is_test' => TRUE,
6a488035
TO
807 'is_active' => 1,
808 'user_name' => '',
809 'url_site' => 'http://dummy.com',
810 'url_recur' => 'http://dummy.com',
811 'billing_mode' => 1,
8950ecdc 812 'sequential' => 1,
204efedd 813 'payment_instrument_id' => 'Debit Card',
7cf5bd55 814 ];
0f07bb06 815 $processorParams = array_merge($processorParams, $params);
8950ecdc 816 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
817 return $processor['id'];
22e39333 818 }
819
820 /**
821 * Create Payment Processor.
822 *
823 * @param array $processorParams
824 *
0193ebdb 825 * @return \CRM_Core_Payment_Dummy
39b959db 826 * Instance of Dummy Payment Processor
31eddd0d 827 *
828 * @throws \CiviCRM_API3_Exception
22e39333 829 */
31eddd0d 830 public function dummyProcessorCreate($processorParams = []) {
8950ecdc 831 $paymentProcessorID = $this->processorCreate($processorParams);
31eddd0d 832 // 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
833 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
834 $processorParams['is_test'] = FALSE;
835 $this->processorCreate($processorParams);
7a3b0ca3 836 return System::singleton()->getById($paymentProcessorID);
6a488035
TO
837 }
838
839 /**
eceb18cc 840 * Create contribution page.
6a488035 841 *
c490a46a 842 * @param array $params
7cf5bd55 843 *
16b10e64
CW
844 * @return array
845 * Array of contribution page
6a488035 846 */
7cf5bd55 847 public function contributionPageCreate($params = []) {
848 $this->_pageParams = array_merge([
6a488035
TO
849 'title' => 'Test Contribution Page',
850 'financial_type_id' => 1,
851 'currency' => 'USD',
852 'financial_account_id' => 1,
6a488035
TO
853 'is_active' => 1,
854 'is_allow_other_amount' => 1,
855 'min_amount' => 10,
856 'max_amount' => 1000,
7cf5bd55 857 ], $params);
23ead872 858 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
6a488035
TO
859 }
860
a646bdd2
SB
861 /**
862 * Create a sample batch.
863 */
864 public function batchCreate() {
865 $params = $this->_params;
866 $params['name'] = $params['title'] = 'Batch_433397';
867 $params['status_id'] = 1;
868 $result = $this->callAPISuccess('batch', 'create', $params);
869 return $result['id'];
870 }
871
6a488035 872 /**
eceb18cc 873 * Create Tag.
6a488035 874 *
2a6da8d7 875 * @param array $params
7cf5bd55 876 *
a6c01b45
CW
877 * @return array
878 * result of created tag
6a488035 879 */
7cf5bd55 880 public function tagCreate($params = []) {
881 $defaults = [
fb32de45 882 'name' => 'New Tag3',
883 'description' => 'This is description for Our New Tag ',
884 'domain_id' => '1',
7cf5bd55 885 ];
fb32de45 886 $params = array_merge($defaults, $params);
6c6e6187 887 $result = $this->callAPISuccess('Tag', 'create', $params);
fb32de45 888 return $result['values'][$result['id']];
6a488035
TO
889 }
890
891 /**
eceb18cc 892 * Delete Tag.
6a488035 893 *
e16033b4
TO
894 * @param int $tagId
895 * Id of the tag to be deleted.
16b10e64
CW
896 *
897 * @return int
6a488035 898 */
00be9182 899 public function tagDelete($tagId) {
6a488035 900 require_once 'api/api.php';
7cf5bd55 901 $params = [
6a488035 902 'tag_id' => $tagId,
7cf5bd55 903 ];
f6722559 904 $result = $this->callAPISuccess('Tag', 'delete', $params);
6a488035
TO
905 return $result['id'];
906 }
907
908 /**
909 * Add entity(s) to the tag
910 *
e16033b4 911 * @param array $params
77b97be7
EM
912 *
913 * @return bool
6a488035 914 */
00be9182 915 public function entityTagAdd($params) {
f6722559 916 $result = $this->callAPISuccess('entity_tag', 'create', $params);
6a488035
TO
917 return TRUE;
918 }
919
920 /**
5210fa35 921 * Create pledge.
6a488035 922 *
5210fa35 923 * @param array $params
924 * Parameters.
77b97be7 925 *
a6c01b45 926 * @return int
5210fa35 927 * id of created pledge
6a488035 928 */
5210fa35 929 public function pledgeCreate($params) {
7cf5bd55 930 $params = array_merge([
6a488035
TO
931 'pledge_create_date' => date('Ymd'),
932 'start_date' => date('Ymd'),
933 'scheduled_date' => date('Ymd'),
934 'amount' => 100.00,
935 'pledge_status_id' => '2',
936 'financial_type_id' => '1',
937 'pledge_original_installment_amount' => 20,
938 'frequency_interval' => 5,
939 'frequency_unit' => 'year',
940 'frequency_day' => 15,
941 'installments' => 5,
7cf5bd55 942 ],
943 $params);
6a488035 944
f6722559 945 $result = $this->callAPISuccess('Pledge', 'create', $params);
6a488035
TO
946 return $result['id'];
947 }
948
949 /**
eceb18cc 950 * Delete contribution.
6a488035 951 *
c490a46a 952 * @param int $pledgeId
6a488035 953 */
00be9182 954 public function pledgeDelete($pledgeId) {
7cf5bd55 955 $params = [
6a488035 956 'pledge_id' => $pledgeId,
7cf5bd55 957 ];
f6722559 958 $this->callAPISuccess('Pledge', 'delete', $params);
6a488035
TO
959 }
960
961 /**
eceb18cc 962 * Create contribution.
6a488035 963 *
c3a3074f 964 * @param array $params
965 * Array of parameters.
16b10e64 966 *
a6c01b45
CW
967 * @return int
968 * id of created contribution
7aeb7f06 969 * @throws \CRM_Core_Exception
6a488035 970 */
78ab0ca4 971 public function contributionCreate($params) {
69fccd8f 972
7cf5bd55 973 $params = array_merge([
6a488035 974 'domain_id' => 1,
6a488035
TO
975 'receive_date' => date('Ymd'),
976 'total_amount' => 100.00,
69fccd8f 977 'fee_amount' => 5.00,
78ab0ca4 978 'financial_type_id' => 1,
979 'payment_instrument_id' => 1,
6a488035 980 'non_deductible_amount' => 10.00,
6a488035 981 'source' => 'SSF',
6a488035 982 'contribution_status_id' => 1,
7cf5bd55 983 ], $params);
6a488035 984
f6722559 985 $result = $this->callAPISuccess('contribution', 'create', $params);
6a488035
TO
986 return $result['id'];
987 }
988
6a488035 989 /**
eceb18cc 990 * Delete contribution.
6a488035
TO
991 *
992 * @param int $contributionId
8deefe64
EM
993 *
994 * @return array|int
b086dc3a 995 * @throws \CRM_Core_Exception
6a488035 996 */
00be9182 997 public function contributionDelete($contributionId) {
7cf5bd55 998 $params = [
6a488035 999 'contribution_id' => $contributionId,
7cf5bd55 1000 ];
f6722559 1001 $result = $this->callAPISuccess('contribution', 'delete', $params);
6a488035
TO
1002 return $result;
1003 }
1004
1005 /**
eceb18cc 1006 * Create an Event.
6a488035 1007 *
e16033b4
TO
1008 * @param array $params
1009 * Name-value pair for an event.
6a488035 1010 *
a6c01b45 1011 * @return array
6a488035 1012 */
7cf5bd55 1013 public function eventCreate($params = []) {
6a488035
TO
1014 // if no contact was passed, make up a dummy event creator
1015 if (!isset($params['contact_id'])) {
7cf5bd55 1016 $params['contact_id'] = $this->_contactCreate([
6a488035
TO
1017 'contact_type' => 'Individual',
1018 'first_name' => 'Event',
1019 'last_name' => 'Creator',
7cf5bd55 1020 ]);
6a488035
TO
1021 }
1022
1023 // set defaults for missing params
7cf5bd55 1024 $params = array_merge([
6a488035
TO
1025 'title' => 'Annual CiviCRM meet',
1026 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1027 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1028 'event_type_id' => 1,
1029 'is_public' => 1,
1030 'start_date' => 20081021,
1031 'end_date' => 20081023,
1032 'is_online_registration' => 1,
1033 'registration_start_date' => 20080601,
1034 'registration_end_date' => 20081015,
1035 'max_participants' => 100,
1036 'event_full_text' => 'Sorry! We are already full',
c039f658 1037 'is_monetary' => 0,
6a488035 1038 'is_active' => 1,
6a488035 1039 'is_show_location' => 0,
e579af79 1040 'is_email_confirm' => 1,
7cf5bd55 1041 ], $params);
6a488035 1042
8cba5814 1043 return $this->callAPISuccess('Event', 'create', $params);
6a488035
TO
1044 }
1045
f660d2ad 1046 /**
1047 * Create a paid event.
1048 *
1049 * @param array $params
1050 *
2d6f64f4 1051 * @param array $options
1052 *
f660d2ad 1053 * @return array
3fc37a30 1054 *
1055 * @throws \CRM_Core_Exception
f660d2ad 1056 */
2d6f64f4 1057 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]]) {
f660d2ad 1058 $event = $this->eventCreate($params);
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);
1062 $priceSet = CRM_Utils_Array::value($this->priceSetID, $priceSet);
1063 $this->eventFeeBlock = CRM_Utils_Array::value('fields', $priceSet);
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') {
1287 $smartGroupParams = array_merge([
1288 'formValues' => ['contact_type' => ['IN' => [$contactType]]],
1289 ],
1290 $smartGroupParams);
c3137c08 1291 $savedSearch = CRM_Contact_BAO_SavedSearch::create($smartGroupParams);
1292
1293 $groupParams['saved_search_id'] = $savedSearch->id;
1294 return $this->groupCreate($groupParams);
1295 }
7811a84b 1296
5c496c74 1297 /**
eceb18cc 1298 * Create a UFField.
7cf5bd55 1299 *
5c496c74 1300 * @param array $params
1301 */
7cf5bd55 1302 public function uFFieldCreate($params = []) {
1303 $params = array_merge([
5c496c74 1304 'uf_group_id' => 1,
1305 'field_name' => 'first_name',
1306 'is_active' => 1,
1307 'is_required' => 1,
1308 'visibility' => 'Public Pages and Listings',
1309 'is_searchable' => '1',
1310 'label' => 'first_name',
1311 'field_type' => 'Individual',
1312 'weight' => 1,
7cf5bd55 1313 ], $params);
5c496c74 1314 $this->callAPISuccess('uf_field', 'create', $params);
1315 }
2a6da8d7 1316
6a488035 1317 /**
eceb18cc 1318 * Add a UF Join Entry.
6a488035 1319 *
100fef9d 1320 * @param array $params
7cf5bd55 1321 *
a6c01b45
CW
1322 * @return int
1323 * $id of created UF Join
6a488035 1324 */
00be9182 1325 public function ufjoinCreate($params = NULL) {
6a488035 1326 if ($params === NULL) {
7cf5bd55 1327 $params = [
6a488035
TO
1328 'is_active' => 1,
1329 'module' => 'CiviEvent',
1330 'entity_table' => 'civicrm_event',
1331 'entity_id' => 3,
1332 'weight' => 1,
1333 'uf_group_id' => 1,
7cf5bd55 1334 ];
6a488035 1335 }
f6722559 1336 $result = $this->callAPISuccess('uf_join', 'create', $params);
6a488035
TO
1337 return $result;
1338 }
1339
c03f1689
EM
1340 /**
1341 * @param array $params
1342 * Optional parameters.
d31d0888 1343 * @param bool $reloadConfig
1344 * While enabling CiviCampaign component, we shouldn't always forcibly
1345 * reload config as this hinder hook call in test environment
c03f1689
EM
1346 *
1347 * @return int
1348 * Campaign ID.
1349 */
7cf5bd55 1350 public function campaignCreate($params = [], $reloadConfig = TRUE) {
d31d0888 1351 $this->enableCiviCampaign($reloadConfig);
7cf5bd55 1352 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
c03f1689
EM
1353 'name' => 'big_campaign',
1354 'title' => 'Campaign',
7cf5bd55 1355 ], $params));
c03f1689
EM
1356 return $campaign['id'];
1357 }
1358
6a488035 1359 /**
eceb18cc 1360 * Create Group for a contact.
6a488035
TO
1361 *
1362 * @param int $contactId
1363 */
00be9182 1364 public function contactGroupCreate($contactId) {
7cf5bd55 1365 $params = [
6a488035
TO
1366 'contact_id.1' => $contactId,
1367 'group_id' => 1,
7cf5bd55 1368 ];
6a488035 1369
f6722559 1370 $this->callAPISuccess('GroupContact', 'Create', $params);
6a488035
TO
1371 }
1372
1373 /**
eceb18cc 1374 * Delete Group for a contact.
6a488035 1375 *
100fef9d 1376 * @param int $contactId
6a488035 1377 */
00be9182 1378 public function contactGroupDelete($contactId) {
7cf5bd55 1379 $params = [
6a488035
TO
1380 'contact_id.1' => $contactId,
1381 'group_id' => 1,
7cf5bd55 1382 ];
0f643fb2 1383 $this->civicrm_api('GroupContact', 'Delete', $params);
6a488035
TO
1384 }
1385
1386 /**
eceb18cc 1387 * Create Activity.
6a488035 1388 *
c490a46a 1389 * @param array $params
7cf5bd55 1390 *
2a6da8d7 1391 * @return array|int
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',
01ead735 1842 'civicrm_pledge_block',
294cc627 1843 'civicrm_pledge_payment',
1cf3c2b1 1844 'civicrm_price_set_entity',
108ff21a
EM
1845 'civicrm_price_field_value',
1846 'civicrm_price_field',
7cf5bd55 1847 ];
b38530f2 1848 $this->quickCleanup($tablesToTruncate);
4278b952 1849 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
108ff21a 1850 $this->restoreDefaultPriceSetConfig();
9fbf312f 1851 $this->disableTaxAndInvoicing();
83644f47 1852 $this->setCurrencySeparators(',');
1853 CRM_Core_PseudoConstant::flush('taxRates');
7a3b0ca3 1854 System::singleton()->flushProcessors();
bc438a52 1855 // @fixme this parameter is leaking - it should not be defined as a class static
1856 // but for now we just handle in tear down.
1857 CRM_Contribute_BAO_Query::$_contribOrSoftCredit = 'only contribs';
108ff21a
EM
1858 }
1859
5a433c56 1860 /**
1861 * Reset the price set config so results exist.
1862 */
00be9182 1863 public function restoreDefaultPriceSetConfig() {
cdd71d6b 1864 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1865 CRM_Core_DAO::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
108ff21a 1866 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 1867 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 1868 }
39b959db 1869
5a433c56 1870 /**
1871 * Recreate default membership types.
1872 */
1873 public function restoreMembershipTypes() {
1874 CRM_Core_DAO::executeQuery(
1875 "REPLACE INTO civicrm_membership_type
1876 (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)
1877VALUES
1878 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1879 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1880 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1881 ");
1882 }
1883
6a488035
TO
1884 /*
1885 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1886 * Default behaviour is to also delete the entity
e16033b4 1887 * @param array $params
e4f46be0 1888 * Params array to check against.
e16033b4
TO
1889 * @param int $id
1890 * Id of the entity concerned.
1891 * @param string $entity
1892 * Name of entity concerned (e.g. membership).
1893 * @param bool $delete
1894 * Should the entity be deleted as part of this check.
1895 * @param string $errorText
1896 * Text to print on error.
6a488035 1897 */
39b959db 1898
4cbe18b8 1899 /**
c490a46a 1900 * @param array $params
100fef9d 1901 * @param int $id
4cbe18b8
EM
1902 * @param $entity
1903 * @param int $delete
1904 * @param string $errorText
1905 *
b086dc3a 1906 * @throws CRM_Core_Exception
4cbe18b8 1907 */
00be9182 1908 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
6a488035 1909
7cf5bd55 1910 $result = $this->callAPISuccessGetSingle($entity, [
6a488035 1911 'id' => $id,
7cf5bd55 1912 ]);
6a488035
TO
1913
1914 if ($delete) {
7cf5bd55 1915 $this->callAPISuccess($entity, 'Delete', [
6a488035 1916 'id' => $id,
7cf5bd55 1917 ]);
6a488035 1918 }
7cf5bd55 1919 $dateFields = $keys = $dateTimeFields = [];
1920 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
6a488035
TO
1921 foreach ($fields['values'] as $field => $settings) {
1922 if (array_key_exists($field, $result)) {
e255b57a 1923 $keys[CRM_Utils_Array::value('name', $settings, $field)] = $field;
6a488035
TO
1924 }
1925 else {
e255b57a 1926 $keys[CRM_Utils_Array::value('name', $settings, $field)] = CRM_Utils_Array::value('name', $settings, $field);
6a488035 1927 }
afd404ea 1928 $type = CRM_Utils_Array::value('type', $settings);
1929 if ($type == CRM_Utils_Type::T_DATE) {
1930 $dateFields[] = $settings['name'];
1931 // we should identify both real names & unique names as dates
5896d037 1932 if ($field != $settings['name']) {
afd404ea 1933 $dateFields[] = $field;
1934 }
1935 }
5896d037 1936 if ($type == CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME) {
afd404ea 1937 $dateTimeFields[] = $settings['name'];
1938 // we should identify both real names & unique names as dates
5896d037 1939 if ($field != $settings['name']) {
afd404ea 1940 $dateTimeFields[] = $field;
1941 }
6a488035
TO
1942 }
1943 }
1944
1945 if (strtolower($entity) == 'contribution') {
1946 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1947 // this is not returned in id format
1948 unset($params['payment_instrument_id']);
1949 $params['contribution_source'] = $params['source'];
1950 unset($params['source']);
1951 }
1952
1953 foreach ($params as $key => $value) {
afd404ea 1954 if ($key == 'version' || substr($key, 0, 3) == 'api' || !array_key_exists($keys[$key], $result)) {
6a488035
TO
1955 continue;
1956 }
1957 if (in_array($key, $dateFields)) {
1958 $value = date('Y-m-d', strtotime($value));
1959 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1960 }
afd404ea 1961 if (in_array($key, $dateTimeFields)) {
1962 $value = date('Y-m-d H:i:s', strtotime($value));
a72cec08 1963 $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 1964 }
1965 $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
1966 }
1967 }
1968
1969 /**
eceb18cc 1970 * Get formatted values in the actual and expected result.
7cf5bd55 1971 *
e16033b4
TO
1972 * @param array $actual
1973 * Actual calculated values.
1974 * @param array $expected
1975 * Expected values.
6a488035 1976 */
00be9182 1977 public function checkArrayEquals(&$actual, &$expected) {
6a488035
TO
1978 self::unsetId($actual);
1979 self::unsetId($expected);
25115a3e 1980 $this->assertEquals($expected, $actual);
6a488035
TO
1981 }
1982
1983 /**
100fef9d 1984 * Unset the key 'id' from the array
7cf5bd55 1985 *
e16033b4
TO
1986 * @param array $unformattedArray
1987 * The array from which the 'id' has to be unset.
6a488035 1988 */
00be9182 1989 public static function unsetId(&$unformattedArray) {
7cf5bd55 1990 $formattedArray = [];
6a488035
TO
1991 if (array_key_exists('id', $unformattedArray)) {
1992 unset($unformattedArray['id']);
1993 }
a7488080 1994 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
6a488035 1995 foreach ($unformattedArray['values'] as $key => $value) {
6c6e6187 1996 if (is_array($value)) {
6a488035
TO
1997 foreach ($value as $k => $v) {
1998 if ($k == 'id') {
1999 unset($value[$k]);
2000 }
2001 }
2002 }
2003 elseif ($key == 'id') {
2004 $unformattedArray[$key];
2005 }
7cf5bd55 2006 $formattedArray = [$value];
6a488035
TO
2007 }
2008 $unformattedArray['values'] = $formattedArray;
2009 }
2010 }
2011
2012 /**
2013 * Helper to enable/disable custom directory support
2014 *
e16033b4
TO
2015 * @param array $customDirs
2016 * With members:.
6a488035
TO
2017 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2018 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2019 */
00be9182 2020 public function customDirectories($customDirs) {
6a488035
TO
2021 $config = CRM_Core_Config::singleton();
2022
2023 if (empty($customDirs['php_path']) || $customDirs['php_path'] === FALSE) {
2024 unset($config->customPHPPathDir);
2025 }
2026 elseif ($customDirs['php_path'] === TRUE) {
2027 $config->customPHPPathDir = dirname(dirname(__FILE__)) . '/custom_directories/php/';
2028 }
2029 else {
2030 $config->customPHPPathDir = $php_path;
2031 }
2032
2033 if (empty($customDirs['template_path']) || $customDirs['template_path'] === FALSE) {
2034 unset($config->customTemplateDir);
2035 }
2036 elseif ($customDirs['template_path'] === TRUE) {
2037 $config->customTemplateDir = dirname(dirname(__FILE__)) . '/custom_directories/templates/';
2038 }
2039 else {
2040 $config->customTemplateDir = $template_path;
2041 }
2042 }
2043
2044 /**
eceb18cc 2045 * Generate a temporary folder.
6a488035 2046 *
2a6da8d7 2047 * @param string $prefix
7cf5bd55 2048 *
a6c01b45 2049 * @return string
6a488035 2050 */
00be9182 2051 public function createTempDir($prefix = 'test-') {
6a488035
TO
2052 $tempDir = CRM_Utils_File::tempdir($prefix);
2053 $this->tempDirs[] = $tempDir;
2054 return $tempDir;
2055 }
2056
00be9182 2057 public function cleanTempDirs() {
6a488035
TO
2058 if (!is_array($this->tempDirs)) {
2059 // fix test errors where this is not set
2060 return;
2061 }
2062 foreach ($this->tempDirs as $tempDir) {
2063 if (is_dir($tempDir)) {
2064 CRM_Utils_File::cleanDir($tempDir, TRUE, FALSE);
2065 }
2066 }
2067 }
2068
2069 /**
eceb18cc 2070 * Temporarily replace the singleton extension with a different one.
7cf5bd55 2071 *
1e1fdcf6 2072 * @param \CRM_Extension_System $system
6a488035 2073 */
00be9182 2074 public function setExtensionSystem(CRM_Extension_System $system) {
6a488035
TO
2075 if ($this->origExtensionSystem == NULL) {
2076 $this->origExtensionSystem = CRM_Extension_System::singleton();
2077 }
2078 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2079 }
2080
00be9182 2081 public function unsetExtensionSystem() {
6a488035
TO
2082 if ($this->origExtensionSystem !== NULL) {
2083 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2084 $this->origExtensionSystem = NULL;
2085 }
2086 }
f17d75bb 2087
076d8c82
TO
2088 /**
2089 * Temporarily alter the settings-metadata to add a mock setting.
2090 *
2091 * WARNING: The setting metadata will disappear on the next cache-clear.
2092 *
2093 * @param $extras
7cf5bd55 2094 *
076d8c82
TO
2095 * @return void
2096 */
00be9182 2097 public function setMockSettingsMetaData($extras) {
5896d037
TO
2098 CRM_Utils_Hook::singleton()
2099 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2100 $metadata = array_merge($metadata, $extras);
2101 });
076d8c82 2102
1209c8a7
CW
2103 Civi::service('settings_manager')->flush();
2104
7cf5bd55 2105 $fields = $this->callAPISuccess('setting', 'getfields', []);
076d8c82
TO
2106 foreach ($extras as $key => $spec) {
2107 $this->assertNotEmpty($spec['title']);
2108 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2109 }
2110 }
2111
4cbe18b8 2112 /**
100fef9d 2113 * @param string $name
4cbe18b8 2114 */
00be9182 2115 public function financialAccountDelete($name) {
f17d75bb
PN
2116 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2117 $financialAccount->name = $name;
5896d037 2118 if ($financialAccount->find(TRUE)) {
f17d75bb
PN
2119 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2120 $entityFinancialType->financial_account_id = $financialAccount->id;
2121 $entityFinancialType->delete();
2122 $financialAccount->delete();
2123 }
2124 }
fb32de45 2125
2b7d3f8a 2126 /**
2127 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2128 * (NB unclear if this is still required)
2129 */
00be9182 2130 public function _sethtmlGlobals() {
7cf5bd55 2131 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2132 'required' => [
2b7d3f8a 2133 'html_quickform_rule_required',
21dfd5f5 2134 'HTML/QuickForm/Rule/Required.php',
7cf5bd55 2135 ],
2136 'maxlength' => [
2b7d3f8a 2137 'html_quickform_rule_range',
21dfd5f5 2138 'HTML/QuickForm/Rule/Range.php',
7cf5bd55 2139 ],
2140 'minlength' => [
2b7d3f8a 2141 'html_quickform_rule_range',
21dfd5f5 2142 'HTML/QuickForm/Rule/Range.php',
7cf5bd55 2143 ],
2144 'rangelength' => [
2b7d3f8a 2145 'html_quickform_rule_range',
21dfd5f5 2146 'HTML/QuickForm/Rule/Range.php',
7cf5bd55 2147 ],
2148 'email' => [
2b7d3f8a 2149 'html_quickform_rule_email',
21dfd5f5 2150 'HTML/QuickForm/Rule/Email.php',
7cf5bd55 2151 ],
2152 'regex' => [
2b7d3f8a 2153 'html_quickform_rule_regex',
21dfd5f5 2154 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2155 ],
2156 'lettersonly' => [
2b7d3f8a 2157 'html_quickform_rule_regex',
21dfd5f5 2158 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2159 ],
2160 'alphanumeric' => [
2b7d3f8a 2161 'html_quickform_rule_regex',
21dfd5f5 2162 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2163 ],
2164 'numeric' => [
2b7d3f8a 2165 'html_quickform_rule_regex',
21dfd5f5 2166 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2167 ],
2168 'nopunctuation' => [
2b7d3f8a 2169 'html_quickform_rule_regex',
21dfd5f5 2170 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2171 ],
2172 'nonzero' => [
2b7d3f8a 2173 'html_quickform_rule_regex',
21dfd5f5 2174 'HTML/QuickForm/Rule/Regex.php',
7cf5bd55 2175 ],
2176 'callback' => [
2b7d3f8a 2177 'html_quickform_rule_callback',
21dfd5f5 2178 'HTML/QuickForm/Rule/Callback.php',
7cf5bd55 2179 ],
2180 'compare' => [
2b7d3f8a 2181 'html_quickform_rule_compare',
21dfd5f5 2182 'HTML/QuickForm/Rule/Compare.php',
7cf5bd55 2183 ],
2184 ];
2b7d3f8a 2185 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
7cf5bd55 2186 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2187 'group' => [
2b7d3f8a 2188 'HTML/QuickForm/group.php',
21dfd5f5 2189 'HTML_QuickForm_group',
7cf5bd55 2190 ],
2191 'hidden' => [
2b7d3f8a 2192 'HTML/QuickForm/hidden.php',
21dfd5f5 2193 'HTML_QuickForm_hidden',
7cf5bd55 2194 ],
2195 'reset' => [
2b7d3f8a 2196 'HTML/QuickForm/reset.php',
21dfd5f5 2197 'HTML_QuickForm_reset',
7cf5bd55 2198 ],
2199 'checkbox' => [
2b7d3f8a 2200 'HTML/QuickForm/checkbox.php',
21dfd5f5 2201 'HTML_QuickForm_checkbox',
7cf5bd55 2202 ],
2203 'file' => [
2b7d3f8a 2204 'HTML/QuickForm/file.php',
21dfd5f5 2205 'HTML_QuickForm_file',
7cf5bd55 2206 ],
2207 'image' => [
2b7d3f8a 2208 'HTML/QuickForm/image.php',
21dfd5f5 2209 'HTML_QuickForm_image',
7cf5bd55 2210 ],
2211 'password' => [
2b7d3f8a 2212 'HTML/QuickForm/password.php',
21dfd5f5 2213 'HTML_QuickForm_password',
7cf5bd55 2214 ],
2215 'radio' => [
2b7d3f8a 2216 'HTML/QuickForm/radio.php',
21dfd5f5 2217 'HTML_QuickForm_radio',
7cf5bd55 2218 ],
2219 'button' => [
2b7d3f8a 2220 'HTML/QuickForm/button.php',
21dfd5f5 2221 'HTML_QuickForm_button',
7cf5bd55 2222 ],
2223 'submit' => [
2b7d3f8a 2224 'HTML/QuickForm/submit.php',
21dfd5f5 2225 'HTML_QuickForm_submit',
7cf5bd55 2226 ],
2227 'select' => [
2b7d3f8a 2228 'HTML/QuickForm/select.php',
21dfd5f5 2229 'HTML_QuickForm_select',
7cf5bd55 2230 ],
2231 'hiddenselect' => [
2b7d3f8a 2232 'HTML/QuickForm/hiddenselect.php',
21dfd5f5 2233 'HTML_QuickForm_hiddenselect',
7cf5bd55 2234 ],
2235 'text' => [
2b7d3f8a 2236 'HTML/QuickForm/text.php',
21dfd5f5 2237 'HTML_QuickForm_text',
7cf5bd55 2238 ],
2239 'textarea' => [
2b7d3f8a 2240 'HTML/QuickForm/textarea.php',
21dfd5f5 2241 'HTML_QuickForm_textarea',
7cf5bd55 2242 ],
2243 'fckeditor' => [
2b7d3f8a 2244 'HTML/QuickForm/fckeditor.php',
21dfd5f5 2245 'HTML_QuickForm_FCKEditor',
7cf5bd55 2246 ],
2247 'tinymce' => [
2b7d3f8a 2248 'HTML/QuickForm/tinymce.php',
21dfd5f5 2249 'HTML_QuickForm_TinyMCE',
7cf5bd55 2250 ],
2251 'dojoeditor' => [
2b7d3f8a 2252 'HTML/QuickForm/dojoeditor.php',
21dfd5f5 2253 'HTML_QuickForm_dojoeditor',
7cf5bd55 2254 ],
2255 'link' => [
2b7d3f8a 2256 'HTML/QuickForm/link.php',
21dfd5f5 2257 'HTML_QuickForm_link',
7cf5bd55 2258 ],
2259 'advcheckbox' => [
2b7d3f8a 2260 'HTML/QuickForm/advcheckbox.php',
21dfd5f5 2261 'HTML_QuickForm_advcheckbox',
7cf5bd55 2262 ],
2263 'date' => [
2b7d3f8a 2264 'HTML/QuickForm/date.php',
21dfd5f5 2265 'HTML_QuickForm_date',
7cf5bd55 2266 ],
2267 'static' => [
2b7d3f8a 2268 'HTML/QuickForm/static.php',
21dfd5f5 2269 'HTML_QuickForm_static',
7cf5bd55 2270 ],
2271 'header' => [
2b7d3f8a 2272 'HTML/QuickForm/header.php',
21dfd5f5 2273 'HTML_QuickForm_header',
7cf5bd55 2274 ],
2275 'html' => [
2b7d3f8a 2276 'HTML/QuickForm/html.php',
21dfd5f5 2277 'HTML_QuickForm_html',
7cf5bd55 2278 ],
2279 'hierselect' => [
2b7d3f8a 2280 'HTML/QuickForm/hierselect.php',
21dfd5f5 2281 'HTML_QuickForm_hierselect',
7cf5bd55 2282 ],
2283 'autocomplete' => [
2b7d3f8a 2284 'HTML/QuickForm/autocomplete.php',
21dfd5f5 2285 'HTML_QuickForm_autocomplete',
7cf5bd55 2286 ],
2287 'xbutton' => [
2b7d3f8a 2288 'HTML/QuickForm/xbutton.php',
21dfd5f5 2289 'HTML_QuickForm_xbutton',
7cf5bd55 2290 ],
2291 'advmultiselect' => [
2b7d3f8a 2292 'HTML/QuickForm/advmultiselect.php',
21dfd5f5 2293 'HTML_QuickForm_advmultiselect',
7cf5bd55 2294 ],
2295 ];
2b7d3f8a 2296 }
2297
48e399ac
EM
2298 /**
2299 * Set up an acl allowing contact to see 2 specified groups
c206647d 2300 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
48e399ac 2301 *
c206647d 2302 * You need to have pre-created these groups & created the user e.g
48e399ac
EM
2303 * $this->createLoggedInUser();
2304 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2305 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
ea3ddccf 2306 *
2307 * @param bool $isProfile
48e399ac 2308 */
181f536c 2309 public function setupACL($isProfile = FALSE) {
aaac0e0b
EM
2310 global $_REQUEST;
2311 $_REQUEST = $this->_params;
108ff21a 2312
7cf5bd55 2313 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
2314 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
e5720c45
SL
2315 $ov = new CRM_Core_DAO_OptionValue();
2316 $ov->option_group_id = $optionGroupID;
2317 $ov->value = 55;
2318 if ($ov->find(TRUE)) {
2319 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2320 }
7cf5bd55 2321 $optionValue = $this->callAPISuccess('option_value', 'create', [
5896d037 2322 'option_group_id' => $optionGroupID,
48e399ac
EM
2323 'label' => 'pick me',
2324 'value' => 55,
7cf5bd55 2325 ]);
48e399ac 2326
48e399ac
EM
2327 CRM_Core_DAO::executeQuery("
2328 TRUNCATE civicrm_acl_cache
2329 ");
2330
2331 CRM_Core_DAO::executeQuery("
2332 TRUNCATE civicrm_acl_contact_cache
2333 ");
2334
48e399ac
EM
2335 CRM_Core_DAO::executeQuery("
2336 INSERT INTO civicrm_acl_entity_role (
181f536c 2337 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2338 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
48e399ac
EM
2339 ");
2340
181f536c 2341 if ($isProfile) {
2342 CRM_Core_DAO::executeQuery("
2343 INSERT INTO civicrm_acl (
2344 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2345 )
2346 VALUES (
2347 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2348 );
2349 ");
2350 }
2351 else {
2352 CRM_Core_DAO::executeQuery("
2353 INSERT INTO civicrm_acl (
2354 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2355 )
2356 VALUES (
2357 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2358 );
2359 ");
2360
2361 CRM_Core_DAO::executeQuery("
2362 INSERT INTO civicrm_acl (
2363 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2364 )
2365 VALUES (
2366 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2367 );
2368 ");
181f536c 2369 }
48e399ac 2370
48e399ac 2371 $this->_loggedInUser = CRM_Core_Session::singleton()->get('userID');
7cf5bd55 2372 $this->callAPISuccess('group_contact', 'create', [
48e399ac
EM
2373 'group_id' => $this->_permissionedGroup,
2374 'contact_id' => $this->_loggedInUser,
7cf5bd55 2375 ]);
08a2ea5e 2376
2377 if (!$isProfile) {
2378 //flush cache
2379 CRM_ACL_BAO_Cache::resetCache();
340c24cc 2380 CRM_ACL_API::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
08a2ea5e 2381 }
48e399ac
EM
2382 }
2383
cab024d4 2384 /**
100fef9d 2385 * Alter default price set so that the field numbers are not all 1 (hiding errors)
cab024d4 2386 */
00be9182 2387 public function offsetDefaultPriceSet() {
7cf5bd55 2388 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
cab024d4 2389 $firstID = $contributionPriceSet['id'];
7cf5bd55 2390 $this->callAPISuccess('price_set', 'create', [
92915c55
TO
2391 'id' => $contributionPriceSet['id'],
2392 'is_active' => 0,
2393 'name' => 'old',
7cf5bd55 2394 ]);
cab024d4
EM
2395 unset($contributionPriceSet['id']);
2396 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
7cf5bd55 2397 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
92915c55 2398 'price_set_id' => $firstID,
7cf5bd55 2399 'options' => ['limit' => 1],
2400 ]);
cab024d4
EM
2401 unset($priceField['id']);
2402 $priceField['price_set_id'] = $newPriceSet['id'];
2403 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
7cf5bd55 2404 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
92915c55
TO
2405 'price_set_id' => $firstID,
2406 'sequential' => 1,
7cf5bd55 2407 'options' => ['limit' => 1],
2408 ]);
cab024d4
EM
2409
2410 unset($priceFieldValue['id']);
2411 //create some padding to use up ids
2412 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2413 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
7cf5bd55 2414 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
cab024d4
EM
2415 }
2416
4aef704e 2417 /**
eceb18cc 2418 * Create an instance of the paypal processor.
7cf5bd55 2419 *
4aef704e 2420 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2421 * this parent class & we don't have a structure for that yet
2422 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
e4f46be0 2423 * & the best protection against that is the functions this class affords
7cf5bd55 2424 *
1e1fdcf6 2425 * @param array $params
7cf5bd55 2426 *
79d7553f 2427 * @return int $result['id'] payment processor id
4aef704e 2428 */
7cf5bd55 2429 public function paymentProcessorCreate($params = []) {
2430 $params = array_merge([
39b959db
SL
2431 'name' => 'demo',
2432 'domain_id' => CRM_Core_Config::domainID(),
2433 'payment_processor_type_id' => 'PayPal',
2434 'is_active' => 1,
2435 'is_default' => 0,
2436 'is_test' => 1,
2437 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2438 'password' => '1183377788',
2439 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2440 'url_site' => 'https://www.sandbox.paypal.com/',
2441 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2442 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2443 'class_name' => 'Payment_PayPalImpl',
2444 'billing_mode' => 3,
2445 'financial_type_id' => 1,
2446 'financial_account_id' => 12,
2447 // Credit card = 1 so can pass 'by accident'.
2448 'payment_instrument_id' => 'Debit Card',
7cf5bd55 2449 ], $params);
5896d037 2450 if (!is_numeric($params['payment_processor_type_id'])) {
4aef704e 2451 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2452 //here
7cf5bd55 2453 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
4aef704e 2454 'name' => $params['payment_processor_type_id'],
2455 'return' => 'id',
7cf5bd55 2456 ], 'integer');
4aef704e 2457 }
2458 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2459 return $result['id'];
2460 }
a9ac877b 2461
0dbefed3 2462 /**
eceb18cc 2463 * Set up initial recurring payment allowing subsequent IPN payments.
9f68fe61
MW
2464 *
2465 * @param array $recurParams (Optional)
2466 * @param array $contributionParams (Optional)
7aeb7f06 2467 *
2468 * @throws \CRM_Core_Exception
0dbefed3 2469 */
9f68fe61
MW
2470 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2471 $contributionParams = array_merge([
39b959db
SL
2472 'total_amount' => '200',
2473 'invoice_id' => $this->_invoiceID,
2474 'financial_type_id' => 'Donation',
2475 'contribution_status_id' => 'Pending',
2476 'contact_id' => $this->_contactID,
2477 'contribution_page_id' => $this->_contributionPageID,
2478 'payment_processor_id' => $this->_paymentProcessorID,
2479 'is_test' => 0,
f03241be 2480 'receive_date' => '2019-07-25 07:34:23',
39b959db
SL
2481 'skipCleanMoney' => TRUE,
2482 ], $contributionParams);
7cf5bd55 2483 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
0dbefed3
EM
2484 'contact_id' => $this->_contactID,
2485 'amount' => 1000,
2486 'sequential' => 1,
2487 'installments' => 5,
2488 'frequency_unit' => 'Month',
2489 'frequency_interval' => 1,
2490 'invoice_id' => $this->_invoiceID,
2491 'contribution_status_id' => 2,
481312d9 2492 'payment_processor_id' => $this->_paymentProcessorID,
2493 // processor provided ID - use contact ID as proxy.
2494 'processor_id' => $this->_contactID,
0dea0c7c 2495 'api.Order.create' => $contributionParams,
2496 ], $recurParams))['values'][0];
0dbefed3 2497 $this->_contributionRecurID = $contributionRecur['id'];
0dea0c7c 2498 $this->_contributionID = $contributionRecur['api.Order.create']['id'];
7aeb7f06 2499 $this->ids['Contribution'][0] = $this->_contributionID;
0dbefed3 2500 }
a86d27fc 2501
a9ac877b 2502 /**
100fef9d 2503 * 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
2504 *
2505 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
7aeb7f06 2506 *
2507 * @throws \CRM_Core_Exception
a9ac877b 2508 */
7cf5bd55 2509 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2510 $membershipParams = $recurParams = [];
b3c283b4
MW
2511 if (!empty($params['duration_unit'])) {
2512 $membershipParams['duration_unit'] = $params['duration_unit'];
2513 }
2514 if (!empty($params['frequency_unit'])) {
2515 $recurParams['frequency_unit'] = $params['frequency_unit'];
2516 }
2517
2518 $this->ids['membership_type'] = $this->membershipTypeCreate($membershipParams);
69140e67 2519 //create a contribution so our membership & contribution don't both have id = 1
0dea0c7c 2520 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
7cf5bd55 2521 $this->contributionCreate([
b6b59c64 2522 'contact_id' => $this->_contactID,
2523 'is_test' => 1,
2524 'financial_type_id' => 1,
2525 'invoice_id' => 'abcd',
2526 'trxn_id' => 345,
f03241be 2527 'receive_date' => '2019-07-25 07:34:23',
7cf5bd55 2528 ]);
b6b59c64 2529 }
69140e67 2530
0dea0c7c 2531 $this->ids['membership'] = $this->callAPISuccess('Membership', 'create', [
a9ac877b
EM
2532 'contact_id' => $this->_contactID,
2533 'membership_type_id' => $this->ids['membership_type'],
a9ac877b 2534 'format.only_id' => TRUE,
f03241be 2535 'source' => 'Payment',
0dea0c7c 2536 'skipLineItem' => TRUE,
7cf5bd55 2537 ]);
0dea0c7c 2538 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2539 'line_items' => [
2540 [
2541 'line_item' => [
2542 [
2543 'entity_table' => 'civicrm_membership',
2544 'entity_id' => $this->ids['membership'],
2545 'label' => 'General',
2546 'qty' => 1,
2547 'unit_price' => 200,
2548 'line_total' => 200,
2549 'financial_type_id' => 1,
2550 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2551 'return' => 'id',
2552 'label' => 'Membership Amount',
2553 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2554 ]),
2555 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2556 'return' => 'id',
2557 'label' => 'General',
2558 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2559 ]),
2560 ],
2561 ],
2562 ],
2563 ],
7cf5bd55 2564 ]);
0dea0c7c 2565 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids['membership'], 'contribution_recur_id' => $this->_contributionRecurID]);
a9ac877b 2566 }
6a488035 2567
4cbe18b8
EM
2568 /**
2569 * @param $message
2570 *
2571 * @throws Exception
a9ac877b 2572 */
00be9182 2573 public function CiviUnitTestCase_fatalErrorHandler($message) {
a9ac877b
EM
2574 throw new Exception("{$message['message']}: {$message['code']}");
2575 }
4aef704e 2576
d67f1f28 2577 /**
eceb18cc 2578 * Wrap the entire test case in a transaction.
d67f1f28
TO
2579 *
2580 * Only subsequent DB statements will be wrapped in TX -- this cannot
2581 * retroactively wrap old DB statements. Therefore, it makes sense to
2582 * call this at the beginning of setUp().
2583 *
2584 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2585 * this option does not work with, e.g., custom-data.
2586 *
2587 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2588 * if TRUNCATE or ALTER is called while using a transaction.
2589 *
e16033b4
TO
2590 * @param bool $nest
2591 * Whether to use nesting or reference-counting.
d67f1f28 2592 */
00be9182 2593 public function useTransaction($nest = TRUE) {
d67f1f28
TO
2594 if (!$this->tx) {
2595 $this->tx = new CRM_Core_Transaction($nest);
2596 $this->tx->rollback();
2597 }
2598 }
a335f6b2 2599
b3c30fda 2600 /**
54957108 2601 * Assert the attachment exists.
2602 *
2603 * @param bool $exists
b3c30fda
CW
2604 * @param array $apiResult
2605 */
2606 protected function assertAttachmentExistence($exists, $apiResult) {
2607 $fileId = $apiResult['id'];
2608 $this->assertTrue(is_numeric($fileId));
2609 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
7cf5bd55 2610 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2611 1 => [$fileId, 'Int'],
2612 ]);
2613 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2614 1 => [$fileId, 'Int'],
2615 ]);
b3c30fda
CW
2616 }
2617
299c1530 2618 /**
2619 * Assert 2 sql strings are the same, ignoring double spaces.
2620 *
2621 * @param string $expectedSQL
2622 * @param string $actualSQL
2623 * @param string $message
2624 */
2625 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2626 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2627 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2628 $this->assertEquals($expected, $actual, $message);
2629 }
2630
c039f658 2631 /**
2632 * Create a price set for an event.
2633 *
2634 * @param int $feeTotal
601c7a24 2635 * @param int $minAmt
f660d2ad 2636 * @param string $type
c039f658 2637 *
2d6f64f4 2638 * @param array $options
2639 *
c039f658 2640 * @return int
2641 * Price Set ID.
74531938 2642 * @throws \CRM_Core_Exception
c039f658 2643 */
2d6f64f4 2644 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
c039f658 2645 // creating price set, price field
2646 $paramsSet['title'] = 'Price Set';
2647 $paramsSet['name'] = CRM_Utils_String::titleToVar('Price Set');
2648 $paramsSet['is_active'] = FALSE;
2649 $paramsSet['extends'] = 1;
601c7a24 2650 $paramsSet['min_amount'] = $minAmt;
c039f658 2651
f660d2ad 2652 $priceSet = CRM_Price_BAO_PriceSet::create($paramsSet);
2653 $this->_ids['price_set'] = $priceSet->id;
c039f658 2654
7cf5bd55 2655 $paramsField = [
c039f658 2656 'label' => 'Price Field',
2657 'name' => CRM_Utils_String::titleToVar('Price Field'),
f660d2ad 2658 'html_type' => $type,
c039f658 2659 'price' => $feeTotal,
7cf5bd55 2660 'option_label' => ['1' => 'Price Field'],
2661 'option_value' => ['1' => $feeTotal],
2662 'option_name' => ['1' => $feeTotal],
2663 'option_weight' => ['1' => 1],
2664 'option_amount' => ['1' => 1],
c039f658 2665 'is_display_amounts' => 1,
2666 'weight' => 1,
2667 'options_per_line' => 1,
7cf5bd55 2668 'is_active' => ['1' => 1],
f660d2ad 2669 'price_set_id' => $this->_ids['price_set'],
c039f658 2670 'is_enter_qty' => 1,
8484a5f0 2671 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
7cf5bd55 2672 ];
f660d2ad 2673 if ($type === 'Radio') {
2d6f64f4 2674 foreach ($options as $index => $option) {
2675 $paramsField['is_enter_qty'] = 0;
2676 $optionID = $index + 2;
2677 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2678 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2679 }
2680
f660d2ad 2681 }
74531938 2682 $this->callAPISuccess('PriceField', 'create', $paramsField);
7cf5bd55 2683 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids['price_set']]);
f660d2ad 2684 $this->_ids['price_field'] = array_keys($fields['values']);
7cf5bd55 2685 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids['price_field'][0]]);
f660d2ad 2686 $this->_ids['price_field_value'] = array_keys($fieldValues['values']);
c039f658 2687
f660d2ad 2688 return $this->_ids['price_set'];
c039f658 2689 }
2690
481312d9 2691 /**
2692 * Add a profile to a contribution page.
2693 *
2694 * @param string $name
2695 * @param int $contributionPageID
5d6cf648 2696 * @param string $module
481312d9 2697 */
5d6cf648
JM
2698 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2699 $params = [
481312d9 2700 'uf_group_id' => $name,
5d6cf648 2701 'module' => $module,
481312d9 2702 'entity_table' => 'civicrm_contribution_page',
2703 'entity_id' => $contributionPageID,
2704 'weight' => 1,
5d6cf648
JM
2705 ];
2706 if ($module !== 'CiviContribute') {
2707 $params['module_data'] = [$module => []];
2708 }
2709 $this->callAPISuccess('UFJoin', 'create', $params);
481312d9 2710 }
2711
db62fd2b
PN
2712 /**
2713 * Add participant with contribution
2714 *
2715 * @return array
f3e6da5e 2716 *
2717 * @throws \CRM_Core_Exception
db62fd2b 2718 */
5266bd48 2719 protected function createPartiallyPaidParticipantOrder() {
2720 $orderParams = $this->getParticipantOrderParams();
2721 $orderParams['api.Payment.create'] = ['total_amount' => 150];
f3e6da5e 2722 return $this->callAPISuccess('Order', 'create', $orderParams);
db62fd2b
PN
2723 }
2724
73c0e107
PN
2725 /**
2726 * Create price set
2727 *
2728 * @param string $component
2729 * @param int $componentId
39b959db 2730 * @param array $priceFieldOptions
73c0e107
PN
2731 *
2732 * @return array
2733 */
7cf5bd55 2734 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
5c3d600f
PN
2735 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2736 $paramsSet['name'] = CRM_Utils_String::titleToVar($paramsSet['title']);
73c0e107 2737 $paramsSet['is_active'] = TRUE;
faba82fc 2738 $paramsSet['financial_type_id'] = 'Event Fee';
73c0e107
PN
2739 $paramsSet['extends'] = 1;
2740 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2741 $priceSetId = $priceSet['id'];
2742 //Checking for priceset added in the table.
2743 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2744 'id', $paramsSet['title'], 'Check DB for created priceset'
2745 );
7cf5bd55 2746 $paramsField = array_merge([
73c0e107
PN
2747 'label' => 'Price Field',
2748 'name' => CRM_Utils_String::titleToVar('Price Field'),
2749 'html_type' => 'CheckBox',
7cf5bd55 2750 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2751 'option_value' => ['1' => 100, '2' => 200],
2752 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2753 'option_weight' => ['1' => 1, '2' => 2],
2754 'option_amount' => ['1' => 100, '2' => 200],
73c0e107
PN
2755 'is_display_amounts' => 1,
2756 'weight' => 1,
2757 'options_per_line' => 1,
7cf5bd55 2758 'is_active' => ['1' => 1, '2' => 1],
73c0e107
PN
2759 'price_set_id' => $priceSet['id'],
2760 'is_enter_qty' => 1,
8484a5f0 2761 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
7cf5bd55 2762 ], $priceFieldOptions);
c91b1cc3 2763
73c0e107
PN
2764 $priceField = CRM_Price_BAO_PriceField::create($paramsField);
2765 if ($componentId) {
2766 CRM_Price_BAO_PriceSet::addTo('civicrm_' . $component, $componentId, $priceSetId);
2767 }
7cf5bd55 2768 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id]);
73c0e107
PN
2769 }
2770
b80f2ad1
E
2771 /**
2772 * Replace the template with a test-oriented template designed to show all the variables.
2773 *
2774 * @param string $templateName
7a2ee417 2775 * @param string $type
b80f2ad1 2776 */
7a2ee417 2777 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2778 $testTemplate = file_get_contents(__DIR__ . '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
b80f2ad1
E
2779 CRM_Core_DAO::executeQuery(
2780 "UPDATE civicrm_option_group og
2781 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2782 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
7a2ee417 2783 SET m.msg_{$type} = %1
2784 WHERE og.name LIKE 'msg_tpl_workflow_%'
b80f2ad1 2785 AND ov.name = '{$templateName}'
7a2ee417 2786 AND m.is_default = 1", [1 => [$testTemplate, 'String']]
b80f2ad1
E
2787 );
2788 }
2789
2790 /**
2791 * Reinstate the default template.
2792 *
2793 * @param string $templateName
7a2ee417 2794 * @param string $type
b80f2ad1 2795 */
7a2ee417 2796 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
b80f2ad1
E
2797 CRM_Core_DAO::executeQuery(
2798 "UPDATE civicrm_option_group og
2799 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2800 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2801 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
7a2ee417 2802 SET m.msg_{$type} = m2.msg_{$type}
b80f2ad1
E
2803 WHERE og.name = 'msg_tpl_workflow_contribution'
2804 AND ov.name = '{$templateName}'
2805 AND m.is_default = 1"
2806 );
2807 }
2808
8d35246a
EM
2809 /**
2810 * Flush statics relating to financial type.
2811 */
2812 protected function flushFinancialTypeStatics() {
2813 if (isset(\Civi::$statics['CRM_Financial_BAO_FinancialType'])) {
2814 unset(\Civi::$statics['CRM_Financial_BAO_FinancialType']);
2815 }
4954339a
EM
2816 if (isset(\Civi::$statics['CRM_Contribute_PseudoConstant'])) {
2817 unset(\Civi::$statics['CRM_Contribute_PseudoConstant']);
2818 }
8d35246a 2819 CRM_Contribute_PseudoConstant::flush('financialType');
4954339a
EM
2820 CRM_Contribute_PseudoConstant::flush('membershipType');
2821 // Pseudoconstants may be saved to the cache table.
2822 CRM_Core_DAO::executeQuery("TRUNCATE civicrm_cache");
7cf5bd55 2823 CRM_Financial_BAO_FinancialType::$_statusACLFt = [];
8d35246a
EM
2824 CRM_Financial_BAO_FinancialType::$_availableFinancialTypes = NULL;
2825 }
2826
2827 /**
2828 * Set the permissions to the supplied array.
2829 *
2830 * @param array $permissions
2831 */
2832 protected function setPermissions($permissions) {
2833 CRM_Core_Config::singleton()->userPermissionClass->permissions = $permissions;
2834 $this->flushFinancialTypeStatics();
2835 }
2836
bf722049 2837 /**
2838 * @param array $params
2839 * @param $context
2840 */
2841 public function _checkFinancialRecords($params, $context) {
7cf5bd55 2842 $entityParams = [
bf722049 2843 'entity_id' => $params['id'],
2844 'entity_table' => 'civicrm_contribution',
7cf5bd55 2845 ];
2846 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
bf722049 2847 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2848 if ($context == 'pending') {
2849 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
2850 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2851 return;
2852 }
2853 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
7cf5bd55 2854 $trxnParams = [
bf722049 2855 'id' => $trxn['financial_trxn_id'],
7cf5bd55 2856 ];
bf722049 2857 if ($context != 'online' && $context != 'payLater') {
7cf5bd55 2858 $compareParams = [
bf722049 2859 'to_financial_account_id' => 6,
2860 'total_amount' => CRM_Utils_Array::value('total_amount', $params, 100),
2861 'status_id' => 1,
7cf5bd55 2862 ];
bf722049 2863 }
2864 if ($context == 'feeAmount') {
2865 $compareParams['fee_amount'] = 50;
2866 }
2867 elseif ($context == 'online') {
7cf5bd55 2868 $compareParams = [
bf722049 2869 'to_financial_account_id' => 12,
2870 'total_amount' => CRM_Utils_Array::value('total_amount', $params, 100),
2871 'status_id' => 1,
f69a9ac3 2872 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, 1),
7cf5bd55 2873 ];
bf722049 2874 }
2875 elseif ($context == 'payLater') {
7cf5bd55 2876 $compareParams = [
bf722049 2877 'to_financial_account_id' => 7,
2878 'total_amount' => CRM_Utils_Array::value('total_amount', $params, 100),
2879 'status_id' => 2,
7cf5bd55 2880 ];
bf722049 2881 }
2882 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
7cf5bd55 2883 $entityParams = [
bf722049 2884 'financial_trxn_id' => $trxn['financial_trxn_id'],
2885 'entity_table' => 'civicrm_financial_item',
7cf5bd55 2886 ];
bf722049 2887 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
7cf5bd55 2888 $fitemParams = [
bf722049 2889 'id' => $entityTrxn['entity_id'],
7cf5bd55 2890 ];
2891 $compareParams = [
bf722049 2892 'amount' => CRM_Utils_Array::value('total_amount', $params, 100),
2893 'status_id' => 1,
1a1a2f4f 2894 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
7cf5bd55 2895 ];
bf722049 2896 if ($context == 'payLater') {
7cf5bd55 2897 $compareParams = [
bf722049 2898 'amount' => CRM_Utils_Array::value('total_amount', $params, 100),
2899 'status_id' => 3,
1a1a2f4f 2900 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
7cf5bd55 2901 ];
bf722049 2902 }
2903 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2904 if ($context == 'feeAmount') {
7cf5bd55 2905 $maxParams = [
bf722049 2906 'entity_id' => $params['id'],
2907 'entity_table' => 'civicrm_contribution',
7cf5bd55 2908 ];
bf722049 2909 $maxTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($maxParams, TRUE));
7cf5bd55 2910 $trxnParams = [
bf722049 2911 'id' => $maxTrxn['financial_trxn_id'],
7cf5bd55 2912 ];
2913 $compareParams = [
bf722049 2914 'to_financial_account_id' => 5,
2915 'from_financial_account_id' => 6,
2916 'total_amount' => 50,
2917 'status_id' => 1,
7cf5bd55 2918 ];
bf722049 2919 $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['id'], 'DESC');
2920 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
7cf5bd55 2921 $fitemParams = [
bf722049 2922 'entity_id' => $trxnId['financialTrxnId'],
2923 'entity_table' => 'civicrm_financial_trxn',
7cf5bd55 2924 ];
2925 $compareParams = [
bf722049 2926 'amount' => 50,
2927 'status_id' => 1,
2928 'financial_account_id' => 5,
7cf5bd55 2929 ];
bf722049 2930 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2931 }
2932 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2933 // line should be copied into all the functions that call this function & evaluated there
2934 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2935 // when calling completeTransaction or repeatTransaction.
7cf5bd55 2936 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
bf722049 2937 }
2938
8484a5f0 2939 /**
2940 * Return financial type id on basis of name
2941 *
2942 * @param string $name Financial type m/c name
2943 *
2944 * @return int
2945 */
2946 public function getFinancialTypeId($name) {
2947 return CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2948 }
2949
204beda3 2950 /**
2951 * Cleanup function for contents of $this->ids.
2952 *
2953 * This is a best effort cleanup to use in tear downs etc.
2954 *
2955 * It will not fail if the data has already been removed (some tests may do
2956 * their own cleanup).
2957 */
2958 protected function cleanUpSetUpIDs() {
2959 foreach ($this->setupIDs as $entity => $id) {
2960 try {
7cf5bd55 2961 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
204beda3 2962 }
2963 catch (CiviCRM_API3_Exception $e) {
2964 // This is a best-effort cleanup function, ignore.
2965 }
2966 }
2967 }
2968
adbc354b
PN
2969 /**
2970 * Create Financial Type.
2971 *
2972 * @param array $params
2973 *
2974 * @return array
2975 */
7cf5bd55 2976 protected function createFinancialType($params = []) {
adbc354b 2977 $params = array_merge($params,
7cf5bd55 2978 [
adbc354b
PN
2979 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
2980 'is_active' => 1,
7cf5bd55 2981 ]
adbc354b
PN
2982 );
2983 return $this->callAPISuccess('FinancialType', 'create', $params);
2984 }
2985
e1c5a855 2986 /**
2987 * Create Payment Instrument.
2988 *
2989 * @param array $params
2990 * @param string $financialAccountName
2991 *
2992 * @return int
2993 */
7cf5bd55 2994 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
2995 $params = array_merge([
e1c5a855 2996 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
2997 'option_group_id' => 'payment_instrument',
2998 'is_active' => 1,
7cf5bd55 2999 ], $params);
897ff8c5 3000 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
e1c5a855 3001
3002 $relationTypeID = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3003
3004 $financialAccountParams = [
3005 'entity_table' => 'civicrm_option_value',
897ff8c5 3006 'entity_id' => $newPaymentInstrument,
e1c5a855 3007 'account_relationship' => $relationTypeID,
3008 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3009 ];
3010 CRM_Financial_BAO_FinancialTypeAccount::add($financialAccountParams);
3011
3012 return CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3013 }
3014
ec5da26a 3015 /**
3016 * Enable Tax and Invoicing
b086dc3a 3017 *
3018 * @param array $params
3019 *
3020 * @return \Civi\Core\SettingsBag
ec5da26a 3021 */
7cf5bd55 3022 protected function enableTaxAndInvoicing($params = []) {
ec5da26a 3023 // Enable component contribute setting
3024 $contributeSetting = array_merge($params,
7cf5bd55 3025 [
ec5da26a 3026 'invoicing' => 1,
3027 'invoice_prefix' => 'INV_',
3028 'credit_notes_prefix' => 'CN_',
3029 'due_date' => 10,
3030 'due_date_period' => 'days',
3031 'notes' => '',
3032 'is_email_pdf' => 1,
3033 'tax_term' => 'Sales Tax',
3034 'tax_display_settings' => 'Inclusive',
7cf5bd55 3035 ]
ec5da26a 3036 );
3037 return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
3038 }
3039
9fbf312f 3040 /**
3041 * Enable Tax and Invoicing
3042 */
7cf5bd55 3043 protected function disableTaxAndInvoicing($params = []) {
f436577a 3044 if (!empty(\Civi::$statics['CRM_Core_PseudoConstant']) && isset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3045 unset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates']);
3046 }
9fbf312f 3047 // Enable component contribute setting
3048 $contributeSetting = array_merge($params,
7cf5bd55 3049 [
9fbf312f 3050 'invoicing' => 0,
7cf5bd55 3051 ]
9fbf312f 3052 );
3053 return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
3054 }
3055
7a1f3919
PN
3056 /**
3057 * Add Sales Tax relation for financial type with financial account.
3058 *
3059 * @param int $financialTypeId
3060 *
3061 * @return obj
3062 */
3063 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
7cf5bd55 3064 $params = [
7a1f3919
PN
3065 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3066 'financial_account_type_id' => key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3067 'is_deductible' => 1,
3068 'is_tax' => 1,
3069 'tax_rate' => 10,
3070 'is_active' => 1,
7cf5bd55 3071 ];
7a1f3919 3072 $account = CRM_Financial_BAO_FinancialAccount::add($params);
7cf5bd55 3073 $entityParams = [
7a1f3919 3074 'entity_table' => 'civicrm_financial_type',
7a1f3919 3075 'entity_id' => $financialTypeId,
5b3543ce 3076 'account_relationship' => key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
7cf5bd55 3077 ];
5b3543ce 3078
f436577a 3079 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3080 \Civi::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3081
5b3543ce
JM
3082 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3083 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3084 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3085 $dao->copyValues($entityParams);
3086 $dao->find();
3087 if ($dao->fetch()) {
3088 $entityParams['id'] = $dao->id;
3089 }
3090 $entityParams['financial_account_id'] = $account->id;
3091
7a1f3919
PN
3092 return CRM_Financial_BAO_FinancialTypeAccount::add($entityParams);
3093 }
3094
65e172a3 3095 /**
3096 * Create price set with contribution test for test setup.
3097 *
3098 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3099 * on parent class at some point (fn is not in 4.4).
3100 *
3101 * @param $entity
3102 * @param array $params
3103 */
7cf5bd55 3104 public function createPriceSetWithPage($entity = NULL, $params = []) {
3105 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3106 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
65e172a3 3107 'title' => "Test Contribution Page",
3108 'financial_type_id' => 1,
3109 'currency' => 'NZD',
3110 'goal_amount' => 50,
3111 'is_pay_later' => 1,
3112 'is_monetary' => TRUE,
3113 'is_email_receipt' => FALSE,
7cf5bd55 3114 ]);
3115 $priceSet = $this->callAPISuccess('price_set', 'create', [
65e172a3 3116 'is_quick_config' => 0,
3117 'extends' => 'CiviMember',
3118 'financial_type_id' => 1,
3119 'title' => 'my Page',
7cf5bd55 3120 ]);
65e172a3 3121 $priceSetID = $priceSet['id'];
3122
3123 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
7cf5bd55 3124 $priceField = $this->callAPISuccess('price_field', 'create', [
65e172a3 3125 'price_set_id' => $priceSetID,
3126 'label' => 'Goat Breed',
3127 'html_type' => 'Radio',
7cf5bd55 3128 ]);
3129 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3130 'price_set_id' => $priceSetID,
3131 'price_field_id' => $priceField['id'],
3132 'label' => 'Long Haired Goat',
3133 'amount' => 20,
3134 'financial_type_id' => 'Donation',
3135 'membership_type_id' => $membershipTypeID,
3136 'membership_num_terms' => 1,
7cf5bd55 3137 ]);
3138 $this->_ids['price_field_value'] = [$priceFieldValue['id']];
3139 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3140 'price_set_id' => $priceSetID,
3141 'price_field_id' => $priceField['id'],
3142 'label' => 'Shoe-eating Goat',
3143 'amount' => 10,
3144 'financial_type_id' => 'Donation',
3145 'membership_type_id' => $membershipTypeID,
3146 'membership_num_terms' => 2,
7cf5bd55 3147 ]);
65e172a3 3148 $this->_ids['price_field_value'][] = $priceFieldValue['id'];
3149
7cf5bd55 3150 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
39b959db
SL
3151 'price_set_id' => $priceSetID,
3152 'price_field_id' => $priceField['id'],
3153 'label' => 'Shoe-eating Goat',
3154 'amount' => 10,
3155 'financial_type_id' => 'Donation',
7cf5bd55 3156 ]);
65e172a3 3157 $this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];
3158
3159 $this->_ids['price_set'] = $priceSetID;
3160 $this->_ids['contribution_page'] = $contributionPageResult['id'];
7cf5bd55 3161 $this->_ids['price_field'] = [$priceField['id']];
65e172a3 3162
3163 $this->_ids['membership_type'] = $membershipTypeID;
3164 }
3165
3c9d67b0 3166 /**
3167 * Only specified contact returned.
7cf5bd55 3168 *
3c9d67b0 3169 * @implements CRM_Utils_Hook::aclWhereClause
7cf5bd55 3170 *
3c9d67b0 3171 * @param $type
3172 * @param $tables
3173 * @param $whereTables
3174 * @param $contactID
3175 * @param $where
3176 */
3177 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3178 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts) . ")";
3179 }
3180
88ebed7c
JP
3181 /**
3182 * @implements CRM_Utils_Hook::selectWhereClause
3183 *
3184 * @param string $entity
3185 * @param array $clauses
3186 */
3187 public function selectWhereClauseHook($entity, &$clauses) {
3188 if ($entity == 'Event') {
3189 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3190 }
3191 }
3192
29a59599
JP
3193 /**
3194 * An implementation of hook_civicrm_post used with all our test cases.
3195 *
3196 * @param $op
3197 * @param string $objectName
3198 * @param int $objectId
3199 * @param $objectRef
3200 */
3201 public function onPost($op, $objectName, $objectId, &$objectRef) {
3202 if ($op == 'create' && $objectName == 'Individual') {
3203 CRM_Core_DAO::executeQuery(
3204 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
7cf5bd55 3205 [
3206 1 => [$objectId, 'Integer'],
3207 ]
29a59599
JP
3208 );
3209 }
3210
3211 if ($op == 'edit' && $objectName == 'Participant') {
7cf5bd55 3212 $params = [
3213 1 => [$objectId, 'Integer'],
3214 ];
29a59599
JP
3215 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3216 CRM_Core_DAO::executeQuery($query, $params);
3217 }
3218 }
3219
f8df7165 3220 /**
3221 * Instantiate form object.
3222 *
3223 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3224 *
3225 * @param string $class
3226 * Name of form class.
3227 *
4715d267 3228 * @param array $formValues
3229 *
3230 * @param string $pageName
3231 *
f8df7165 3232 * @return \CRM_Core_Form
4715d267 3233 * @throws \CRM_Core_Exception
f8df7165 3234 */
4715d267 3235 public function getFormObject($class, $formValues = [], $pageName = '') {
f8df7165 3236 $form = new $class();
3237 $_SERVER['REQUEST_METHOD'] = 'GET';
3238 $form->controller = new CRM_Core_Controller();
4715d267 3239 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3240 $_SESSION['_' . $form->controller->_name . '_container']['values'][$pageName] = $formValues;
f8df7165 3241 return $form;
3242 }
3243
83644f47 3244 /**
3245 * Get possible thousand separators.
3246 *
3247 * @return array
3248 */
3249 public function getThousandSeparators() {
7cf5bd55 3250 return [['.'], [',']];
83644f47 3251 }
3252
ebebd629 3253 /**
3254 * Get the boolean options as a provider.
3255 *
3256 * @return array
3257 */
3258 public function getBooleanDataProvider() {
3259 return [[TRUE], [FALSE]];
3260 }
3261
83644f47 3262 /**
3263 * Set the separators for thousands and decimal points.
3264 *
3265 * @param string $thousandSeparator
3266 */
3267 protected function setCurrencySeparators($thousandSeparator) {
3268 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3269 Civi::settings()
3270 ->set('monetaryDecimalPoint', ($thousandSeparator === ',' ? '.' : ','));
3271 }
3272
3273 /**
3274 * Format money as it would be input.
3275 *
3276 * @param string $amount
3277 *
3278 * @return string
3279 */
3280 protected function formatMoneyInput($amount) {
3281 return CRM_Utils_Money::format($amount, NULL, '%a');
3282 }
3283
3ca4bd1b 3284 /**
3285 * Get the contribution object.
3286 *
3287 * @param int $contributionID
3288 *
3289 * @return \CRM_Contribute_BAO_Contribution
3290 */
3291 protected function getContributionObject($contributionID) {
3292 $contributionObj = new CRM_Contribute_BAO_Contribution();
3293 $contributionObj->id = $contributionID;
3294 $contributionObj->find(TRUE);
3295 return $contributionObj;
3296 }
3297
df3320dc 3298 /**
3299 * Enable multilingual.
3300 */
3301 public function enableMultilingual() {
7cf5bd55 3302 $this->callAPISuccess('Setting', 'create', [
df3320dc 3303 'lcMessages' => 'en_US',
7cf5bd55 3304 'languageLimit' => [
df3320dc 3305 'en_US' => 1,
7cf5bd55 3306 ],
3307 ]);
df3320dc 3308
3309 CRM_Core_I18n_Schema::makeMultilingual('en_US');
3310
3311 global $dbLocale;
3312 $dbLocale = '_en_US';
3313 }
3314
51c566a3
SL
3315 /**
3316 * Setup or clean up SMS tests
7cf5bd55 3317 *
51c566a3
SL
3318 * @param bool $teardown
3319 *
3320 * @throws \CiviCRM_API3_Exception
3321 */
3322 public function setupForSmsTests($teardown = FALSE) {
3323 require_once 'CiviTest/CiviTestSMSProvider.php';
3324
3325 // Option value params for CiviTestSMSProvider
3326 $groupID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
7cf5bd55 3327 $params = [
51c566a3
SL
3328 'option_group_id' => $groupID,
3329 'label' => 'unittestSMS',
3330 'value' => 'unit.test.sms',
7cf5bd55 3331 'name' => 'CiviTestSMSProvider',
51c566a3 3332 'is_default' => 1,
7cf5bd55 3333 'is_active' => 1,
3334 'version' => 3,
3335 ];
51c566a3
SL
3336
3337 if ($teardown) {
3338 // Test completed, delete provider
3339 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
7cf5bd55 3340 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
51c566a3
SL
3341 return;
3342 }
3343
3344 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3345 // in CRM/Sms/Provider.php even though it is not an extension.
3346 return civicrm_api3('option_value', 'create', $params);
3347 }
3348
68989e71 3349 /**
3350 * Start capturing browser output.
3351 *
3352 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3353 */
3354 protected function startCapturingOutput() {
3355 ob_start();
3356 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3357 }
3358
3359 /**
3360 * Stop capturing browser output and return as a csv.
3361 *
3362 * @param bool $isFirstRowHeaders
3363 *
3364 * @return \League\Csv\Reader
3365 *
3366 * @throws \League\Csv\Exception
3367 */
3368 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3369 $output = ob_get_flush();
3370 $stream = fopen('php://memory', 'r+');
3371 fwrite($stream, $output);
3372 rewind($stream);
1fc9f4a7 3373 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
68989e71 3374 $csv = Reader::createFromString($output);
3375 if ($isFirstRowHeaders) {
3376 $csv->setHeaderOffset(0);
3377 }
3378 ob_clean();
3379 return $csv;
3380 }
3381
91786f44 3382 /**
3383 * Rename various labels to not match the names.
3384 *
3385 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3386 * code that expects it to.
3387 */
3388 protected function renameLabels() {
3389 $replacements = ['Pending', 'Refunded'];
3390 foreach ($replacements as $name) {
3391 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3392 }
3393 }
3394
3395 /**
3396 * Undo any label renaming.
3397 */
3398 protected function resetLabels() {
3399 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3400 }
3401
5266bd48 3402 /**
3403 * Get parameters to set up a multi-line participant order.
3404 *
3405 * @return array
3406 * @throws \CRM_Core_Exception
3407 */
3408 protected function getParticipantOrderParams(): array {
3409 $this->_contactId = $this->individualCreate();
3410 $event = $this->eventCreate();
3411 $this->_eventId = $event['id'];
3412 $eventParams = [
3413 'id' => $this->_eventId,
3414 'financial_type_id' => 4,
3415 'is_monetary' => 1,
3416 ];
3417 $this->callAPISuccess('event', 'create', $eventParams);
3418 $priceFields = $this->createPriceSet('event', $this->_eventId);
3419 $participantParams = [
3420 'financial_type_id' => 4,
3421 'event_id' => $this->_eventId,
3422 'role_id' => 1,
3423 'status_id' => 14,
3424 'fee_currency' => 'USD',
3425 'contact_id' => $this->_contactId,
3426 ];
3427 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3428 $orderParams = [
3429 'total_amount' => 300,
3430 'currency' => 'USD',
3431 'contact_id' => $this->_contactId,
3432 'financial_type_id' => 4,
3433 'contribution_status_id' => 'Pending',
3434 'contribution_mode' => 'participant',
3435 'participant_id' => $participant['id'],
3436 ];
3437 foreach ($priceFields['values'] as $key => $priceField) {
3438 $orderParams['line_items'][] = [
3439 'line_item' => [
3440 [
3441 'price_field_id' => $priceField['price_field_id'],
3442 'price_field_value_id' => $priceField['id'],
3443 'label' => $priceField['label'],
3444 'field_title' => $priceField['label'],
3445 'qty' => 1,
3446 'unit_price' => $priceField['amount'],
3447 'line_total' => $priceField['amount'],
3448 'financial_type_id' => $priceField['financial_type_id'],
3449 'entity_table' => 'civicrm_participant',
3450 ],
3451 ],
3452 'params' => $participant,
3453 ];
3454 }
3455 return $orderParams;
3456 }
3457
3d4f6f65 3458 /**
3459 * @param $payments
3460 *
3461 * @throws \CRM_Core_Exception
3462 */
3463 protected function validatePayments($payments) {
3464 foreach ($payments as $payment) {
4804f442 3465 $balance = CRM_Contribute_BAO_Contribution::getContributionBalance($payment['contribution_id']);
3466 if ($balance < 0 && $balance + $payment['total_amount'] === 0.0) {
3467 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3468 // This is a pretty rough way at guessing which payment is the overpayment - but
3469 // for the test suite it should be enough.
3470 continue;
3471 }
3d4f6f65 3472 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3473 'financial_trxn_id' => $payment['id'],
3474 'entity_table' => 'civicrm_financial_item',
3475 'return' => ['amount'],
3476 ])['values'];
3477 $itemTotal = 0;
3478 foreach ($items as $item) {
3479 $itemTotal += $item['amount'];
3480 }
3481 $this->assertEquals($payment['total_amount'], $itemTotal);
3482 }
3483 }
3484
3485 /**
3486 * Validate all created payments.
3487 *
3488 * @throws \CRM_Core_Exception
3489 */
3490 protected function validateAllPayments() {
3491 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3492 $this->validatePayments($payments);
3493 }
3494
7aeb7f06 3495 /**
3496 * Validate all created contributions.
3497 *
3498 * @throws \CRM_Core_Exception
3499 */
3500 protected function validateAllContributions() {
3501 $contributions = $this->callAPISuccess('Contribution', 'get', [])['values'];
3502 foreach ($contributions as $contribution) {
3503 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3504 $total = 0;
3505 foreach ($lineItems as $lineItem) {
3506 $total += $lineItem['line_total'];
3507 }
3508 $this->assertEquals($total, $contribution['total_amount']);
3509 }
3510 }
3511
a86d27fc 3512}