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