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