3 * File for the CiviUnitTestCase class
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
12 * This file is part of CiviCRM
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.
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.
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/>.
29 use Civi\Api4\Contribution
;
30 use Civi\Api4\CustomField
;
31 use Civi\Api4\CustomGroup
;
32 use Civi\Api4\FinancialType
;
33 use Civi\Api4\LineItem
;
34 use Civi\Api4\OptionGroup
;
35 use Civi\Api4\RelationshipType
;
36 use Civi\Payment\System
;
37 use Civi\Api4\OptionValue
;
38 use Civi\Test\Api3DocTrait
;
39 use League\Csv\Reader
;
42 * Include class definitions
44 require_once 'api/api.php';
45 define('API_LATEST_VERSION', 3);
48 * Base class for CiviCRM unit tests
50 * This class supports two (mutually-exclusive) techniques for cleaning up test data. Subclasses
51 * may opt for one or neither:
53 * 1. quickCleanup() is a helper which truncates a series of tables. Call quickCleanup()
54 * as part of setUp() and/or tearDown(). quickCleanup() is thorough - but it can
55 * be cumbersome to use (b/c you must identify the tables to cleanup) and slow to execute.
56 * 2. useTransaction() executes the test inside a transaction. It's easier to use
57 * (because you don't need to identify specific tables), but it doesn't work for tests
58 * which manipulate schema or truncate data -- and could behave inconsistently
59 * for tests which specifically examine DB transactions.
61 * Common functions for unit tests
65 class CiviUnitTestCase
extends PHPUnit\Framework\TestCase
{
68 use \Civi\Test\GenericAssertionsTrait
;
69 use \Civi\Test\DbTestTrait
;
70 use \Civi\Test\ContactTestTrait
;
71 use \Civi\Test\MailingTestTrait
;
74 * Database has been initialized.
78 private static $dbInit = FALSE;
81 * Database connection.
83 * @var PHPUnit_Extensions_Database_DB_IDatabaseConnection
92 static protected $_dbName;
99 protected $_apiversion = 3;
102 * Track tables we have modified during a test.
106 protected $_tablesToTruncate = [];
110 * Array of temporary directory names
116 * populateOnce allows to skip db resets in setUp
118 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
119 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
122 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
124 * @see http://forum.civicrm.org/index.php/topic,18065.0.html
126 public static $populateOnce = FALSE;
129 * DBResetRequired allows skipping DB reset
130 * in specific test case. If you still need
131 * to reset single test (method) of such case, call
132 * $this->cleanDB() in the first line of this
136 public $DBResetRequired = TRUE;
139 * @var CRM_Core_Transaction|null
144 * Array of IDs created to support the test.
147 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
154 * Should financials be checked after the test but before tear down.
156 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
157 * are some test data issues and some real bugs currently blocking.
161 protected $isValidateFinancialsOnPostAssert = FALSE;
164 * Should location types be checked to ensure primary addresses are correctly assigned after each test.
168 protected $isLocationTypesOnPostAssert = TRUE;
171 * Has the test class been verified as 'getsafe'.
173 * If a class is getsafe it means that where
174 * callApiSuccess is called 'return' is specified or 'return' =>'id'
175 * can be added by that function. This is part of getting away
176 * from open-ended get calls.
178 * Eventually we want to not be doing these in our test classes & start
179 * to work to not do them in our main code base. Note they mainly
180 * cause issues for activity.get and contact.get as these are where the
181 * too many joins limit is most likely to be hit.
185 protected $isGetSafe = FALSE;
188 * Class used for hooks during tests.
190 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
192 * $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
194 * @var \CRM_Utils_Hook_UnitTests
200 * Common values to be re-used multiple times within a class - usually to create the relevant entity
202 protected $_params = [];
205 * @var CRM_Extension_System
207 protected $origExtensionSystem;
210 * Array of IDs created during test setup routine.
212 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
216 public $setupIDs = [];
221 * Because we are overriding the parent class constructor, we
222 * need to show the same arguments as exist in the constructor of
223 * PHPUnit_Framework_TestCase, since
224 * PHPUnit_Framework_TestSuite::createTest() creates a
225 * ReflectionClass of the Test class and checks the constructor
226 * of that class to decide how to set up the test.
228 * @param string $name
230 * @param string $dataName
232 public function __construct($name = NULL, array $data = [], $dataName = '') {
233 parent
::__construct($name, $data, $dataName);
235 // we need full error reporting
236 error_reporting(E_ALL
& ~E_NOTICE
);
238 self
::$_dbName = self
::getDBName();
240 // also load the class loader
241 require_once 'CRM/Core/ClassLoader.php';
242 CRM_Core_ClassLoader
::singleton()->register();
243 if (function_exists('_civix_phpunit_setUp')) {
244 // FIXME: loosen coupling
245 _civix_phpunit_setUp();
250 * Override to run the test and assert its state.
254 * @throws \PHPUnit_Framework_IncompleteTest
255 * @throws \PHPUnit_Framework_SkippedTest
257 protected function runTest() {
259 return parent
::runTest();
261 catch (PEAR_Exception
$e) {
262 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
263 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
270 public function requireDBReset() {
271 return $this->DBResetRequired
;
277 public static function getDBName() {
278 static $dbName = NULL;
279 if ($dbName === NULL) {
280 require_once "DB.php";
281 $dsn = CRM_Utils_SQL
::autoSwitchDSN(CIVICRM_DSN
);
282 $dsninfo = DB
::parseDSN($dsn);
283 $dbName = $dsninfo['database'];
289 * Create database connection for this instance.
291 * Initialize the test database if it hasn't been initialized
294 protected function getConnection() {
295 if (!self
::$dbInit) {
296 $dbName = self
::getDBName();
298 // install test database
299 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
301 static::_populateDB(FALSE, $this);
303 self
::$dbInit = TRUE;
309 * Required implementation of abstract method.
311 protected function getDataSet() {
315 * @param bool $perClass
316 * @param null $object
319 * TRUE if the populate logic runs; FALSE if it is skipped
321 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
322 if (CIVICRM_UF
!== 'UnitTests') {
323 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
326 if ($perClass ||
$object == NULL) {
330 $dbreset = $object->requireDBReset();
333 if (self
::$populateOnce ||
!$dbreset) {
336 self
::$populateOnce = NULL;
338 Civi\Test
::data()->populate();
343 public static function setUpBeforeClass(): void
{
344 static::_populateDB(TRUE);
346 // also set this global hack
347 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
351 * Common setup functions for all unit tests.
353 protected function setUp(): void
{
354 $session = CRM_Core_Session
::singleton();
355 $session->set('userID', NULL);
357 $this->_apiversion
= 3;
359 // Use a temporary file for STDIN
360 $GLOBALS['stdin'] = tmpfile();
361 if ($GLOBALS['stdin'] === FALSE) {
362 echo "Couldn't open temporary file\n";
366 // Get and save a connection to the database
367 $this->_dbconn
= $this->getConnection();
369 // reload database before each test
370 // $this->_populateDB();
372 // "initialize" CiviCRM to avoid problems when running single tests
373 // FIXME: look at it closer in second stage
375 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
376 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
378 // disable any left-over test extensions
379 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
381 // reset all the caches
382 CRM_Utils_System
::flushCache();
384 // initialize the object once db is loaded
385 \Civi
::$statics = [];
387 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
389 // when running unit tests, use mockup user framework
390 $this->hookClass
= CRM_Utils_Hook
::singleton();
392 // Make sure the DB connection is setup properly
393 $config->userSystem
->setMySQLTimeZone();
394 $env = new CRM_Utils_Check_Component_Env();
395 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
397 // clear permissions stub to not check permissions
398 $config->userPermissionClass
->permissions
= NULL;
400 //flush component settings
401 CRM_Core_Component
::getEnabledComponents(TRUE);
403 $_REQUEST = $_GET = $_POST = [];
404 error_reporting(E_ALL
);
406 $this->renameLabels();
407 $this->_sethtmlGlobals();
408 $this->ensureMySQLMode(['IGNORE_SPACE', 'ERROR_FOR_DIVISION_BY_ZERO', 'STRICT_TRANS_TABLES']);
412 * Read everything from the datasets directory and insert into the db.
414 public function loadAllFixtures(): void
{
415 $fixturesDir = __DIR__
. '/../../fixtures';
417 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
419 $jsonFiles = glob($fixturesDir . '/*.json');
420 foreach ($jsonFiles as $jsonFixture) {
421 $json = json_decode(file_get_contents($jsonFixture));
422 foreach ($json as $tableName => $vars) {
423 if ($tableName === 'civicrm_contact') {
424 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');
427 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
429 foreach ($vars as $entity) {
430 $keys = $values = [];
431 foreach ($entity as $key => $value) {
433 $values[] = is_numeric($value) ?
$value : "'{$value}'";
435 CRM_Core_DAO
::executeQuery("
436 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
443 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
447 * Load the data that used to be handled by the discontinued dbunit class.
449 * This could do with further tidy up - the initial priority is simply to get rid of
450 * the dbunity package which is no longer supported.
452 * @param string $fileName
454 protected function loadXMLDataSet($fileName) {
455 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
456 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
457 foreach ($xml as $tableName => $element) {
458 if (!empty($element)) {
459 foreach ($element as $row) {
460 $keys = $values = [];
461 if (isset($row['@attributes'])) {
462 foreach ($row['@attributes'] as $key => $value) {
464 $values[] = is_numeric($value) ?
$value : "'{$value}'";
467 elseif (!empty($row)) {
468 // cos we copied it & it is inconsistent....
469 foreach ($row as $key => $value) {
471 $values[] = is_numeric($value) ?
$value : "'{$value}'";
475 if (!empty($values)) {
476 CRM_Core_DAO
::executeQuery("
477 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
483 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
487 * Create default domain contacts for the two domains added during test class.
488 * database population.
490 * @throws \API_Exception
492 public function createDomainContacts(): void
{
493 $this->organizationCreate(['api.Email.create' => ['email' => 'fixme.domainemail@example.org']]);
494 $this->organizationCreate([
495 'organization_name' => 'Second Domain',
496 'api.Email.create' => ['email' => 'domainemail2@example.org'],
497 'api.Address.create' => [
498 'street_address' => '15 Main St',
499 'location_type_id' => 1,
500 'city' => 'Collinsville',
501 'country_id' => 1228,
502 'state_province_id' => 1003,
503 'postal_code' => 6022,
506 OptionValue
::replace(FALSE)->addWhere(
507 'option_group_id:name', '=', 'from_email_address'
510 'name' => '"FIXME" <info@EXAMPLE.ORG>',
511 'label' => '"FIXME" <info@EXAMPLE.ORG>',
512 ])->setRecords([['domain_id' => 1], ['domain_id' => 2]])->execute();
516 * Common teardown functions for all unit tests.
518 * @throws \CRM_Core_Exception
519 * @throws \API_Exception
521 protected function tearDown(): void
{
522 $this->_apiversion
= 3;
523 $this->resetLabels();
525 error_reporting(E_ALL
& ~E_NOTICE
);
526 CRM_Utils_Hook
::singleton()->reset();
527 if ($this->hookClass
) {
528 $this->hookClass
->reset();
530 CRM_Core_Session
::singleton()->reset(1);
533 $this->tx
->rollback()->commit();
536 CRM_Core_Transaction
::forceRollbackIfEnabled();
537 \Civi\Core\Transaction\Manager
::singleton(TRUE);
540 CRM_Core_Transaction
::forceRollbackIfEnabled();
541 \Civi\Core\Transaction\Manager
::singleton(TRUE);
543 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match', 'civicrm_email', 'civicrm_address'];
544 $this->quickCleanup($tablesToTruncate);
545 $this->createDomainContacts();
548 $this->cleanTempDirs();
549 $this->unsetExtensionSystem();
550 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
551 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
552 // Ensure the destruct runs by unsetting it. Also, unsetting
553 // classes frees memory as they are not otherwise unset until the
559 * CHeck that all tests that have created payments have created them with the right financial entities.
561 * @throws \API_Exception
562 * @throws \CRM_Core_Exception
564 protected function assertPostConditions(): void
{
565 // Reset to version 3 as not all (e.g payments) work on v4
566 $this->_apiversion
= 3;
567 if ($this->isLocationTypesOnPostAssert
) {
568 $this->assertLocationValidity();
570 $this->assertCount(1, OptionGroup
::get(FALSE)
571 ->addWhere('name', '=', 'from_email_address')
573 if (!$this->isValidateFinancialsOnPostAssert
) {
576 $this->validateAllPayments();
577 $this->validateAllContributions();
581 * Create a batch of external API calls which can
582 * be executed concurrently.
585 * $calls = $this->createExternalAPI()
586 * ->addCall('Contact', 'get', ...)
587 * ->addCall('Contact', 'get', ...)
593 * @return \Civi\API\ExternalBatch
594 * @throws PHPUnit_Framework_SkippedTestError
596 public function createExternalAPI() {
597 global $civicrm_root;
599 'version' => $this->_apiversion
,
603 $calls = new \Civi\API\
ExternalBatch($defaultParams);
605 if (!$calls->isSupported()) {
606 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
613 * Create required data based on $this->entity & $this->params
614 * This is just a way to set up the test data for delete & get functions
615 * so the distinction between set
616 * up & tested functions is clearer
621 public function createTestEntity() {
622 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
626 * @param int $contactTypeId
630 public function contactTypeDelete($contactTypeId) {
631 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
633 throw new Exception('Could not delete contact type');
638 * @param array $params
642 public function membershipTypeCreate($params = []) {
643 CRM_Member_PseudoConstant
::flush('membershipType');
644 CRM_Core_Config
::clearDBCache();
645 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
646 $params = array_merge([
648 'duration_unit' => 'year',
649 'duration_interval' => 1,
650 'period_type' => 'rolling',
651 'member_of_contact_id' => $memberOfOrganization,
653 'financial_type_id' => 2,
656 'visibility' => 'Public',
659 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
661 CRM_Member_PseudoConstant
::flush('membershipType');
662 CRM_Utils_Cache
::singleton()->flush();
664 return (int) $result['id'];
670 * @param array $params
673 * @throws \CRM_Core_Exception
675 public function contactMembershipCreate($params) {
676 $params = array_merge([
677 'join_date' => '2007-01-21',
678 'start_date' => '2007-01-21',
679 'end_date' => '2007-12-21',
680 'source' => 'Payment',
681 'membership_type_id' => 'General',
683 if (!is_numeric($params['membership_type_id'])) {
684 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
685 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
686 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
690 $result = $this->callAPISuccess('Membership', 'create', $params);
691 return $result['id'];
695 * Delete Membership Type.
697 * @param array $params
699 public function membershipTypeDelete($params) {
700 $this->callAPISuccess('MembershipType', 'Delete', $params);
704 * @param int $membershipID
706 public function membershipDelete($membershipID) {
707 $deleteParams = ['id' => $membershipID];
708 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
712 * @param string $name
716 public function membershipStatusCreate($name = 'test member status') {
717 $params['name'] = $name;
718 $params['start_event'] = 'start_date';
719 $params['end_event'] = 'end_date';
720 $params['is_current_member'] = 1;
721 $params['is_active'] = 1;
723 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
724 CRM_Member_PseudoConstant
::flush('membershipStatus');
725 return (int) $result['id'];
729 * Delete the given membership status, deleting any memberships of the status first.
731 * @param int $membershipStatusID
733 * @throws \CRM_Core_Exception
735 public function membershipStatusDelete(int $membershipStatusID) {
736 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
737 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
740 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
741 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
742 $renewedMembershipEndDate = new DateTime($membershipEndDate);
743 switch ($durationUnit) {
745 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
749 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
750 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
751 $renewedMembershipEndDate->add(new DateInterval('P1D'));
752 $renewedMembershipEndDate->add(new DateInterval('P1M'));
753 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
756 return $renewedMembershipEndDate->format('Y-m-d');
760 * Create a relationship type.
762 * @param array $params
766 * @throws \CRM_Core_Exception
768 public function relationshipTypeCreate($params = []) {
769 $params = array_merge([
770 'name_a_b' => 'Relation 1 for relationship type create',
771 'name_b_a' => 'Relation 2 for relationship type create',
772 'contact_type_a' => 'Individual',
773 'contact_type_b' => 'Organization',
778 $result = $this->callAPISuccess('relationship_type', 'create', $params);
779 CRM_Core_PseudoConstant
::flush('relationshipType');
781 return $result['id'];
785 * Delete Relatinship Type.
787 * @param int $relationshipTypeID
789 public function relationshipTypeDelete($relationshipTypeID) {
790 $params['id'] = $relationshipTypeID;
791 $check = $this->callAPISuccess('relationship_type', 'get', $params);
792 if (!empty($check['count'])) {
793 $this->callAPISuccess('relationship_type', 'delete', $params);
798 * @param array $params
801 * @throws \CRM_Core_Exception
803 public function paymentProcessorTypeCreate($params = []) {
804 $params = array_merge([
805 'name' => 'API_Test_PP',
806 'title' => 'API Test Payment Processor',
807 'class_name' => 'CRM_Core_Payment_APITest',
808 'billing_mode' => 'form',
813 $result = $this->callAPISuccess('PaymentProcessorType', 'create', $params);
815 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
817 return $result['id'];
821 * Create test Authorize.net instance.
823 * @param array $params
827 public function paymentProcessorAuthorizeNetCreate($params = []) {
828 $params = array_merge([
829 'name' => 'Authorize',
830 'domain_id' => CRM_Core_Config
::domainID(),
831 'payment_processor_type_id' => 'AuthNet',
832 'title' => 'AuthNet',
837 'user_name' => '4y5BfuW7jm',
838 'password' => '4cAmW927n8uLf5J8',
839 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
840 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
841 'class_name' => 'Payment_AuthorizeNet',
845 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
846 return (int) $result['id'];
850 * Create Participant.
852 * @param array $params
853 * Array of contact id and event id values.
856 * $id of participant created
858 public function participantCreate($params = []) {
859 if (empty($params['contact_id'])) {
860 $params['contact_id'] = $this->individualCreate();
862 if (empty($params['event_id'])) {
863 $event = $this->eventCreate();
864 $params['event_id'] = $event['id'];
869 'register_date' => 20070219,
870 'source' => 'Wimbeldon',
871 'event_level' => 'Payment',
875 $params = array_merge($defaults, $params);
876 $result = $this->callAPISuccess('Participant', 'create', $params);
877 return $result['id'];
881 * Create Payment Processor.
884 * Id Payment Processor
886 public function processorCreate($params = []) {
890 'payment_processor_type_id' => 'Dummy',
891 'financial_account_id' => 12,
895 'url_site' => 'http://dummy.com',
896 'url_recur' => 'http://dummy.com',
899 'payment_instrument_id' => 'Debit Card',
901 $processorParams = array_merge($processorParams, $params);
902 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
903 return $processor['id'];
907 * Create Payment Processor.
909 * @param array $processorParams
911 * @return \CRM_Core_Payment_Dummy
912 * Instance of Dummy Payment Processor
914 * @throws \CiviCRM_API3_Exception
916 public function dummyProcessorCreate($processorParams = []) {
917 $paymentProcessorID = $this->processorCreate($processorParams);
918 // 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
919 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
920 $processorParams['is_test'] = FALSE;
921 $this->processorCreate($processorParams);
922 return System
::singleton()->getById($paymentProcessorID);
926 * Create contribution page.
928 * @param array $params
931 * Array of contribution page
933 public function contributionPageCreate($params = []) {
934 $this->_pageParams
= array_merge([
935 'title' => 'Test Contribution Page',
936 'financial_type_id' => 1,
938 'financial_account_id' => 1,
940 'is_allow_other_amount' => 1,
942 'max_amount' => 1000,
944 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
948 * Create a sample batch.
950 public function batchCreate() {
951 $params = $this->_params
;
952 $params['name'] = $params['title'] = 'Batch_433397';
953 $params['status_id'] = 1;
954 $result = $this->callAPISuccess('batch', 'create', $params);
955 return $result['id'];
961 * @param array $params
964 * result of created tag
966 public function tagCreate($params = []) {
968 'name' => 'New Tag3',
969 'description' => 'This is description for Our New Tag ',
972 $params = array_merge($defaults, $params);
973 $result = $this->callAPISuccess('Tag', 'create', $params);
974 return $result['values'][$result['id']];
981 * Id of the tag to be deleted.
985 public function tagDelete($tagId) {
986 require_once 'api/api.php';
990 $result = $this->callAPISuccess('Tag', 'delete', $params);
991 return $result['id'];
995 * Add entity(s) to the tag
997 * @param array $params
1001 public function entityTagAdd($params) {
1002 $result = $this->callAPISuccess('entity_tag', 'create', $params);
1009 * @param array $params
1013 * id of created pledge
1015 * @throws \CRM_Core_Exception
1017 public function pledgeCreate($params) {
1018 $params = array_merge([
1019 'pledge_create_date' => date('Ymd'),
1020 'start_date' => date('Ymd'),
1021 'scheduled_date' => date('Ymd'),
1023 'pledge_status_id' => '2',
1024 'financial_type_id' => '1',
1025 'pledge_original_installment_amount' => 20,
1026 'frequency_interval' => 5,
1027 'frequency_unit' => 'year',
1028 'frequency_day' => 15,
1029 'installments' => 5,
1033 $result = $this->callAPISuccess('Pledge', 'create', $params);
1034 return $result['id'];
1038 * Delete contribution.
1040 * @param int $pledgeId
1042 * @throws \CRM_Core_Exception
1044 public function pledgeDelete($pledgeId) {
1046 'pledge_id' => $pledgeId,
1048 $this->callAPISuccess('Pledge', 'delete', $params);
1052 * Create contribution.
1054 * @param array $params
1055 * Array of parameters.
1058 * id of created contribution
1060 public function contributionCreate(array $params): int {
1061 $params = array_merge([
1063 'receive_date' => date('Ymd'),
1064 'total_amount' => 100.00,
1065 'fee_amount' => 5.00,
1066 'financial_type_id' => 1,
1067 'payment_instrument_id' => 1,
1068 'non_deductible_amount' => 10.00,
1070 'contribution_status_id' => 1,
1073 $result = $this->callAPISuccess('contribution', 'create', $params);
1074 return $result['id'];
1078 * Delete contribution.
1080 * @param int $contributionId
1083 * @throws \CRM_Core_Exception
1085 public function contributionDelete($contributionId) {
1087 'contribution_id' => $contributionId,
1089 $result = $this->callAPISuccess('contribution', 'delete', $params);
1096 * @param array $params
1097 * Name-value pair for an event.
1100 * @throws \CRM_Core_Exception
1102 public function eventCreate($params = []) {
1103 // if no contact was passed, make up a dummy event creator
1104 if (!isset($params['contact_id'])) {
1105 $params['contact_id'] = $this->_contactCreate([
1106 'contact_type' => 'Individual',
1107 'first_name' => 'Event',
1108 'last_name' => 'Creator',
1112 // set defaults for missing params
1113 $params = array_merge([
1114 'title' => 'Annual CiviCRM meet',
1115 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1116 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1117 'event_type_id' => 1,
1119 'start_date' => 20081021,
1120 'end_date' => 20081023,
1121 'is_online_registration' => 1,
1122 'registration_start_date' => 20080601,
1123 'registration_end_date' => 20081015,
1124 'max_participants' => 100,
1125 'event_full_text' => 'Sorry! We are already full',
1128 'is_show_location' => 0,
1129 'is_email_confirm' => 1,
1132 return $this->callAPISuccess('Event', 'create', $params);
1136 * Create a paid event.
1138 * @param array $params
1140 * @param array $options
1142 * @param string $key
1143 * Index for storing event ID in ids array.
1147 * @throws \CRM_Core_Exception
1149 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1150 $params['is_monetary'] = TRUE;
1151 $event = $this->eventCreate($params);
1152 $this->ids
['Event'][$key] = (int) $event['id'];
1153 $this->ids
['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1154 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->ids
['PriceSet'][$key]);
1155 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->ids
['PriceSet'][$key], TRUE, FALSE);
1156 $priceSet = $priceSet[$this->ids
['PriceSet'][$key]] ??
NULL;
1157 $this->eventFeeBlock
= $priceSet['fields'] ??
NULL;
1169 public function eventDelete($id) {
1173 return $this->callAPISuccess('event', 'delete', $params);
1177 * Delete participant.
1179 * @param int $participantID
1183 public function participantDelete($participantID) {
1185 'id' => $participantID,
1187 $check = $this->callAPISuccess('Participant', 'get', $params);
1188 if ($check['count'] > 0) {
1189 return $this->callAPISuccess('Participant', 'delete', $params);
1194 * Create participant payment.
1196 * @param int $participantID
1197 * @param int $contributionID
1200 * $id of created payment
1202 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1203 //Create Participant Payment record With Values
1205 'participant_id' => $participantID,
1206 'contribution_id' => $contributionID,
1209 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1210 return $result['id'];
1214 * Delete participant payment.
1216 * @param int $paymentID
1218 public function participantPaymentDelete($paymentID) {
1222 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1228 * @param int $contactID
1231 * location id of created location
1233 public function locationAdd($contactID) {
1236 'location_type' => 'New Location Type',
1238 'name' => 'Saint Helier St',
1239 'county' => 'Marin',
1240 'country' => 'UNITED STATES',
1241 'state_province' => 'Michigan',
1242 'supplemental_address_1' => 'Hallmark Ct',
1243 'supplemental_address_2' => 'Jersey Village',
1244 'supplemental_address_3' => 'My Town',
1249 'contact_id' => $contactID,
1250 'address' => $address,
1251 'location_format' => '2.0',
1252 'location_type' => 'New Location Type',
1255 $result = $this->callAPISuccess('Location', 'create', $params);
1260 * Delete Locations of contact.
1262 * @param array $params
1265 public function locationDelete($params) {
1266 $this->callAPISuccess('Location', 'delete', $params);
1270 * Add a Location Type.
1272 * @param array $params
1274 * @return CRM_Core_DAO_LocationType
1275 * location id of created location
1277 public function locationTypeCreate($params = NULL) {
1278 if ($params === NULL) {
1280 'name' => 'New Location Type',
1281 'vcard_name' => 'New Location Type',
1282 'description' => 'Location Type for Delete',
1287 $locationType = new CRM_Core_DAO_LocationType();
1288 $locationType->copyValues($params);
1289 $locationType->save();
1290 // clear getfields cache
1291 CRM_Core_PseudoConstant
::flush();
1292 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1293 return $locationType->id
;
1297 * Delete a Location Type.
1299 * @param int $locationTypeId
1301 public function locationTypeDelete($locationTypeId) {
1302 $locationType = new CRM_Core_DAO_LocationType();
1303 $locationType->id
= $locationTypeId;
1304 $locationType->delete();
1310 * @param array $params
1312 * @return CRM_Core_DAO_Mapping
1313 * Mapping id of created mapping
1315 public function mappingCreate($params = NULL) {
1316 if ($params === NULL) {
1318 'name' => 'Mapping name',
1319 'description' => 'Mapping description',
1320 // 'Export Contact' mapping.
1321 'mapping_type_id' => 7,
1325 $mapping = new CRM_Core_DAO_Mapping();
1326 $mapping->copyValues($params);
1328 // clear getfields cache
1329 CRM_Core_PseudoConstant
::flush();
1330 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1337 * @param int $mappingId
1339 public function mappingDelete($mappingId) {
1340 $mapping = new CRM_Core_DAO_Mapping();
1341 $mapping->id
= $mappingId;
1346 * Prepare class for ACLs.
1348 protected function prepareForACLs() {
1349 $config = CRM_Core_Config
::singleton();
1350 $config->userPermissionClass
->permissions
= [];
1356 protected function cleanUpAfterACLs() {
1357 CRM_Utils_Hook
::singleton()->reset();
1358 $tablesToTruncate = [
1360 'civicrm_acl_cache',
1361 'civicrm_acl_entity_role',
1362 'civicrm_acl_contact_cache',
1364 $this->quickCleanup($tablesToTruncate);
1365 $config = CRM_Core_Config
::singleton();
1366 unset($config->userPermissionClass
->permissions
);
1370 * Create a smart group.
1372 * By default it will be a group of households.
1374 * @param array $smartGroupParams
1375 * @param array $groupParams
1376 * @param string $contactType
1380 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1381 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1382 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1384 $groupParams['saved_search_id'] = $savedSearch->id
;
1385 return $this->groupCreate($groupParams);
1391 * @param array $params
1393 public function uFFieldCreate($params = []) {
1394 $params = array_merge([
1396 'field_name' => 'first_name',
1399 'visibility' => 'Public Pages and Listings',
1400 'is_searchable' => '1',
1401 'label' => 'first_name',
1402 'field_type' => 'Individual',
1405 $this->callAPISuccess('uf_field', 'create', $params);
1409 * Add a UF Join Entry.
1411 * @param array $params
1414 * $id of created UF Join
1416 public function ufjoinCreate($params = NULL) {
1417 if ($params === NULL) {
1420 'module' => 'CiviEvent',
1421 'entity_table' => 'civicrm_event',
1427 $result = $this->callAPISuccess('uf_join', 'create', $params);
1432 * @param array $params
1433 * Optional parameters.
1434 * @param bool $reloadConfig
1435 * While enabling CiviCampaign component, we shouldn't always forcibly
1436 * reload config as this hinder hook call in test environment
1441 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1442 $this->enableCiviCampaign($reloadConfig);
1443 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1444 'name' => 'big_campaign',
1445 'title' => 'Campaign',
1447 return $campaign['id'];
1451 * Create Group for a contact.
1453 * @param int $contactId
1455 public function contactGroupCreate($contactId) {
1457 'contact_id.1' => $contactId,
1461 $this->callAPISuccess('GroupContact', 'Create', $params);
1465 * Delete Group for a contact.
1467 * @param int $contactId
1469 public function contactGroupDelete($contactId) {
1471 'contact_id.1' => $contactId,
1474 $this->civicrm_api('GroupContact', 'Delete', $params);
1480 * @param array $params
1484 * @throws \CRM_Core_Exception
1485 * @throws \CiviCRM_API3_Exception
1487 public function activityCreate($params = []) {
1488 $params = array_merge([
1489 'subject' => 'Discussion on warm beer',
1490 'activity_date_time' => date('Ymd'),
1492 'location' => 'Baker Street',
1493 'details' => 'Lets schedule a meeting',
1495 'activity_type_id' => 'Meeting',
1497 if (!isset($params['source_contact_id'])) {
1498 $params['source_contact_id'] = $this->individualCreate();
1500 if (!isset($params['target_contact_id'])) {
1501 $params['target_contact_id'] = $this->individualCreate([
1502 'first_name' => 'Julia',
1503 'last_name' => 'Anderson',
1505 'email' => 'julia_anderson@civicrm.org',
1506 'contact_type' => 'Individual',
1509 if (!isset($params['assignee_contact_id'])) {
1510 $params['assignee_contact_id'] = $params['target_contact_id'];
1513 $result = civicrm_api3('Activity', 'create', $params);
1515 $result['target_contact_id'] = $params['target_contact_id'];
1516 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1521 * Create an activity type.
1523 * @param array $params
1528 public function activityTypeCreate($params) {
1529 return $this->callAPISuccess('ActivityType', 'create', $params);
1533 * Delete activity type.
1535 * @param int $activityTypeId
1536 * Id of the activity type.
1540 public function activityTypeDelete($activityTypeId) {
1541 $params['activity_type_id'] = $activityTypeId;
1542 return $this->callAPISuccess('ActivityType', 'delete', $params);
1546 * Create custom group.
1548 * @param array $params
1552 public function customGroupCreate($params = []) {
1554 'title' => 'new custom group',
1555 'extends' => 'Contact',
1557 'style' => 'Inline',
1561 $params = array_merge($defaults, $params);
1563 return $this->callAPISuccess('custom_group', 'create', $params);
1567 * Existing function doesn't allow params to be over-ridden so need a new one
1568 * this one allows you to only pass in the params you want to change
1570 * @param array $params
1574 public function CustomGroupCreateByParams($params = []) {
1576 'title' => "API Custom Group",
1577 'extends' => 'Contact',
1579 'style' => 'Inline',
1582 $params = array_merge($defaults, $params);
1583 return $this->callAPISuccess('custom_group', 'create', $params);
1587 * Create custom group with multi fields.
1589 * @param array $params
1593 public function CustomGroupMultipleCreateByParams($params = []) {
1598 $params = array_merge($defaults, $params);
1599 return $this->CustomGroupCreateByParams($params);
1603 * Create custom group with multi fields.
1605 * @param array $params
1609 public function CustomGroupMultipleCreateWithFields($params = []) {
1610 // also need to pass on $params['custom_field'] if not set but not in place yet
1612 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1613 $ids['custom_group_id'] = $customGroup['id'];
1615 $customField = $this->customFieldCreate([
1616 'custom_group_id' => $ids['custom_group_id'],
1617 'label' => 'field_1' . $ids['custom_group_id'],
1621 $ids['custom_field_id'][] = $customField['id'];
1623 $customField = $this->customFieldCreate([
1624 'custom_group_id' => $ids['custom_group_id'],
1625 'default_value' => '',
1626 'label' => 'field_2' . $ids['custom_group_id'],
1629 $ids['custom_field_id'][] = $customField['id'];
1631 $customField = $this->customFieldCreate([
1632 'custom_group_id' => $ids['custom_group_id'],
1633 'default_value' => '',
1634 'label' => 'field_3' . $ids['custom_group_id'],
1637 $ids['custom_field_id'][] = $customField['id'];
1643 * Create a custom group with a single text custom field. See
1644 * participant:testCreateWithCustom for how to use this
1646 * @param string $function
1648 * @param string $filename
1652 * ids of created objects
1654 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1655 $params = ['title' => $function];
1656 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1657 $params['extends'] = $entity ?
$entity : 'Contact';
1658 $customGroup = $this->customGroupCreate($params);
1659 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1660 CRM_Core_PseudoConstant
::flush();
1662 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1666 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1667 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1669 * @param string $function
1671 * @param string $filename
1675 * ids of created objects
1677 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1678 $params = ['title' => $function];
1679 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1680 $params['extends'] = $entity ?
$entity : 'Contact';
1681 $customGroup = $this->customGroupCreate($params);
1682 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1683 CRM_Core_PseudoConstant
::flush();
1685 'defaultValue' => 'Default Value',
1686 'lowercasevalue' => 'Lowercase Value',
1687 1 => 'Integer Value',
1690 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1691 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1692 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1693 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1694 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1695 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1696 foreach ($options as $option_value => $option_label) {
1697 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1698 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1702 'custom_group_id' => $customGroup['id'],
1703 'custom_field_id' => $customField['id'],
1704 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1705 'custom_field_group_options' => $options,
1710 * Delete custom group.
1712 * @param int $customGroupID
1716 public function customGroupDelete($customGroupID) {
1717 $params['id'] = $customGroupID;
1718 return $this->callAPISuccess('custom_group', 'delete', $params);
1722 * Create custom field.
1724 * @param array $params
1725 * (custom_group_id) is required.
1729 public function customFieldCreate($params) {
1730 $params = array_merge([
1731 'label' => 'Custom Field',
1732 'data_type' => 'String',
1733 'html_type' => 'Text',
1734 'is_searchable' => 1,
1736 'default_value' => 'defaultValue',
1739 $result = $this->callAPISuccess('custom_field', 'create', $params);
1740 // these 2 functions are called with force to flush static caches
1741 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1742 CRM_Core_Component
::getEnabledComponents(1);
1747 * Delete custom field.
1749 * @param int $customFieldID
1753 public function customFieldDelete($customFieldID) {
1755 $params['id'] = $customFieldID;
1756 return $this->callAPISuccess('custom_field', 'delete', $params);
1766 public function noteCreate($cId) {
1768 'entity_table' => 'civicrm_contact',
1769 'entity_id' => $cId,
1770 'note' => 'hello I am testing Note',
1771 'contact_id' => $cId,
1772 'modified_date' => date('Ymd'),
1773 'subject' => 'Test Note',
1776 return $this->callAPISuccess('Note', 'create', $params);
1780 * Enable CiviCampaign Component.
1782 * @param bool $reloadConfig
1783 * Force relaod config or not
1785 public function enableCiviCampaign($reloadConfig = TRUE) {
1786 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1787 if ($reloadConfig) {
1788 // force reload of config object
1789 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1791 //flush cache by calling with reset
1792 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1796 * Create custom field with Option Values.
1798 * @param array $customGroup
1799 * @param string $name
1800 * Name of custom field.
1801 * @param array $extraParams
1802 * Additional parameters to pass through.
1806 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1808 'custom_group_id' => $customGroup['id'],
1809 'name' => 'test_custom_group',
1810 'label' => 'Country',
1811 'html_type' => 'Select',
1812 'data_type' => 'String',
1815 'is_searchable' => 0,
1821 'name' => 'option_group1',
1822 'label' => 'option_group_label1',
1826 'option_label' => ['Label1', 'Label2'],
1827 'option_value' => ['value1', 'value2'],
1828 'option_name' => [$name . '_1', $name . '_2'],
1829 'option_weight' => [1, 2],
1830 'option_status' => [1, 1],
1833 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1835 return $this->callAPISuccess('custom_field', 'create', $params);
1843 public function confirmEntitiesDeleted($entities) {
1844 foreach ($entities as $entity) {
1846 $result = $this->callAPISuccess($entity, 'Get', []);
1847 if ($result['error'] == 1 ||
$result['count'] > 0) {
1848 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1856 * Quick clean by emptying tables created for the test.
1858 * @param array $tablesToTruncate
1859 * @param bool $dropCustomValueTables
1861 * @throws \CRM_Core_Exception
1862 * @throws \API_Exception
1864 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1866 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1868 if ($dropCustomValueTables) {
1869 $this->cleanupCustomGroups();
1870 // Reset autoincrement too.
1871 $tablesToTruncate[] = 'civicrm_custom_group';
1872 $tablesToTruncate[] = 'civicrm_custom_field';
1875 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1877 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1878 foreach ($tablesToTruncate as $table) {
1879 $sql = "TRUNCATE TABLE $table";
1880 CRM_Core_DAO
::executeQuery($sql);
1882 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1886 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1888 * @throws \CRM_Core_Exception
1890 public function quickCleanUpFinancialEntities() {
1891 $tablesToTruncate = [
1893 'civicrm_activity_contact',
1894 'civicrm_contribution',
1895 'civicrm_contribution_soft',
1896 'civicrm_contribution_product',
1897 'civicrm_financial_trxn',
1898 'civicrm_financial_item',
1899 'civicrm_contribution_recur',
1900 'civicrm_line_item',
1901 'civicrm_contribution_page',
1902 'civicrm_payment_processor',
1903 'civicrm_entity_financial_trxn',
1904 'civicrm_membership',
1905 'civicrm_membership_type',
1906 'civicrm_membership_payment',
1907 'civicrm_membership_log',
1908 'civicrm_membership_block',
1910 'civicrm_participant',
1911 'civicrm_participant_payment',
1913 'civicrm_pcp_block',
1915 'civicrm_pledge_block',
1916 'civicrm_pledge_payment',
1917 'civicrm_price_set_entity',
1918 'civicrm_price_field_value',
1919 'civicrm_price_field',
1921 $this->quickCleanup($tablesToTruncate);
1922 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1923 $this->restoreDefaultPriceSetConfig();
1924 $this->disableTaxAndInvoicing();
1925 $this->setCurrencySeparators(',');
1926 FinancialType
::delete(FALSE)->addWhere(
1927 'name', 'NOT IN', ['Donation' , 'Member Dues', 'Campaign Contribution', 'Event Fee']
1929 CRM_Core_PseudoConstant
::flush('taxRates');
1930 System
::singleton()->flushProcessors();
1931 // @fixme this parameter is leaking - it should not be defined as a class static
1932 // but for now we just handle in tear down.
1933 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1937 * Reset the price set config so results exist.
1939 public function restoreDefaultPriceSetConfig() {
1940 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1941 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1942 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)");
1943 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)");
1947 * Recreate default membership types.
1949 public function restoreMembershipTypes() {
1950 CRM_Core_DAO
::executeQuery(
1951 "REPLACE INTO civicrm_membership_type
1952 (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)
1954 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1955 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1956 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1961 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1962 * Default behaviour is to also delete the entity
1963 * @param array $params
1964 * Params array to check against.
1966 * Id of the entity concerned.
1967 * @param string $entity
1968 * Name of entity concerned (e.g. membership).
1969 * @param bool $delete
1970 * Should the entity be deleted as part of this check.
1971 * @param string $errorText
1972 * Text to print on error.
1976 * @param array $params
1979 * @param int $delete
1980 * @param string $errorText
1982 * @throws CRM_Core_Exception
1984 public function getAndCheck(array $params, int $id, $entity, int $delete = 1, string $errorText = ''): void
{
1986 $result = $this->callAPISuccessGetSingle($entity, [
1988 'return' => array_keys($params),
1992 $this->callAPISuccess($entity, 'Delete', [
1996 $dateFields = $keys = $dateTimeFields = [];
1997 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1998 foreach ($fields['values'] as $field => $settings) {
1999 if (array_key_exists($field, $result)) {
2000 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
2003 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
2005 $type = $settings['type'] ??
NULL;
2006 if ($type === CRM_Utils_Type
::T_DATE
) {
2007 $dateFields[] = $settings['name'];
2008 // we should identify both real names & unique names as dates
2009 if ($field !== $settings['name']) {
2010 $dateFields[] = $field;
2013 if ($type === CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
2014 $dateTimeFields[] = $settings['name'];
2015 // we should identify both real names & unique names as dates
2016 if ($field !== $settings['name']) {
2017 $dateTimeFields[] = $field;
2022 if (strtolower($entity) === 'contribution') {
2023 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
2024 // this is not returned in id format
2025 unset($params['payment_instrument_id']);
2026 $params['contribution_source'] = $params['source'];
2027 unset($params['source']);
2030 foreach ($params as $key => $value) {
2031 if ($key === 'version' ||
strpos($key, 'api') === 0 ||
(!array_key_exists($key, $keys) ||
!array_key_exists($keys[$key], $result))) {
2034 if (in_array($key, $dateFields, TRUE)) {
2035 $value = date('Y-m-d', strtotime($value));
2036 $result[$key] = date('Y-m-d', strtotime($result[$key]));
2038 if (in_array($key, $dateTimeFields, TRUE)) {
2039 $value = date('Y-m-d H:i:s', strtotime($value));
2040 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
2042 $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);
2047 * Get formatted values in the actual and expected result.
2049 * @param array $actual
2050 * Actual calculated values.
2051 * @param array $expected
2054 public function checkArrayEquals(&$actual, &$expected) {
2055 self
::unsetId($actual);
2056 self
::unsetId($expected);
2057 $this->assertEquals($expected, $actual);
2061 * Unset the key 'id' from the array
2063 * @param array $unformattedArray
2064 * The array from which the 'id' has to be unset.
2066 public static function unsetId(&$unformattedArray) {
2067 $formattedArray = [];
2068 if (array_key_exists('id', $unformattedArray)) {
2069 unset($unformattedArray['id']);
2071 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
2072 foreach ($unformattedArray['values'] as $key => $value) {
2073 if (is_array($value)) {
2074 foreach ($value as $k => $v) {
2080 elseif ($key == 'id') {
2081 $unformattedArray[$key];
2083 $formattedArray = [$value];
2085 $unformattedArray['values'] = $formattedArray;
2090 * Helper to enable/disable custom directory support
2092 * @param array $customDirs
2094 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2095 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2097 public function customDirectories($customDirs) {
2098 $config = CRM_Core_Config
::singleton();
2100 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2101 unset($config->customPHPPathDir
);
2103 elseif ($customDirs['php_path'] === TRUE) {
2104 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2107 $config->customPHPPathDir
= $php_path;
2110 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2111 unset($config->customTemplateDir
);
2113 elseif ($customDirs['template_path'] === TRUE) {
2114 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2117 $config->customTemplateDir
= $template_path;
2122 * Generate a temporary folder.
2124 * @param string $prefix
2128 public function createTempDir($prefix = 'test-') {
2129 $tempDir = CRM_Utils_File
::tempdir($prefix);
2130 $this->tempDirs
[] = $tempDir;
2134 public function cleanTempDirs() {
2135 if (!is_array($this->tempDirs
)) {
2136 // fix test errors where this is not set
2139 foreach ($this->tempDirs
as $tempDir) {
2140 if (is_dir($tempDir)) {
2141 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2147 * Temporarily replace the singleton extension with a different one.
2149 * @param \CRM_Extension_System $system
2151 public function setExtensionSystem(CRM_Extension_System
$system) {
2152 if ($this->origExtensionSystem
== NULL) {
2153 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2155 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2158 public function unsetExtensionSystem() {
2159 if ($this->origExtensionSystem
!== NULL) {
2160 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2161 $this->origExtensionSystem
= NULL;
2166 * Temporarily alter the settings-metadata to add a mock setting.
2168 * WARNING: The setting metadata will disappear on the next cache-clear.
2174 public function setMockSettingsMetaData($extras) {
2175 CRM_Utils_Hook
::singleton()
2176 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2177 $metadata = array_merge($metadata, $extras);
2180 Civi
::service('settings_manager')->flush();
2182 $fields = $this->callAPISuccess('setting', 'getfields', []);
2183 foreach ($extras as $key => $spec) {
2184 $this->assertNotEmpty($spec['title']);
2185 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2190 * @param string $name
2192 public function financialAccountDelete($name) {
2193 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2194 $financialAccount->name
= $name;
2195 if ($financialAccount->find(TRUE)) {
2196 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2197 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2198 $entityFinancialType->delete();
2199 $financialAccount->delete();
2204 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2205 * (NB unclear if this is still required)
2207 public function _sethtmlGlobals() {
2208 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2210 'html_quickform_rule_required',
2211 'HTML/QuickForm/Rule/Required.php',
2214 'html_quickform_rule_range',
2215 'HTML/QuickForm/Rule/Range.php',
2218 'html_quickform_rule_range',
2219 'HTML/QuickForm/Rule/Range.php',
2222 'html_quickform_rule_range',
2223 'HTML/QuickForm/Rule/Range.php',
2226 'html_quickform_rule_email',
2227 'HTML/QuickForm/Rule/Email.php',
2230 'html_quickform_rule_regex',
2231 'HTML/QuickForm/Rule/Regex.php',
2234 'html_quickform_rule_regex',
2235 'HTML/QuickForm/Rule/Regex.php',
2238 'html_quickform_rule_regex',
2239 'HTML/QuickForm/Rule/Regex.php',
2242 'html_quickform_rule_regex',
2243 'HTML/QuickForm/Rule/Regex.php',
2245 'nopunctuation' => [
2246 'html_quickform_rule_regex',
2247 'HTML/QuickForm/Rule/Regex.php',
2250 'html_quickform_rule_regex',
2251 'HTML/QuickForm/Rule/Regex.php',
2254 'html_quickform_rule_callback',
2255 'HTML/QuickForm/Rule/Callback.php',
2258 'html_quickform_rule_compare',
2259 'HTML/QuickForm/Rule/Compare.php',
2262 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2263 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2265 'HTML/QuickForm/group.php',
2266 'HTML_QuickForm_group',
2269 'HTML/QuickForm/hidden.php',
2270 'HTML_QuickForm_hidden',
2273 'HTML/QuickForm/reset.php',
2274 'HTML_QuickForm_reset',
2277 'HTML/QuickForm/checkbox.php',
2278 'HTML_QuickForm_checkbox',
2281 'HTML/QuickForm/file.php',
2282 'HTML_QuickForm_file',
2285 'HTML/QuickForm/image.php',
2286 'HTML_QuickForm_image',
2289 'HTML/QuickForm/password.php',
2290 'HTML_QuickForm_password',
2293 'HTML/QuickForm/radio.php',
2294 'HTML_QuickForm_radio',
2297 'HTML/QuickForm/button.php',
2298 'HTML_QuickForm_button',
2301 'HTML/QuickForm/submit.php',
2302 'HTML_QuickForm_submit',
2305 'HTML/QuickForm/select.php',
2306 'HTML_QuickForm_select',
2309 'HTML/QuickForm/hiddenselect.php',
2310 'HTML_QuickForm_hiddenselect',
2313 'HTML/QuickForm/text.php',
2314 'HTML_QuickForm_text',
2317 'HTML/QuickForm/textarea.php',
2318 'HTML_QuickForm_textarea',
2321 'HTML/QuickForm/fckeditor.php',
2322 'HTML_QuickForm_FCKEditor',
2325 'HTML/QuickForm/tinymce.php',
2326 'HTML_QuickForm_TinyMCE',
2329 'HTML/QuickForm/dojoeditor.php',
2330 'HTML_QuickForm_dojoeditor',
2333 'HTML/QuickForm/link.php',
2334 'HTML_QuickForm_link',
2337 'HTML/QuickForm/advcheckbox.php',
2338 'HTML_QuickForm_advcheckbox',
2341 'HTML/QuickForm/date.php',
2342 'HTML_QuickForm_date',
2345 'HTML/QuickForm/static.php',
2346 'HTML_QuickForm_static',
2349 'HTML/QuickForm/header.php',
2350 'HTML_QuickForm_header',
2353 'HTML/QuickForm/html.php',
2354 'HTML_QuickForm_html',
2357 'HTML/QuickForm/hierselect.php',
2358 'HTML_QuickForm_hierselect',
2361 'HTML/QuickForm/autocomplete.php',
2362 'HTML_QuickForm_autocomplete',
2365 'HTML/QuickForm/xbutton.php',
2366 'HTML_QuickForm_xbutton',
2368 'advmultiselect' => [
2369 'HTML/QuickForm/advmultiselect.php',
2370 'HTML_QuickForm_advmultiselect',
2376 * Set up an acl allowing contact to see 2 specified groups
2377 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2379 * You need to have pre-created these groups & created the user e.g
2380 * $this->createLoggedInUser();
2381 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2382 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2384 * @param bool $isProfile
2386 public function setupACL($isProfile = FALSE) {
2388 $_REQUEST = $this->_params
;
2390 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2391 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2392 $ov = new CRM_Core_DAO_OptionValue();
2393 $ov->option_group_id
= $optionGroupID;
2395 if ($ov->find(TRUE)) {
2396 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2398 $optionValue = $this->callAPISuccess('option_value', 'create', [
2399 'option_group_id' => $optionGroupID,
2400 'label' => 'pick me',
2404 CRM_Core_DAO
::executeQuery("
2405 TRUNCATE civicrm_acl_cache
2408 CRM_Core_DAO
::executeQuery("
2409 TRUNCATE civicrm_acl_contact_cache
2412 CRM_Core_DAO
::executeQuery("
2413 INSERT INTO civicrm_acl_entity_role (
2414 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2415 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2419 CRM_Core_DAO
::executeQuery("
2420 INSERT INTO civicrm_acl (
2421 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2424 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2429 CRM_Core_DAO
::executeQuery("
2430 INSERT INTO civicrm_acl (
2431 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2434 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2438 CRM_Core_DAO
::executeQuery("
2439 INSERT INTO civicrm_acl (
2440 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2443 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2448 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2449 $this->callAPISuccess('group_contact', 'create', [
2450 'group_id' => $this->_permissionedGroup
,
2451 'contact_id' => $this->_loggedInUser
,
2455 CRM_ACL_BAO_Cache
::resetCache();
2460 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2462 public function offsetDefaultPriceSet() {
2463 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2464 $firstID = $contributionPriceSet['id'];
2465 $this->callAPISuccess('price_set', 'create', [
2466 'id' => $contributionPriceSet['id'],
2470 unset($contributionPriceSet['id']);
2471 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2472 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2473 'price_set_id' => $firstID,
2474 'options' => ['limit' => 1],
2476 unset($priceField['id']);
2477 $priceField['price_set_id'] = $newPriceSet['id'];
2478 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2479 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2480 'price_set_id' => $firstID,
2482 'options' => ['limit' => 1],
2485 unset($priceFieldValue['id']);
2486 //create some padding to use up ids
2487 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2488 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2489 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2493 * Create an instance of the paypal processor.
2495 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2496 * this parent class & we don't have a structure for that yet
2497 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2498 * & the best protection against that is the functions this class affords
2500 * @param array $params
2502 * @return int $result['id'] payment processor id
2504 public function paymentProcessorCreate($params = []) {
2505 $params = array_merge([
2507 'domain_id' => CRM_Core_Config
::domainID(),
2508 'payment_processor_type_id' => 'PayPal',
2512 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2513 'password' => '1183377788',
2514 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2515 'url_site' => 'https://www.sandbox.paypal.com/',
2516 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2517 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2518 'class_name' => 'Payment_PayPalImpl',
2519 'billing_mode' => 3,
2520 'financial_type_id' => 1,
2521 'financial_account_id' => 12,
2522 // Credit card = 1 so can pass 'by accident'.
2523 'payment_instrument_id' => 'Debit Card',
2525 if (!is_numeric($params['payment_processor_type_id'])) {
2526 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2528 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2529 'name' => $params['payment_processor_type_id'],
2533 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2534 return $result['id'];
2538 * Set up initial recurring payment allowing subsequent IPN payments.
2540 * @param array $recurParams (Optional)
2541 * @param array $contributionParams (Optional)
2543 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []): void
{
2544 $this->ids
['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
2545 $contributionParams = array_merge([
2546 'total_amount' => '200',
2547 'invoice_id' => $this->_invoiceID
,
2548 'financial_type_id' => 'Donation',
2549 'contact_id' => $this->_contactID
,
2550 'contribution_page_id' => $this->_contributionPageID
,
2551 'payment_processor_id' => $this->_paymentProcessorID
,
2552 'receive_date' => '2019-07-25 07:34:23',
2553 'skipCleanMoney' => TRUE,
2554 'amount_level' => 'expensive',
2555 'campaign_id' => $this->ids
['campaign'][0],
2556 'source' => 'Online Contribution: Page name',
2557 ], $contributionParams);
2558 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2559 'contact_id' => $this->_contactID
,
2562 'installments' => 5,
2563 'frequency_unit' => 'Month',
2564 'frequency_interval' => 1,
2565 'invoice_id' => $this->_invoiceID
,
2566 'contribution_status_id' => 2,
2567 'payment_processor_id' => $this->_paymentProcessorID
,
2568 // processor provided ID - use contact ID as proxy.
2569 'processor_id' => $this->_contactID
,
2570 'api.Order.create' => $contributionParams,
2571 ], $recurParams))['values'][0];
2572 $this->_contributionRecurID
= $contributionRecur['id'];
2573 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2574 $this->ids
['Contribution'][0] = $this->_contributionID
;
2578 * 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
2580 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2582 * @throws \API_Exception
2584 public function setupMembershipRecurringPaymentProcessorTransaction($params = []): void
{
2585 $membershipParams = $recurParams = [];
2586 if (!empty($params['duration_unit'])) {
2587 $membershipParams['duration_unit'] = $params['duration_unit'];
2589 if (!empty($params['frequency_unit'])) {
2590 $recurParams['frequency_unit'] = $params['frequency_unit'];
2593 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2594 //create a contribution so our membership & contribution don't both have id = 1
2595 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2596 $this->contributionCreate([
2597 'contact_id' => $this->_contactID
,
2599 'financial_type_id' => 1,
2600 'invoice_id' => 'abcd',
2602 'receive_date' => '2019-07-25 07:34:23',
2606 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2611 'label' => 'General',
2613 'unit_price' => 200,
2614 'line_total' => 200,
2615 'financial_type_id' => 1,
2616 'membership_type_id' => $this->ids
['membership_type'],
2620 'contact_id' => $this->_contactID
,
2621 'membership_type_id' => $this->ids
['membership_type'],
2622 'source' => 'Payment',
2627 $this->ids
['membership'] = LineItem
::get()
2628 ->addWhere('contribution_id', '=', $this->ids
['Contribution'][0])
2629 ->addWhere('entity_table', '=', 'civicrm_membership')
2630 ->addSelect('entity_id')
2631 ->execute()->first()['entity_id'];
2639 public function CiviUnitTestCase_fatalErrorHandler($message) {
2640 throw new Exception("{$message['message']}: {$message['code']}");
2644 * Wrap the entire test case in a transaction.
2646 * Only subsequent DB statements will be wrapped in TX -- this cannot
2647 * retroactively wrap old DB statements. Therefore, it makes sense to
2648 * call this at the beginning of setUp().
2650 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2651 * this option does not work with, e.g., custom-data.
2653 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2654 * if TRUNCATE or ALTER is called while using a transaction.
2657 * Whether to use nesting or reference-counting.
2659 public function useTransaction($nest = TRUE) {
2661 $this->tx
= new CRM_Core_Transaction($nest);
2662 $this->tx
->rollback();
2667 * Assert the attachment exists.
2669 * @param bool $exists
2670 * @param array $apiResult
2672 protected function assertAttachmentExistence($exists, $apiResult) {
2673 $fileId = $apiResult['id'];
2674 $this->assertTrue(is_numeric($fileId));
2675 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2676 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2677 1 => [$fileId, 'Int'],
2679 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2680 1 => [$fileId, 'Int'],
2685 * Assert 2 sql strings are the same, ignoring double spaces.
2687 * @param string $expectedSQL
2688 * @param string $actualSQL
2689 * @param string $message
2691 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2692 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2693 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2694 $this->assertEquals($expected, $actual, $message);
2698 * Create a price set for an event.
2700 * @param int $feeTotal
2701 * @param int $minAmt
2702 * @param string $type
2704 * @param array $options
2708 * @throws \CRM_Core_Exception
2710 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2711 // creating price set, price field
2712 $paramsSet['title'] = 'Price Set';
2713 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2714 $paramsSet['is_active'] = FALSE;
2715 $paramsSet['extends'] = 1;
2716 $paramsSet['min_amount'] = $minAmt;
2718 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2719 $this->_ids
['price_set'] = $priceSet->id
;
2722 'label' => 'Price Field',
2723 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2724 'html_type' => $type,
2725 'price' => $feeTotal,
2726 'option_label' => ['1' => 'Price Field'],
2727 'option_value' => ['1' => $feeTotal],
2728 'option_name' => ['1' => $feeTotal],
2729 'option_weight' => ['1' => 1],
2730 'option_amount' => ['1' => 1],
2731 'is_display_amounts' => 1,
2733 'options_per_line' => 1,
2734 'is_active' => ['1' => 1],
2735 'price_set_id' => $this->_ids
['price_set'],
2736 'is_enter_qty' => 1,
2737 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2739 if ($type === 'Radio') {
2740 foreach ($options as $index => $option) {
2741 $paramsField['is_enter_qty'] = 0;
2742 $optionID = $index +
2;
2743 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2744 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2748 $this->callAPISuccess('PriceField', 'create', $paramsField);
2749 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2750 $this->_ids
['price_field'] = array_keys($fields['values']);
2751 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2752 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2754 return $this->_ids
['price_set'];
2758 * Add a profile to a contribution page.
2760 * @param string $name
2761 * @param int $contributionPageID
2762 * @param string $module
2764 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2766 'uf_group_id' => $name,
2767 'module' => $module,
2768 'entity_table' => 'civicrm_contribution_page',
2769 'entity_id' => $contributionPageID,
2772 if ($module !== 'CiviContribute') {
2773 $params['module_data'] = [$module => []];
2775 $this->callAPISuccess('UFJoin', 'create', $params);
2779 * Add participant with contribution
2783 * @throws \CRM_Core_Exception
2785 protected function createPartiallyPaidParticipantOrder() {
2786 $orderParams = $this->getParticipantOrderParams();
2787 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2788 return $this->callAPISuccess('Order', 'create', $orderParams);
2794 * @param string $component
2795 * @param int $componentId
2796 * @param array $priceFieldOptions
2800 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2801 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2802 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2803 $paramsSet['is_active'] = TRUE;
2804 $paramsSet['financial_type_id'] = 'Event Fee';
2805 $paramsSet['extends'] = 1;
2806 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2808 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSet['id']);
2810 $paramsField = array_merge([
2811 'label' => 'Price Field',
2812 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2813 'html_type' => 'CheckBox',
2814 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2815 'option_value' => ['1' => 100, '2' => 200],
2816 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2817 'option_weight' => ['1' => 1, '2' => 2],
2818 'option_amount' => ['1' => 100, '2' => 200],
2819 'is_display_amounts' => 1,
2821 'options_per_line' => 1,
2822 'is_active' => ['1' => 1, '2' => 1],
2823 'price_set_id' => $priceSet['id'],
2824 'is_enter_qty' => 1,
2825 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2826 ], $priceFieldOptions);
2828 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2829 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2833 * Replace the template with a test-oriented template designed to show all the variables.
2835 * @param string $templateName
2836 * @param string $type
2838 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2839 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2840 CRM_Core_DAO
::executeQuery(
2841 "UPDATE civicrm_msg_template
2842 SET msg_{$type} = %1
2843 WHERE workflow_name = '{$templateName}'
2844 AND is_default = 1", [1 => [$testTemplate, 'String']]
2849 * Reinstate the default template.
2851 * @param string $templateName
2852 * @param string $type
2854 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2855 CRM_Core_DAO
::executeQuery(
2856 "UPDATE civicrm_option_group og
2857 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2858 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2859 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2860 SET m.msg_{$type} = m2.msg_{$type}
2861 WHERE og.name = 'msg_tpl_workflow_contribution'
2862 AND ov.name = '{$templateName}'
2863 AND m.is_default = 1"
2868 * Flush statics relating to financial type.
2870 protected function flushFinancialTypeStatics() {
2871 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2872 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2874 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2875 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2877 CRM_Contribute_PseudoConstant
::flush('financialType');
2878 CRM_Contribute_PseudoConstant
::flush('membershipType');
2879 // Pseudoconstants may be saved to the cache table.
2880 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2881 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2882 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2886 * Set the permissions to the supplied array.
2888 * @param array $permissions
2890 protected function setPermissions($permissions) {
2891 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2892 $this->flushFinancialTypeStatics();
2896 * @param array $params
2899 public function _checkFinancialRecords($params, $context) {
2901 'entity_id' => $params['id'],
2902 'entity_table' => 'civicrm_contribution',
2904 $contribution = $this->callAPISuccess('Contribution', 'getsingle', [
2905 'id' => $params['id'],
2906 'return' => ['total_amount', 'fee_amount', 'net_amount'],
2908 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2909 if ($context == 'pending') {
2910 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2911 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2914 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2916 'id' => $trxn['financial_trxn_id'],
2918 if ($context != 'online' && $context != 'payLater') {
2920 'to_financial_account_id' => 6,
2921 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2925 if ($context == 'feeAmount') {
2926 $compareParams['fee_amount'] = 50;
2928 elseif ($context === 'online') {
2930 'to_financial_account_id' => 12,
2931 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2933 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2936 elseif ($context == 'payLater') {
2938 'to_financial_account_id' => 7,
2939 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2943 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2945 'financial_trxn_id' => $trxn['financial_trxn_id'],
2946 'entity_table' => 'civicrm_financial_item',
2948 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2950 'id' => $entityTrxn['entity_id'],
2953 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2955 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2957 if ($context == 'payLater') {
2959 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2961 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2964 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2965 if ($context == 'feeAmount') {
2967 'entity_id' => $params['id'],
2968 'entity_table' => 'civicrm_contribution',
2970 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2972 'id' => $maxTrxn['financial_trxn_id'],
2975 'to_financial_account_id' => 5,
2976 'from_financial_account_id' => 6,
2977 'total_amount' => 50,
2980 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2981 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2983 'entity_id' => $trxnId['financialTrxnId'],
2984 'entity_table' => 'civicrm_financial_trxn',
2989 'financial_account_id' => 5,
2991 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2993 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2994 // line should be copied into all the functions that call this function & evaluated there
2995 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2996 // when calling completeTransaction or repeatTransaction.
2997 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
3001 * Return financial type id on basis of name
3003 * @param string $name Financial type m/c name
3007 public function getFinancialTypeId($name) {
3008 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
3012 * Cleanup function for contents of $this->ids.
3014 * This is a best effort cleanup to use in tear downs etc.
3016 * It will not fail if the data has already been removed (some tests may do
3017 * their own cleanup).
3019 protected function cleanUpSetUpIDs() {
3020 foreach ($this->setupIDs
as $entity => $id) {
3022 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
3024 catch (CiviCRM_API3_Exception
$e) {
3025 // This is a best-effort cleanup function, ignore.
3031 * Create Financial Type.
3033 * @param array $params
3037 protected function createFinancialType($params = []) {
3038 $params = array_merge($params,
3040 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
3044 return $this->callAPISuccess('FinancialType', 'create', $params);
3048 * Create Payment Instrument.
3050 * @param array $params
3051 * @param string $financialAccountName
3055 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3056 $params = array_merge([
3057 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3058 'option_group_id' => 'payment_instrument',
3061 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3063 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3065 $financialAccountParams = [
3066 'entity_table' => 'civicrm_option_value',
3067 'entity_id' => $newPaymentInstrument,
3068 'account_relationship' => $relationTypeID,
3069 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3071 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3073 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3077 * Enable Tax and Invoicing
3079 * @param array $params
3081 protected function enableTaxAndInvoicing(array $params = []): void
{
3082 // Enable component contribute setting
3083 $contributeSetting = array_merge($params,
3086 'invoice_prefix' => 'INV_',
3087 'invoice_due_date' => 10,
3088 'invoice_due_date_period' => 'days',
3089 'invoice_notes' => '',
3090 'invoice_is_email_pdf' => 1,
3091 'tax_term' => 'Sales Tax',
3092 'tax_display_settings' => 'Inclusive',
3095 foreach ($contributeSetting as $setting => $value) {
3096 Civi
::settings()->set($setting, $value);
3101 * Enable Tax and Invoicing
3103 protected function disableTaxAndInvoicing(): void
{
3104 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3105 foreach ($accounts as $account) {
3106 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3107 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3110 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3111 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3113 Civi
::settings()->set('invoice_is_email_pdf', FALSE);
3114 Civi
::settings()->set('invoicing', FALSE);
3118 * Add Sales Tax Account for the financial type.
3120 * @param int $financialTypeId
3122 * @param array $accountParams
3124 * @return CRM_Financial_DAO_EntityFinancialAccount
3125 * @throws \CRM_Core_Exception
3127 protected function addTaxAccountToFinancialType(int $financialTypeId, $accountParams = []) {
3128 $params = array_merge([
3129 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3130 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3131 'is_deductible' => 1,
3136 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3138 'entity_table' => 'civicrm_financial_type',
3139 'entity_id' => $financialTypeId,
3140 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3143 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3144 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = $params['tax_rate'];
3146 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3147 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3148 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3149 $dao->copyValues($entityParams);
3151 if ($dao->fetch()) {
3152 $entityParams['id'] = $dao->id
;
3154 $entityParams['financial_account_id'] = $account->id
;
3156 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3160 * Create price set with contribution test for test setup.
3162 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3163 * on parent class at some point (fn is not in 4.4).
3166 * @param array $params
3168 public function createPriceSetWithPage($entity = NULL, $params = []) {
3169 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3170 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3171 'title' => 'Test Contribution Page',
3172 'financial_type_id' => 1,
3173 'currency' => 'NZD',
3174 'goal_amount' => 50,
3175 'is_pay_later' => 1,
3176 'is_monetary' => TRUE,
3177 'is_email_receipt' => FALSE,
3179 $priceSet = $this->callAPISuccess('price_set', 'create', [
3180 'is_quick_config' => 0,
3181 'extends' => 'CiviMember',
3182 'financial_type_id' => 1,
3183 'title' => 'my Page',
3185 $priceSetID = $priceSet['id'];
3187 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3188 $priceField = $this->callAPISuccess('price_field', 'create', [
3189 'price_set_id' => $priceSetID,
3190 'label' => 'Goat Breed',
3191 'html_type' => 'Radio',
3193 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3194 'price_set_id' => $priceSetID,
3195 'price_field_id' => $priceField['id'],
3196 'label' => 'Long Haired Goat',
3198 'financial_type_id' => 'Donation',
3199 'membership_type_id' => $membershipTypeID,
3200 'membership_num_terms' => 1,
3202 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3203 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3204 'price_set_id' => $priceSetID,
3205 'price_field_id' => $priceField['id'],
3206 'label' => 'Shoe-eating Goat',
3208 'financial_type_id' => 'Donation',
3209 'membership_type_id' => $membershipTypeID,
3210 'membership_num_terms' => 2,
3212 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3214 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3215 'price_set_id' => $priceSetID,
3216 'price_field_id' => $priceField['id'],
3217 'label' => 'Shoe-eating Goat',
3219 'financial_type_id' => 'Donation',
3221 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3223 $this->_ids
['price_set'] = $priceSetID;
3224 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3225 $this->_ids
['price_field'] = [$priceField['id']];
3227 $this->_ids
['membership_type'] = $membershipTypeID;
3231 * Only specified contact returned.
3233 * @implements CRM_Utils_Hook::aclWhereClause
3237 * @param $whereTables
3241 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3242 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3246 * @implements CRM_Utils_Hook::selectWhereClause
3248 * @param string $entity
3249 * @param array $clauses
3251 public function selectWhereClauseHook($entity, &$clauses) {
3252 if ($entity == 'Event') {
3253 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3258 * An implementation of hook_civicrm_post used with all our test cases.
3261 * @param string $objectName
3262 * @param int $objectId
3265 public function onPost($op, $objectName, $objectId, &$objectRef) {
3266 if ($op == 'create' && $objectName == 'Individual') {
3267 CRM_Core_DAO
::executeQuery(
3268 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3270 1 => [$objectId, 'Integer'],
3275 if ($op == 'edit' && $objectName == 'Participant') {
3277 1 => [$objectId, 'Integer'],
3279 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3280 CRM_Core_DAO
::executeQuery($query, $params);
3285 * Instantiate form object.
3287 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3289 * @param string $class
3290 * Name of form class.
3292 * @param array $formValues
3294 * @param string $pageName
3296 * @return \CRM_Core_Form
3297 * @throws \CRM_Core_Exception
3299 public function getFormObject($class, $formValues = [], $pageName = '') {
3300 $_POST = $formValues;
3301 /* @var CRM_Core_Form $form */
3302 $form = new $class();
3303 $_SERVER['REQUEST_METHOD'] = 'GET';
3305 case 'CRM_Event_Cart_Form_Checkout_Payment':
3306 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3307 $form->controller
= new CRM_Event_Cart_Controller_Checkout();
3311 $form->controller
= new CRM_Core_Controller();
3314 $pageName = $form->getName();
3316 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3317 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3322 * Get possible thousand separators.
3326 public function getThousandSeparators() {
3327 return [['.'], [',']];
3331 * Get the boolean options as a provider.
3335 public function getBooleanDataProvider() {
3336 return [[TRUE], [FALSE]];
3340 * Set the separators for thousands and decimal points.
3342 * Note that this only covers some common scenarios.
3344 * It does not cater for a situation where the thousand separator is a [space]
3345 * Latter is the Norwegian localization. At least some tests need to
3346 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3347 * to provide broader coverage.
3349 * @param string $thousandSeparator
3351 protected function setCurrencySeparators($thousandSeparator) {
3352 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3353 Civi
::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3357 * Sets the thousand separator.
3359 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3361 * @param $thousandSeparator
3363 protected function setMonetaryThousandSeparator($thousandSeparator) {
3364 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3368 * Sets the decimal separator.
3370 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3372 * @param $decimalPoint
3374 protected function setMonetaryDecimalPoint($decimalPoint) {
3375 Civi
::settings()->set('monetaryDecimalPoint', $decimalPoint);
3379 * Sets the default currency.
3383 protected function setDefaultCurrency($currency) {
3384 Civi
::settings()->set('defaultCurrency', $currency);
3388 * Format money as it would be input.
3390 * @param string $amount
3394 protected function formatMoneyInput($amount) {
3395 return CRM_Utils_Money
::format($amount, NULL, '%a');
3399 * Get the contribution object.
3401 * @param int $contributionID
3403 * @return \CRM_Contribute_BAO_Contribution
3405 protected function getContributionObject($contributionID) {
3406 $contributionObj = new CRM_Contribute_BAO_Contribution();
3407 $contributionObj->id
= $contributionID;
3408 $contributionObj->find(TRUE);
3409 return $contributionObj;
3413 * Enable multilingual.
3415 public function enableMultilingual() {
3416 $this->callAPISuccess('Setting', 'create', [
3417 'lcMessages' => 'en_US',
3418 'languageLimit' => [
3423 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3426 $dbLocale = '_en_US';
3430 * Setup or clean up SMS tests
3432 * @param bool $teardown
3434 * @throws \CiviCRM_API3_Exception
3436 public function setupForSmsTests($teardown = FALSE) {
3437 require_once 'CiviTest/CiviTestSMSProvider.php';
3439 // Option value params for CiviTestSMSProvider
3440 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3442 'option_group_id' => $groupID,
3443 'label' => 'unittestSMS',
3444 'value' => 'unit.test.sms',
3445 'name' => 'CiviTestSMSProvider',
3452 // Test completed, delete provider
3453 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3454 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3458 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3459 // in CRM/Sms/Provider.php even though it is not an extension.
3460 return civicrm_api3('option_value', 'create', $params);
3464 * Start capturing browser output.
3466 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3468 protected function startCapturingOutput() {
3470 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3474 * Stop capturing browser output and return as a csv.
3476 * @param bool $isFirstRowHeaders
3478 * @return \League\Csv\Reader
3480 * @throws \League\Csv\Exception
3482 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3483 $output = ob_get_flush();
3484 $stream = fopen('php://memory', 'r+');
3485 fwrite($stream, $output);
3487 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3488 $csv = Reader
::createFromString($output);
3489 if ($isFirstRowHeaders) {
3490 $csv->setHeaderOffset(0);
3497 * Rename various labels to not match the names.
3499 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3500 * code that expects it to.
3502 protected function renameLabels() {
3503 $replacements = ['Pending', 'Refunded'];
3504 foreach ($replacements as $name) {
3505 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3510 * Undo any label renaming.
3512 protected function resetLabels() {
3513 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3517 * Get parameters to set up a multi-line participant order.
3520 * @throws \CRM_Core_Exception
3522 protected function getParticipantOrderParams(): array {
3523 $event = $this->eventCreate();
3524 $this->_eventId
= $event['id'];
3526 'id' => $this->_eventId
,
3527 'financial_type_id' => 4,
3530 $this->callAPISuccess('event', 'create', $eventParams);
3531 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3533 'total_amount' => 300,
3534 'currency' => 'USD',
3535 'contact_id' => $this->individualCreate(),
3536 'financial_type_id' => 4,
3537 'contribution_status_id' => 'Pending',
3539 foreach ($priceFields['values'] as $key => $priceField) {
3540 $orderParams['line_items'][] = [
3543 'price_field_id' => $priceField['price_field_id'],
3544 'price_field_value_id' => $priceField['id'],
3545 'label' => $priceField['label'],
3546 'field_title' => $priceField['label'],
3548 'unit_price' => $priceField['amount'],
3549 'line_total' => $priceField['amount'],
3550 'financial_type_id' => $priceField['financial_type_id'],
3551 'entity_table' => 'civicrm_participant',
3555 'financial_type_id' => 4,
3556 'event_id' => $this->_eventId
,
3559 'fee_currency' => 'USD',
3560 'contact_id' => $this->individualCreate(),
3564 return $orderParams;
3570 * @throws \CRM_Core_Exception
3572 protected function validatePayments($payments): void
{
3573 foreach ($payments as $payment) {
3574 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3575 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3576 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3577 // This is a pretty rough way at guessing which payment is the overpayment - but
3578 // for the test suite it should be enough.
3581 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3582 'financial_trxn_id' => $payment['id'],
3583 'entity_table' => 'civicrm_financial_item',
3584 'return' => ['amount'],
3587 foreach ($items as $item) {
3588 $itemTotal +
= $item['amount'];
3590 $this->assertEquals($payment['total_amount'], $itemTotal);
3595 * Validate all created payments.
3597 * @throws \CRM_Core_Exception
3599 protected function validateAllPayments() {
3600 $payments = $this->callAPISuccess('Payment', 'get', [
3601 'return' => ['total_amount', 'tax_amount'],
3602 'options' => ['limit' => 0],
3604 $this->validatePayments($payments);
3608 * Validate all created contributions.
3610 * @throws \API_Exception
3612 protected function validateAllContributions(): void
{
3613 $contributions = Contribution
::get(FALSE)->setSelect(['total_amount', 'tax_amount'])->execute();
3614 foreach ($contributions as $contribution) {
3615 $lineItems = $this->callAPISuccess('LineItem', 'get', [
3616 'contribution_id' => $contribution['id'],
3617 'return' => ['tax_amount', 'line_total', 'entity_table', 'entity_id', 'qty'],
3623 foreach ($lineItems as $lineItem) {
3624 $total +
= $lineItem['line_total'];
3625 $taxTotal +
= (float) ($lineItem['tax_amount'] ??
0);
3626 if ($lineItem['entity_table'] === 'civicrm_membership') {
3627 $memberships[] = $lineItem['entity_id'];
3629 if ($lineItem['entity_table'] === 'civicrm_participant' && $lineItem['qty'] > 0) {
3630 $participants[$lineItem['entity_id']] = $lineItem['entity_id'];
3633 $membershipPayments = $this->callAPISuccess('MembershipPayment', 'get', ['contribution_id' => $contribution['id'], 'return' => 'membership_id'])['values'];
3634 $participantPayments = $this->callAPISuccess('ParticipantPayment', 'get', ['contribution_id' => $contribution['id'], 'return' => 'participant_id'])['values'];
3635 $this->assertCount(count($memberships), $membershipPayments);
3636 $this->assertCount(count($participants), $participantPayments);
3637 foreach ($membershipPayments as $payment) {
3638 $this->assertContains($payment['membership_id'], $memberships);
3640 foreach ($participantPayments as $payment) {
3641 $this->assertContains($payment['participant_id'], $participants);
3643 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ??
0));
3644 $this->assertEquals($total +
$taxTotal, $contribution['total_amount']);
3650 * @throws \CRM_Core_Exception
3652 protected function createRuleGroup() {
3653 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3654 'contact_type' => 'Individual',
3656 'used' => 'General',
3657 'name' => 'TestRule',
3658 'title' => 'TestRule',
3665 * Generic create test.
3667 * @param int $version
3669 * @throws \CRM_Core_Exception
3671 protected function basicCreateTest(int $version) {
3672 $this->_apiversion
= $version;
3673 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
3674 $this->assertEquals(1, $result['count']);
3675 $this->assertNotNull($result['values'][$result['id']]['id']);
3676 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
3680 * Generic delete test.
3682 * @param int $version
3684 * @throws \CRM_Core_Exception
3686 protected function basicDeleteTest($version) {
3687 $this->_apiversion
= $version;
3688 $result = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
3689 $deleteParams = ['id' => $result['id']];
3690 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
3691 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
3692 $this->assertEquals(0, $checkDeleted['count']);
3696 * Create and return a case object for the given Client ID.
3698 * @param int $clientId
3699 * @param int $loggedInUser
3700 * Omit or pass NULL to use the same as clientId
3701 * @param array $extra
3702 * Optional specific parameters such as start_date
3704 * @return CRM_Case_BAO_Case
3706 public function createCase($clientId, $loggedInUser = NULL, $extra = []) {
3707 if (empty($loggedInUser)) {
3708 // backwards compatibility - but it's more typical that the creator is a different person than the client
3709 $loggedInUser = $clientId;
3711 $caseParams = array_merge([
3712 'activity_subject' => 'Case Subject',
3713 'client_id' => $clientId,
3714 'case_type_id' => 1,
3716 'case_type' => 'housing_support',
3717 'subject' => 'Case Subject',
3718 'start_date' => date("Y-m-d"),
3719 'start_date_time' => date("YmdHis"),
3721 'activity_details' => '',
3723 $form = new CRM_Case_Form_Case();
3724 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3728 * Validate that all location entities have exactly one primary.
3730 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3732 public function assertLocationValidity() {
3733 $this->assertEquals(0, CRM_Core_DAO
::singleValueQuery('SELECT COUNT(*) FROM
3735 (SELECT a1.contact_id
3736 FROM civicrm_address a1
3737 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3738 AND a1.contact_id = a2.contact_id
3741 AND a2.id IS NOT NULL
3742 AND a1.contact_id IS NOT NULL
3744 SELECT a1.contact_id
3745 FROM civicrm_address a1
3746 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3747 AND a1.contact_id = a2.contact_id
3748 WHERE a1.is_primary = 0
3750 AND a1.contact_id IS NOT NULL
3754 SELECT a1.contact_id
3755 FROM civicrm_email a1
3756 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3757 AND a1.contact_id = a2.contact_id
3760 AND a2.id IS NOT NULL
3761 AND a1.contact_id IS NOT NULL
3763 SELECT a1.contact_id
3764 FROM civicrm_email a1
3765 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3766 AND a1.contact_id = a2.contact_id
3767 WHERE a1.is_primary = 0
3769 AND a1.contact_id IS NOT NULL
3773 SELECT a1.contact_id
3774 FROM civicrm_phone a1
3775 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3776 AND a1.contact_id = a2.contact_id
3779 AND a2.id IS NOT NULL
3780 AND a1.contact_id IS NOT NULL
3782 SELECT a1.contact_id
3783 FROM civicrm_phone a1
3784 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3785 AND a1.contact_id = a2.contact_id
3786 WHERE a1.is_primary = 0
3788 AND a1.contact_id IS NOT NULL
3792 SELECT a1.contact_id
3794 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3795 AND a1.contact_id = a2.contact_id
3798 AND a2.id IS NOT NULL
3799 AND a1.contact_id IS NOT NULL
3801 SELECT a1.contact_id
3803 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3804 AND a1.contact_id = a2.contact_id
3805 WHERE a1.is_primary = 0
3807 AND a1.contact_id IS NOT NULL
3811 SELECT a1.contact_id
3812 FROM civicrm_openid a1
3813 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3814 AND a1.contact_id = a2.contact_id
3815 WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3818 SELECT a1.contact_id
3819 FROM civicrm_openid a1
3820 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3821 AND a1.contact_id = a2.contact_id
3824 AND a2.id IS NOT NULL
3825 AND a1.contact_id IS NOT NULL
3827 SELECT a1.contact_id
3828 FROM civicrm_openid a1
3829 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3830 AND a1.contact_id = a2.contact_id
3831 WHERE a1.is_primary = 0
3833 AND a1.contact_id IS NOT NULL) as primary_descrepancies
3838 * Ensure the specified mysql mode/s are activated.
3840 * @param array $modes
3842 protected function ensureMySQLMode(array $modes): void
{
3843 $currentModes = array_fill_keys(CRM_Utils_SQL
::getSqlModes(), 1);
3844 $currentModes = array_merge($currentModes, array_fill_keys($modes, 1));
3845 CRM_Core_DAO
::executeQuery("SET GLOBAL sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
3846 CRM_Core_DAO
::executeQuery("SET sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
3850 * Delete any extraneous relationship types.
3852 * @throws \API_Exception
3853 * @throws \Civi\API\Exception\UnauthorizedException
3855 protected function deleteNonDefaultRelationshipTypes(): void
{
3856 RelationshipType
::delete(FALSE)->addWhere('name_a_b', 'NOT IN', [
3863 'Head of Household for',
3864 'Household Member of',
3865 'Case Coordinator is',
3871 * Delete any existing custom data groups.
3873 * @throws \API_Exception
3875 protected function cleanupCustomGroups(): void
{
3876 CustomField
::get(FALSE)->setSelect(['option_group_id', 'custom_group_id'])
3877 ->addChain('delete_options', OptionGroup
::delete()
3878 ->addWhere('id', '=', '$option_group_id')
3880 ->addChain('delete_fields', CustomField
::delete()
3881 ->addWhere('id', '=', '$id')
3884 CustomGroup
::delete(FALSE)->addWhere('id', '>', 0)->execute();