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\Payment\System
;
30 use League\Csv\Reader
;
33 * Include class definitions
35 require_once 'api/api.php';
36 define('API_LATEST_VERSION', 3);
39 * Base class for CiviCRM unit tests
41 * This class supports two (mutually-exclusive) techniques for cleaning up test data. Subclasses
42 * may opt for one or neither:
44 * 1. quickCleanup() is a helper which truncates a series of tables. Call quickCleanup()
45 * as part of setUp() and/or tearDown(). quickCleanup() is thorough - but it can
46 * be cumbersome to use (b/c you must identify the tables to cleanup) and slow to execute.
47 * 2. useTransaction() executes the test inside a transaction. It's easier to use
48 * (because you don't need to identify specific tables), but it doesn't work for tests
49 * which manipulate schema or truncate data -- and could behave inconsistently
50 * for tests which specifically examine DB transactions.
52 * Common functions for unit tests
56 class CiviUnitTestCase
extends PHPUnit\Framework\TestCase
{
58 use \Civi\Test\Api3DocTrait
;
59 use \Civi\Test\GenericAssertionsTrait
;
60 use \Civi\Test\DbTestTrait
;
61 use \Civi\Test\ContactTestTrait
;
62 use \Civi\Test\MailingTestTrait
;
65 * Database has been initialized.
69 private static $dbInit = FALSE;
72 * Database connection.
74 * @var PHPUnit_Extensions_Database_DB_IDatabaseConnection
83 static protected $_dbName;
86 * Track tables we have modified during a test.
90 protected $_tablesToTruncate = [];
94 * Array of temporary directory names
100 * populateOnce allows to skip db resets in setUp
102 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
103 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
106 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
108 * @see http://forum.civicrm.org/index.php/topic,18065.0.html
110 public static $populateOnce = FALSE;
113 * DBResetRequired allows skipping DB reset
114 * in specific test case. If you still need
115 * to reset single test (method) of such case, call
116 * $this->cleanDB() in the first line of this
120 public $DBResetRequired = TRUE;
123 * @var CRM_Core_Transaction|null
128 * Array of IDs created to support the test.
131 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
138 * Should financials be checked after the test but before tear down.
140 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
141 * are some test data issues and some real bugs currently blockinng.
145 protected $isValidateFinancialsOnPostAssert = FALSE;
148 * Should location types be checked to ensure primary addresses are correctly assigned after each test.
152 protected $isLocationTypesOnPostAssert = TRUE;
155 * Class used for hooks during tests.
157 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
159 * $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
161 * @var \CRM_Utils_Hook_UnitTests
163 public $hookClass = NULL;
167 * Common values to be re-used multiple times within a class - usually to create the relevant entity
169 protected $_params = [];
172 * @var CRM_Extension_System
174 protected $origExtensionSystem;
177 * Array of IDs created during test setup routine.
179 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
183 public $setupIDs = [];
188 * Because we are overriding the parent class constructor, we
189 * need to show the same arguments as exist in the constructor of
190 * PHPUnit_Framework_TestCase, since
191 * PHPUnit_Framework_TestSuite::createTest() creates a
192 * ReflectionClass of the Test class and checks the constructor
193 * of that class to decide how to set up the test.
195 * @param string $name
197 * @param string $dataName
199 public function __construct($name = NULL, array $data = [], $dataName = '') {
200 parent
::__construct($name, $data, $dataName);
202 // we need full error reporting
203 error_reporting(E_ALL
& ~E_NOTICE
);
205 self
::$_dbName = self
::getDBName();
207 // also load the class loader
208 require_once 'CRM/Core/ClassLoader.php';
209 CRM_Core_ClassLoader
::singleton()->register();
210 if (function_exists('_civix_phpunit_setUp')) {
211 // FIXME: loosen coupling
212 _civix_phpunit_setUp();
217 * Override to run the test and assert its state.
221 * @throws \PHPUnit_Framework_IncompleteTest
222 * @throws \PHPUnit_Framework_SkippedTest
224 protected function runTest() {
226 return parent
::runTest();
228 catch (PEAR_Exception
$e) {
229 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
230 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
237 public function requireDBReset() {
238 return $this->DBResetRequired
;
244 public static function getDBName() {
245 static $dbName = NULL;
246 if ($dbName === NULL) {
247 require_once "DB.php";
248 $dsn = CRM_Utils_SQL
::autoSwitchDSN(CIVICRM_DSN
);
249 $dsninfo = DB
::parseDSN($dsn);
250 $dbName = $dsninfo['database'];
256 * Create database connection for this instance.
258 * Initialize the test database if it hasn't been initialized
261 protected function getConnection() {
262 if (!self
::$dbInit) {
263 $dbName = self
::getDBName();
265 // install test database
266 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
268 static::_populateDB(FALSE, $this);
270 self
::$dbInit = TRUE;
276 * Required implementation of abstract method.
278 protected function getDataSet() {
282 * @param bool $perClass
283 * @param null $object
286 * TRUE if the populate logic runs; FALSE if it is skipped
288 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
289 if (CIVICRM_UF
!== 'UnitTests') {
290 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
293 if ($perClass ||
$object == NULL) {
297 $dbreset = $object->requireDBReset();
300 if (self
::$populateOnce ||
!$dbreset) {
303 self
::$populateOnce = NULL;
305 Civi\Test
::data()->populate();
310 public static function setUpBeforeClass() {
311 static::_populateDB(TRUE);
313 // also set this global hack
314 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
318 * Common setup functions for all unit tests.
320 protected function setUp() {
321 $session = CRM_Core_Session
::singleton();
322 $session->set('userID', NULL);
324 $this->_apiversion
= 3;
327 $this->errorScope
= CRM_Core_TemporaryErrorScope
::useException();
328 // Use a temporary file for STDIN
329 $GLOBALS['stdin'] = tmpfile();
330 if ($GLOBALS['stdin'] === FALSE) {
331 echo "Couldn't open temporary file\n";
335 // Get and save a connection to the database
336 $this->_dbconn
= $this->getConnection();
338 // reload database before each test
339 // $this->_populateDB();
341 // "initialize" CiviCRM to avoid problems when running single tests
342 // FIXME: look at it closer in second stage
344 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
345 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
347 // disable any left-over test extensions
348 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
350 // reset all the caches
351 CRM_Utils_System
::flushCache();
353 // initialize the object once db is loaded
354 \Civi
::$statics = [];
356 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
358 // when running unit tests, use mockup user framework
359 $this->hookClass
= CRM_Utils_Hook
::singleton();
361 // Make sure the DB connection is setup properly
362 $config->userSystem
->setMySQLTimeZone();
363 $env = new CRM_Utils_Check_Component_Env();
364 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
366 // clear permissions stub to not check permissions
367 $config->userPermissionClass
->permissions
= NULL;
369 //flush component settings
370 CRM_Core_Component
::getEnabledComponents(TRUE);
372 $_REQUEST = $_GET = $_POST = [];
373 error_reporting(E_ALL
);
375 $this->renameLabels();
376 $this->_sethtmlGlobals();
380 * Read everything from the datasets directory and insert into the db.
382 public function loadAllFixtures() {
383 $fixturesDir = __DIR__
. '/../../fixtures';
385 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
387 $jsonFiles = glob($fixturesDir . '/*.json');
388 foreach ($jsonFiles as $jsonFixture) {
389 $json = json_decode(file_get_contents($jsonFixture));
390 foreach ($json as $tableName => $vars) {
391 if ($tableName === 'civicrm_contact') {
392 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');
395 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
397 foreach ($vars as $entity) {
398 $keys = $values = [];
399 foreach ($entity as $key => $value) {
401 $values[] = is_numeric($value) ?
$value : "'{$value}'";
403 CRM_Core_DAO
::executeQuery("
404 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
411 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
415 * Load the data that used to be handled by the discontinued dbunit class.
417 * This could do with further tidy up - the initial priority is simply to get rid of
418 * the dbunity package which is no longer supported.
420 * @param string $fileName
422 protected function loadXMLDataSet($fileName) {
423 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
424 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
425 foreach ($xml as $tableName => $element) {
426 if (!empty($element)) {
427 foreach ($element as $row) {
428 $keys = $values = [];
429 if (isset($row['@attributes'])) {
430 foreach ($row['@attributes'] as $key => $value) {
432 $values[] = is_numeric($value) ?
$value : "'{$value}'";
435 elseif (!empty($row)) {
436 // cos we copied it & it is inconsistent....
437 foreach ($row as $key => $value) {
439 $values[] = is_numeric($value) ?
$value : "'{$value}'";
443 if (!empty($values)) {
444 CRM_Core_DAO
::executeQuery("
445 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
451 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
455 * Create default domain contacts for the two domains added during test class.
456 * database population.
458 public function createDomainContacts() {
459 $this->organizationCreate();
460 $this->organizationCreate(['organization_name' => 'Second Domain']);
464 * Common teardown functions for all unit tests.
466 protected function tearDown() {
467 $this->_apiversion
= 3;
468 $this->resetLabels();
470 error_reporting(E_ALL
& ~E_NOTICE
);
471 CRM_Utils_Hook
::singleton()->reset();
472 if ($this->hookClass
) {
473 $this->hookClass
->reset();
475 CRM_Core_Session
::singleton()->reset(1);
478 $this->tx
->rollback()->commit();
481 CRM_Core_Transaction
::forceRollbackIfEnabled();
482 \Civi\Core\Transaction\Manager
::singleton(TRUE);
485 CRM_Core_Transaction
::forceRollbackIfEnabled();
486 \Civi\Core\Transaction\Manager
::singleton(TRUE);
488 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match'];
489 $this->quickCleanup($tablesToTruncate);
490 $this->createDomainContacts();
493 $this->cleanTempDirs();
494 $this->unsetExtensionSystem();
495 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
496 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
500 * CHeck that all tests that have created payments have created them with the right financial entities.
502 * @throws \CRM_Core_Exception
504 protected function assertPostConditions() {
505 if ($this->isLocationTypesOnPostAssert
) {
506 $this->assertLocationValidity();
508 if (!$this->isValidateFinancialsOnPostAssert
) {
511 $this->validateAllPayments();
512 $this->validateAllContributions();
516 * Create a batch of external API calls which can
517 * be executed concurrently.
520 * $calls = $this->createExternalAPI()
521 * ->addCall('Contact', 'get', ...)
522 * ->addCall('Contact', 'get', ...)
528 * @return \Civi\API\ExternalBatch
529 * @throws PHPUnit_Framework_SkippedTestError
531 public function createExternalAPI() {
532 global $civicrm_root;
534 'version' => $this->_apiversion
,
538 $calls = new \Civi\API\
ExternalBatch($defaultParams);
540 if (!$calls->isSupported()) {
541 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
548 * Create required data based on $this->entity & $this->params
549 * This is just a way to set up the test data for delete & get functions
550 * so the distinction between set
551 * up & tested functions is clearer
556 public function createTestEntity() {
557 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
561 * @param int $contactTypeId
565 public function contactTypeDelete($contactTypeId) {
566 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
568 throw new Exception('Could not delete contact type');
573 * @param array $params
577 public function membershipTypeCreate($params = []) {
578 CRM_Member_PseudoConstant
::flush('membershipType');
579 CRM_Core_Config
::clearDBCache();
580 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
581 $params = array_merge([
583 'duration_unit' => 'year',
584 'duration_interval' => 1,
585 'period_type' => 'rolling',
586 'member_of_contact_id' => $memberOfOrganization,
588 'financial_type_id' => 2,
591 'visibility' => 'Public',
594 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
596 CRM_Member_PseudoConstant
::flush('membershipType');
597 CRM_Utils_Cache
::singleton()->flush();
599 return $result['id'];
605 * @param array $params
608 * @throws \CRM_Core_Exception
610 public function contactMembershipCreate($params) {
611 $params = array_merge([
612 'join_date' => '2007-01-21',
613 'start_date' => '2007-01-21',
614 'end_date' => '2007-12-21',
615 'source' => 'Payment',
616 'membership_type_id' => 'General',
618 if (!is_numeric($params['membership_type_id'])) {
619 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
620 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
621 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
625 $result = $this->callAPISuccess('Membership', 'create', $params);
626 return $result['id'];
630 * Delete Membership Type.
632 * @param array $params
634 public function membershipTypeDelete($params) {
635 $this->callAPISuccess('MembershipType', 'Delete', $params);
639 * @param int $membershipID
641 public function membershipDelete($membershipID) {
642 $deleteParams = ['id' => $membershipID];
643 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
647 * @param string $name
651 * @throws \CRM_Core_Exception
653 public function membershipStatusCreate($name = 'test member status') {
654 $params['name'] = $name;
655 $params['start_event'] = 'start_date';
656 $params['end_event'] = 'end_date';
657 $params['is_current_member'] = 1;
658 $params['is_active'] = 1;
660 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
661 CRM_Member_PseudoConstant
::flush('membershipStatus');
662 return (int) $result['id'];
666 * Delete the given membership status, deleting any memberships of the status first.
668 * @param int $membershipStatusID
670 * @throws \CRM_Core_Exception
672 public function membershipStatusDelete(int $membershipStatusID) {
673 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
674 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
677 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
678 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
679 $renewedMembershipEndDate = new DateTime($membershipEndDate);
680 switch ($durationUnit) {
682 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
686 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
687 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
688 $renewedMembershipEndDate->add(new DateInterval('P1D'));
689 $renewedMembershipEndDate->add(new DateInterval('P1M'));
690 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
693 return $renewedMembershipEndDate->format('Y-m-d');
697 * Create a relationship type.
699 * @param array $params
703 * @throws \CRM_Core_Exception
705 public function relationshipTypeCreate($params = []) {
706 $params = array_merge([
707 'name_a_b' => 'Relation 1 for relationship type create',
708 'name_b_a' => 'Relation 2 for relationship type create',
709 'contact_type_a' => 'Individual',
710 'contact_type_b' => 'Organization',
715 $result = $this->callAPISuccess('relationship_type', 'create', $params);
716 CRM_Core_PseudoConstant
::flush('relationshipType');
718 return $result['id'];
722 * Delete Relatinship Type.
724 * @param int $relationshipTypeID
726 public function relationshipTypeDelete($relationshipTypeID) {
727 $params['id'] = $relationshipTypeID;
728 $check = $this->callAPISuccess('relationship_type', 'get', $params);
729 if (!empty($check['count'])) {
730 $this->callAPISuccess('relationship_type', 'delete', $params);
735 * @param array $params
738 * @throws \CRM_Core_Exception
740 public function paymentProcessorTypeCreate($params = []) {
741 $params = array_merge([
742 'name' => 'API_Test_PP',
743 'title' => 'API Test Payment Processor',
744 'class_name' => 'CRM_Core_Payment_APITest',
745 'billing_mode' => 'form',
750 $result = $this->callAPISuccess('PaymentProcessorType', 'create', $params);
752 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
754 return $result['id'];
758 * Create test Authorize.net instance.
760 * @param array $params
763 * @throws \CRM_Core_Exception
765 public function paymentProcessorAuthorizeNetCreate($params = []) {
766 $params = array_merge([
767 'name' => 'Authorize',
768 'domain_id' => CRM_Core_Config
::domainID(),
769 'payment_processor_type_id' => 'AuthNet',
770 'title' => 'AuthNet',
775 'user_name' => '4y5BfuW7jm',
776 'password' => '4cAmW927n8uLf5J8',
777 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
778 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
779 'class_name' => 'Payment_AuthorizeNet',
783 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
784 return (int) $result['id'];
788 * Create Participant.
790 * @param array $params
791 * Array of contact id and event id values.
794 * $id of participant created
796 public function participantCreate($params = []) {
797 if (empty($params['contact_id'])) {
798 $params['contact_id'] = $this->individualCreate();
800 if (empty($params['event_id'])) {
801 $event = $this->eventCreate();
802 $params['event_id'] = $event['id'];
807 'register_date' => 20070219,
808 'source' => 'Wimbeldon',
809 'event_level' => 'Payment',
813 $params = array_merge($defaults, $params);
814 $result = $this->callAPISuccess('Participant', 'create', $params);
815 return $result['id'];
819 * Create Payment Processor.
822 * Id Payment Processor
824 public function processorCreate($params = []) {
828 'payment_processor_type_id' => 'Dummy',
829 'financial_account_id' => 12,
833 'url_site' => 'http://dummy.com',
834 'url_recur' => 'http://dummy.com',
837 'payment_instrument_id' => 'Debit Card',
839 $processorParams = array_merge($processorParams, $params);
840 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
841 return $processor['id'];
845 * Create Payment Processor.
847 * @param array $processorParams
849 * @return \CRM_Core_Payment_Dummy
850 * Instance of Dummy Payment Processor
852 * @throws \CiviCRM_API3_Exception
854 public function dummyProcessorCreate($processorParams = []) {
855 $paymentProcessorID = $this->processorCreate($processorParams);
856 // 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
857 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
858 $processorParams['is_test'] = FALSE;
859 $this->processorCreate($processorParams);
860 return System
::singleton()->getById($paymentProcessorID);
864 * Create contribution page.
866 * @param array $params
869 * Array of contribution page
871 public function contributionPageCreate($params = []) {
872 $this->_pageParams
= array_merge([
873 'title' => 'Test Contribution Page',
874 'financial_type_id' => 1,
876 'financial_account_id' => 1,
878 'is_allow_other_amount' => 1,
880 'max_amount' => 1000,
882 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
886 * Create a sample batch.
888 public function batchCreate() {
889 $params = $this->_params
;
890 $params['name'] = $params['title'] = 'Batch_433397';
891 $params['status_id'] = 1;
892 $result = $this->callAPISuccess('batch', 'create', $params);
893 return $result['id'];
899 * @param array $params
902 * result of created tag
904 public function tagCreate($params = []) {
906 'name' => 'New Tag3',
907 'description' => 'This is description for Our New Tag ',
910 $params = array_merge($defaults, $params);
911 $result = $this->callAPISuccess('Tag', 'create', $params);
912 return $result['values'][$result['id']];
919 * Id of the tag to be deleted.
923 public function tagDelete($tagId) {
924 require_once 'api/api.php';
928 $result = $this->callAPISuccess('Tag', 'delete', $params);
929 return $result['id'];
933 * Add entity(s) to the tag
935 * @param array $params
939 public function entityTagAdd($params) {
940 $result = $this->callAPISuccess('entity_tag', 'create', $params);
947 * @param array $params
951 * id of created pledge
953 * @throws \CRM_Core_Exception
955 public function pledgeCreate($params) {
956 $params = array_merge([
957 'pledge_create_date' => date('Ymd'),
958 'start_date' => date('Ymd'),
959 'scheduled_date' => date('Ymd'),
961 'pledge_status_id' => '2',
962 'financial_type_id' => '1',
963 'pledge_original_installment_amount' => 20,
964 'frequency_interval' => 5,
965 'frequency_unit' => 'year',
966 'frequency_day' => 15,
971 $result = $this->callAPISuccess('Pledge', 'create', $params);
972 return $result['id'];
976 * Delete contribution.
978 * @param int $pledgeId
980 * @throws \CRM_Core_Exception
982 public function pledgeDelete($pledgeId) {
984 'pledge_id' => $pledgeId,
986 $this->callAPISuccess('Pledge', 'delete', $params);
990 * Create contribution.
992 * @param array $params
993 * Array of parameters.
996 * id of created contribution
997 * @throws \CRM_Core_Exception
999 public function contributionCreate($params) {
1001 $params = array_merge([
1003 'receive_date' => date('Ymd'),
1004 'total_amount' => 100.00,
1005 'fee_amount' => 5.00,
1006 'financial_type_id' => 1,
1007 'payment_instrument_id' => 1,
1008 'non_deductible_amount' => 10.00,
1010 'contribution_status_id' => 1,
1013 $result = $this->callAPISuccess('contribution', 'create', $params);
1014 return $result['id'];
1018 * Delete contribution.
1020 * @param int $contributionId
1023 * @throws \CRM_Core_Exception
1025 public function contributionDelete($contributionId) {
1027 'contribution_id' => $contributionId,
1029 $result = $this->callAPISuccess('contribution', 'delete', $params);
1036 * @param array $params
1037 * Name-value pair for an event.
1040 * @throws \CRM_Core_Exception
1042 public function eventCreate($params = []) {
1043 // if no contact was passed, make up a dummy event creator
1044 if (!isset($params['contact_id'])) {
1045 $params['contact_id'] = $this->_contactCreate([
1046 'contact_type' => 'Individual',
1047 'first_name' => 'Event',
1048 'last_name' => 'Creator',
1052 // set defaults for missing params
1053 $params = array_merge([
1054 'title' => 'Annual CiviCRM meet',
1055 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1056 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1057 'event_type_id' => 1,
1059 'start_date' => 20081021,
1060 'end_date' => 20081023,
1061 'is_online_registration' => 1,
1062 'registration_start_date' => 20080601,
1063 'registration_end_date' => 20081015,
1064 'max_participants' => 100,
1065 'event_full_text' => 'Sorry! We are already full',
1068 'is_show_location' => 0,
1069 'is_email_confirm' => 1,
1072 return $this->callAPISuccess('Event', 'create', $params);
1076 * Create a paid event.
1078 * @param array $params
1080 * @param array $options
1082 * @param string $key
1083 * Index for storing event ID in ids array.
1087 * @throws \CRM_Core_Exception
1089 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1090 $params['is_monetary'] = TRUE;
1091 $event = $this->eventCreate($params);
1092 $this->ids
['Event'][$key] = (int) $event['id'];
1093 $this->ids
['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1094 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->ids
['PriceSet'][$key]);
1095 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->ids
['PriceSet'][$key], TRUE, FALSE);
1096 $priceSet = $priceSet[$this->ids
['PriceSet'][$key]] ??
NULL;
1097 $this->eventFeeBlock
= $priceSet['fields'] ??
NULL;
1109 public function eventDelete($id) {
1113 return $this->callAPISuccess('event', 'delete', $params);
1117 * Delete participant.
1119 * @param int $participantID
1123 public function participantDelete($participantID) {
1125 'id' => $participantID,
1127 $check = $this->callAPISuccess('Participant', 'get', $params);
1128 if ($check['count'] > 0) {
1129 return $this->callAPISuccess('Participant', 'delete', $params);
1134 * Create participant payment.
1136 * @param int $participantID
1137 * @param int $contributionID
1140 * $id of created payment
1142 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1143 //Create Participant Payment record With Values
1145 'participant_id' => $participantID,
1146 'contribution_id' => $contributionID,
1149 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1150 return $result['id'];
1154 * Delete participant payment.
1156 * @param int $paymentID
1158 public function participantPaymentDelete($paymentID) {
1162 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1168 * @param int $contactID
1171 * location id of created location
1173 public function locationAdd($contactID) {
1176 'location_type' => 'New Location Type',
1178 'name' => 'Saint Helier St',
1179 'county' => 'Marin',
1180 'country' => 'UNITED STATES',
1181 'state_province' => 'Michigan',
1182 'supplemental_address_1' => 'Hallmark Ct',
1183 'supplemental_address_2' => 'Jersey Village',
1184 'supplemental_address_3' => 'My Town',
1189 'contact_id' => $contactID,
1190 'address' => $address,
1191 'location_format' => '2.0',
1192 'location_type' => 'New Location Type',
1195 $result = $this->callAPISuccess('Location', 'create', $params);
1200 * Delete Locations of contact.
1202 * @param array $params
1205 public function locationDelete($params) {
1206 $this->callAPISuccess('Location', 'delete', $params);
1210 * Add a Location Type.
1212 * @param array $params
1214 * @return CRM_Core_DAO_LocationType
1215 * location id of created location
1217 public function locationTypeCreate($params = NULL) {
1218 if ($params === NULL) {
1220 'name' => 'New Location Type',
1221 'vcard_name' => 'New Location Type',
1222 'description' => 'Location Type for Delete',
1227 $locationType = new CRM_Core_DAO_LocationType();
1228 $locationType->copyValues($params);
1229 $locationType->save();
1230 // clear getfields cache
1231 CRM_Core_PseudoConstant
::flush();
1232 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1233 return $locationType->id
;
1237 * Delete a Location Type.
1239 * @param int $locationTypeId
1241 public function locationTypeDelete($locationTypeId) {
1242 $locationType = new CRM_Core_DAO_LocationType();
1243 $locationType->id
= $locationTypeId;
1244 $locationType->delete();
1250 * @param array $params
1252 * @return CRM_Core_DAO_Mapping
1253 * Mapping id of created mapping
1255 public function mappingCreate($params = NULL) {
1256 if ($params === NULL) {
1258 'name' => 'Mapping name',
1259 'description' => 'Mapping description',
1260 // 'Export Contact' mapping.
1261 'mapping_type_id' => 7,
1265 $mapping = new CRM_Core_DAO_Mapping();
1266 $mapping->copyValues($params);
1268 // clear getfields cache
1269 CRM_Core_PseudoConstant
::flush();
1270 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1277 * @param int $mappingId
1279 public function mappingDelete($mappingId) {
1280 $mapping = new CRM_Core_DAO_Mapping();
1281 $mapping->id
= $mappingId;
1286 * Prepare class for ACLs.
1288 protected function prepareForACLs() {
1289 $config = CRM_Core_Config
::singleton();
1290 $config->userPermissionClass
->permissions
= [];
1296 protected function cleanUpAfterACLs() {
1297 CRM_Utils_Hook
::singleton()->reset();
1298 $tablesToTruncate = [
1300 'civicrm_acl_cache',
1301 'civicrm_acl_entity_role',
1302 'civicrm_acl_contact_cache',
1304 $this->quickCleanup($tablesToTruncate);
1305 $config = CRM_Core_Config
::singleton();
1306 unset($config->userPermissionClass
->permissions
);
1310 * Create a smart group.
1312 * By default it will be a group of households.
1314 * @param array $smartGroupParams
1315 * @param array $groupParams
1316 * @param string $contactType
1320 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1321 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1322 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1324 $groupParams['saved_search_id'] = $savedSearch->id
;
1325 return $this->groupCreate($groupParams);
1331 * @param array $params
1333 public function uFFieldCreate($params = []) {
1334 $params = array_merge([
1336 'field_name' => 'first_name',
1339 'visibility' => 'Public Pages and Listings',
1340 'is_searchable' => '1',
1341 'label' => 'first_name',
1342 'field_type' => 'Individual',
1345 $this->callAPISuccess('uf_field', 'create', $params);
1349 * Add a UF Join Entry.
1351 * @param array $params
1354 * $id of created UF Join
1356 public function ufjoinCreate($params = NULL) {
1357 if ($params === NULL) {
1360 'module' => 'CiviEvent',
1361 'entity_table' => 'civicrm_event',
1367 $result = $this->callAPISuccess('uf_join', 'create', $params);
1372 * @param array $params
1373 * Optional parameters.
1374 * @param bool $reloadConfig
1375 * While enabling CiviCampaign component, we shouldn't always forcibly
1376 * reload config as this hinder hook call in test environment
1381 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1382 $this->enableCiviCampaign($reloadConfig);
1383 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1384 'name' => 'big_campaign',
1385 'title' => 'Campaign',
1387 return $campaign['id'];
1391 * Create Group for a contact.
1393 * @param int $contactId
1395 public function contactGroupCreate($contactId) {
1397 'contact_id.1' => $contactId,
1401 $this->callAPISuccess('GroupContact', 'Create', $params);
1405 * Delete Group for a contact.
1407 * @param int $contactId
1409 public function contactGroupDelete($contactId) {
1411 'contact_id.1' => $contactId,
1414 $this->civicrm_api('GroupContact', 'Delete', $params);
1420 * @param array $params
1424 * @throws \CRM_Core_Exception
1425 * @throws \CiviCRM_API3_Exception
1427 public function activityCreate($params = []) {
1428 $params = array_merge([
1429 'subject' => 'Discussion on warm beer',
1430 'activity_date_time' => date('Ymd'),
1432 'location' => 'Baker Street',
1433 'details' => 'Lets schedule a meeting',
1435 'activity_type_id' => 'Meeting',
1437 if (!isset($params['source_contact_id'])) {
1438 $params['source_contact_id'] = $this->individualCreate();
1440 if (!isset($params['target_contact_id'])) {
1441 $params['target_contact_id'] = $this->individualCreate([
1442 'first_name' => 'Julia',
1443 'last_name' => 'Anderson',
1445 'email' => 'julia_anderson@civicrm.org',
1446 'contact_type' => 'Individual',
1449 if (!isset($params['assignee_contact_id'])) {
1450 $params['assignee_contact_id'] = $params['target_contact_id'];
1453 $result = civicrm_api3('Activity', 'create', $params);
1455 $result['target_contact_id'] = $params['target_contact_id'];
1456 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1461 * Create an activity type.
1463 * @param array $params
1468 public function activityTypeCreate($params) {
1469 return $this->callAPISuccess('ActivityType', 'create', $params);
1473 * Delete activity type.
1475 * @param int $activityTypeId
1476 * Id of the activity type.
1480 public function activityTypeDelete($activityTypeId) {
1481 $params['activity_type_id'] = $activityTypeId;
1482 return $this->callAPISuccess('ActivityType', 'delete', $params);
1486 * Create custom group.
1488 * @param array $params
1492 public function customGroupCreate($params = []) {
1494 'title' => 'new custom group',
1495 'extends' => 'Contact',
1497 'style' => 'Inline',
1501 $params = array_merge($defaults, $params);
1503 return $this->callAPISuccess('custom_group', 'create', $params);
1507 * Existing function doesn't allow params to be over-ridden so need a new one
1508 * this one allows you to only pass in the params you want to change
1510 * @param array $params
1514 public function CustomGroupCreateByParams($params = []) {
1516 'title' => "API Custom Group",
1517 'extends' => 'Contact',
1519 'style' => 'Inline',
1522 $params = array_merge($defaults, $params);
1523 return $this->callAPISuccess('custom_group', 'create', $params);
1527 * Create custom group with multi fields.
1529 * @param array $params
1533 public function CustomGroupMultipleCreateByParams($params = []) {
1538 $params = array_merge($defaults, $params);
1539 return $this->CustomGroupCreateByParams($params);
1543 * Create custom group with multi fields.
1545 * @param array $params
1549 public function CustomGroupMultipleCreateWithFields($params = []) {
1550 // also need to pass on $params['custom_field'] if not set but not in place yet
1552 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1553 $ids['custom_group_id'] = $customGroup['id'];
1555 $customField = $this->customFieldCreate([
1556 'custom_group_id' => $ids['custom_group_id'],
1557 'label' => 'field_1' . $ids['custom_group_id'],
1561 $ids['custom_field_id'][] = $customField['id'];
1563 $customField = $this->customFieldCreate([
1564 'custom_group_id' => $ids['custom_group_id'],
1565 'default_value' => '',
1566 'label' => 'field_2' . $ids['custom_group_id'],
1569 $ids['custom_field_id'][] = $customField['id'];
1571 $customField = $this->customFieldCreate([
1572 'custom_group_id' => $ids['custom_group_id'],
1573 'default_value' => '',
1574 'label' => 'field_3' . $ids['custom_group_id'],
1577 $ids['custom_field_id'][] = $customField['id'];
1583 * Create a custom group with a single text custom field. See
1584 * participant:testCreateWithCustom for how to use this
1586 * @param string $function
1588 * @param string $filename
1592 * ids of created objects
1594 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1595 $params = ['title' => $function];
1596 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1597 $params['extends'] = $entity ?
$entity : 'Contact';
1598 $customGroup = $this->customGroupCreate($params);
1599 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1600 CRM_Core_PseudoConstant
::flush();
1602 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1606 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1607 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1609 * @param string $function
1611 * @param string $filename
1615 * ids of created objects
1617 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1618 $params = ['title' => $function];
1619 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1620 $params['extends'] = $entity ?
$entity : 'Contact';
1621 $customGroup = $this->customGroupCreate($params);
1622 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1623 CRM_Core_PseudoConstant
::flush();
1625 'defaultValue' => 'Default Value',
1626 'lowercasevalue' => 'Lowercase Value',
1627 1 => 'Integer Value',
1630 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1631 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1632 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1633 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1634 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1635 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1636 foreach ($options as $option_value => $option_label) {
1637 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1638 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1642 'custom_group_id' => $customGroup['id'],
1643 'custom_field_id' => $customField['id'],
1644 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1645 'custom_field_group_options' => $options,
1650 * Delete custom group.
1652 * @param int $customGroupID
1656 public function customGroupDelete($customGroupID) {
1657 $params['id'] = $customGroupID;
1658 return $this->callAPISuccess('custom_group', 'delete', $params);
1662 * Create custom field.
1664 * @param array $params
1665 * (custom_group_id) is required.
1669 public function customFieldCreate($params) {
1670 $params = array_merge([
1671 'label' => 'Custom Field',
1672 'data_type' => 'String',
1673 'html_type' => 'Text',
1674 'is_searchable' => 1,
1676 'default_value' => 'defaultValue',
1679 $result = $this->callAPISuccess('custom_field', 'create', $params);
1680 // these 2 functions are called with force to flush static caches
1681 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1682 CRM_Core_Component
::getEnabledComponents(1);
1687 * Delete custom field.
1689 * @param int $customFieldID
1693 public function customFieldDelete($customFieldID) {
1695 $params['id'] = $customFieldID;
1696 return $this->callAPISuccess('custom_field', 'delete', $params);
1706 public function noteCreate($cId) {
1708 'entity_table' => 'civicrm_contact',
1709 'entity_id' => $cId,
1710 'note' => 'hello I am testing Note',
1711 'contact_id' => $cId,
1712 'modified_date' => date('Ymd'),
1713 'subject' => 'Test Note',
1716 return $this->callAPISuccess('Note', 'create', $params);
1720 * Enable CiviCampaign Component.
1722 * @param bool $reloadConfig
1723 * Force relaod config or not
1725 public function enableCiviCampaign($reloadConfig = TRUE) {
1726 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1727 if ($reloadConfig) {
1728 // force reload of config object
1729 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1731 //flush cache by calling with reset
1732 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1736 * Create custom field with Option Values.
1738 * @param array $customGroup
1739 * @param string $name
1740 * Name of custom field.
1741 * @param array $extraParams
1742 * Additional parameters to pass through.
1746 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1748 'custom_group_id' => $customGroup['id'],
1749 'name' => 'test_custom_group',
1750 'label' => 'Country',
1751 'html_type' => 'Select',
1752 'data_type' => 'String',
1755 'is_searchable' => 0,
1761 'name' => 'option_group1',
1762 'label' => 'option_group_label1',
1766 'option_label' => ['Label1', 'Label2'],
1767 'option_value' => ['value1', 'value2'],
1768 'option_name' => [$name . '_1', $name . '_2'],
1769 'option_weight' => [1, 2],
1770 'option_status' => [1, 1],
1773 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1775 return $this->callAPISuccess('custom_field', 'create', $params);
1783 public function confirmEntitiesDeleted($entities) {
1784 foreach ($entities as $entity) {
1786 $result = $this->callAPISuccess($entity, 'Get', []);
1787 if ($result['error'] == 1 ||
$result['count'] > 0) {
1788 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1796 * Quick clean by emptying tables created for the test.
1798 * @param array $tablesToTruncate
1799 * @param bool $dropCustomValueTables
1801 * @throws \CRM_Core_Exception
1803 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1805 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1807 if ($dropCustomValueTables) {
1808 $optionGroupResult = CRM_Core_DAO
::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1809 while ($optionGroupResult->fetch()) {
1810 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1811 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1812 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1813 // generic cleanup routine.
1814 if (!empty($optionGroupResult->option_group_id
) && $optionGroupResult->option_group_id
> 50) {
1815 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id
);
1818 $tablesToTruncate[] = 'civicrm_custom_group';
1819 $tablesToTruncate[] = 'civicrm_custom_field';
1822 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1824 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1825 foreach ($tablesToTruncate as $table) {
1826 $sql = "TRUNCATE TABLE $table";
1827 CRM_Core_DAO
::executeQuery($sql);
1829 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1831 if ($dropCustomValueTables) {
1832 $dbName = self
::getDBName();
1834 SELECT TABLE_NAME as tableName
1835 FROM INFORMATION_SCHEMA.TABLES
1836 WHERE TABLE_SCHEMA = '{$dbName}'
1837 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1840 $tableDAO = CRM_Core_DAO
::executeQuery($query);
1841 while ($tableDAO->fetch()) {
1842 $sql = "DROP TABLE {$tableDAO->tableName}";
1843 CRM_Core_DAO
::executeQuery($sql);
1849 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1851 * @throws \CRM_Core_Exception
1853 public function quickCleanUpFinancialEntities() {
1854 $tablesToTruncate = [
1856 'civicrm_activity_contact',
1857 'civicrm_contribution',
1858 'civicrm_contribution_soft',
1859 'civicrm_contribution_product',
1860 'civicrm_financial_trxn',
1861 'civicrm_financial_item',
1862 'civicrm_contribution_recur',
1863 'civicrm_line_item',
1864 'civicrm_contribution_page',
1865 'civicrm_payment_processor',
1866 'civicrm_entity_financial_trxn',
1867 'civicrm_membership',
1868 'civicrm_membership_type',
1869 'civicrm_membership_payment',
1870 'civicrm_membership_log',
1871 'civicrm_membership_block',
1873 'civicrm_participant',
1874 'civicrm_participant_payment',
1876 'civicrm_pcp_block',
1878 'civicrm_pledge_block',
1879 'civicrm_pledge_payment',
1880 'civicrm_price_set_entity',
1881 'civicrm_price_field_value',
1882 'civicrm_price_field',
1884 $this->quickCleanup($tablesToTruncate);
1885 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1886 $this->restoreDefaultPriceSetConfig();
1887 $this->disableTaxAndInvoicing();
1888 $this->setCurrencySeparators(',');
1889 CRM_Core_PseudoConstant
::flush('taxRates');
1890 System
::singleton()->flushProcessors();
1891 // @fixme this parameter is leaking - it should not be defined as a class static
1892 // but for now we just handle in tear down.
1893 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1897 * Reset the price set config so results exist.
1899 public function restoreDefaultPriceSetConfig() {
1900 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1901 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1902 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)");
1903 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)");
1907 * Recreate default membership types.
1909 public function restoreMembershipTypes() {
1910 CRM_Core_DAO
::executeQuery(
1911 "REPLACE INTO civicrm_membership_type
1912 (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)
1914 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1915 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1916 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1921 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1922 * Default behaviour is to also delete the entity
1923 * @param array $params
1924 * Params array to check against.
1926 * Id of the entity concerned.
1927 * @param string $entity
1928 * Name of entity concerned (e.g. membership).
1929 * @param bool $delete
1930 * Should the entity be deleted as part of this check.
1931 * @param string $errorText
1932 * Text to print on error.
1936 * @param array $params
1939 * @param int $delete
1940 * @param string $errorText
1942 * @throws CRM_Core_Exception
1944 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1946 $result = $this->callAPISuccessGetSingle($entity, [
1951 $this->callAPISuccess($entity, 'Delete', [
1955 $dateFields = $keys = $dateTimeFields = [];
1956 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1957 foreach ($fields['values'] as $field => $settings) {
1958 if (array_key_exists($field, $result)) {
1959 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
1962 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
1964 $type = $settings['type'] ??
NULL;
1965 if ($type == CRM_Utils_Type
::T_DATE
) {
1966 $dateFields[] = $settings['name'];
1967 // we should identify both real names & unique names as dates
1968 if ($field != $settings['name']) {
1969 $dateFields[] = $field;
1972 if ($type == CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
1973 $dateTimeFields[] = $settings['name'];
1974 // we should identify both real names & unique names as dates
1975 if ($field != $settings['name']) {
1976 $dateTimeFields[] = $field;
1981 if (strtolower($entity) == 'contribution') {
1982 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1983 // this is not returned in id format
1984 unset($params['payment_instrument_id']);
1985 $params['contribution_source'] = $params['source'];
1986 unset($params['source']);
1989 foreach ($params as $key => $value) {
1990 if ($key == 'version' ||
substr($key, 0, 3) == 'api' ||
!array_key_exists($keys[$key], $result)) {
1993 if (in_array($key, $dateFields)) {
1994 $value = date('Y-m-d', strtotime($value));
1995 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1997 if (in_array($key, $dateTimeFields)) {
1998 $value = date('Y-m-d H:i:s', strtotime($value));
1999 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
2001 $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);
2006 * Get formatted values in the actual and expected result.
2008 * @param array $actual
2009 * Actual calculated values.
2010 * @param array $expected
2013 public function checkArrayEquals(&$actual, &$expected) {
2014 self
::unsetId($actual);
2015 self
::unsetId($expected);
2016 $this->assertEquals($expected, $actual);
2020 * Unset the key 'id' from the array
2022 * @param array $unformattedArray
2023 * The array from which the 'id' has to be unset.
2025 public static function unsetId(&$unformattedArray) {
2026 $formattedArray = [];
2027 if (array_key_exists('id', $unformattedArray)) {
2028 unset($unformattedArray['id']);
2030 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
2031 foreach ($unformattedArray['values'] as $key => $value) {
2032 if (is_array($value)) {
2033 foreach ($value as $k => $v) {
2039 elseif ($key == 'id') {
2040 $unformattedArray[$key];
2042 $formattedArray = [$value];
2044 $unformattedArray['values'] = $formattedArray;
2049 * Helper to enable/disable custom directory support
2051 * @param array $customDirs
2053 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2054 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2056 public function customDirectories($customDirs) {
2057 $config = CRM_Core_Config
::singleton();
2059 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2060 unset($config->customPHPPathDir
);
2062 elseif ($customDirs['php_path'] === TRUE) {
2063 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2066 $config->customPHPPathDir
= $php_path;
2069 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2070 unset($config->customTemplateDir
);
2072 elseif ($customDirs['template_path'] === TRUE) {
2073 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2076 $config->customTemplateDir
= $template_path;
2081 * Generate a temporary folder.
2083 * @param string $prefix
2087 public function createTempDir($prefix = 'test-') {
2088 $tempDir = CRM_Utils_File
::tempdir($prefix);
2089 $this->tempDirs
[] = $tempDir;
2093 public function cleanTempDirs() {
2094 if (!is_array($this->tempDirs
)) {
2095 // fix test errors where this is not set
2098 foreach ($this->tempDirs
as $tempDir) {
2099 if (is_dir($tempDir)) {
2100 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2106 * Temporarily replace the singleton extension with a different one.
2108 * @param \CRM_Extension_System $system
2110 public function setExtensionSystem(CRM_Extension_System
$system) {
2111 if ($this->origExtensionSystem
== NULL) {
2112 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2114 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2117 public function unsetExtensionSystem() {
2118 if ($this->origExtensionSystem
!== NULL) {
2119 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2120 $this->origExtensionSystem
= NULL;
2125 * Temporarily alter the settings-metadata to add a mock setting.
2127 * WARNING: The setting metadata will disappear on the next cache-clear.
2133 public function setMockSettingsMetaData($extras) {
2134 CRM_Utils_Hook
::singleton()
2135 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2136 $metadata = array_merge($metadata, $extras);
2139 Civi
::service('settings_manager')->flush();
2141 $fields = $this->callAPISuccess('setting', 'getfields', []);
2142 foreach ($extras as $key => $spec) {
2143 $this->assertNotEmpty($spec['title']);
2144 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2149 * @param string $name
2151 public function financialAccountDelete($name) {
2152 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2153 $financialAccount->name
= $name;
2154 if ($financialAccount->find(TRUE)) {
2155 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2156 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2157 $entityFinancialType->delete();
2158 $financialAccount->delete();
2163 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2164 * (NB unclear if this is still required)
2166 public function _sethtmlGlobals() {
2167 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2169 'html_quickform_rule_required',
2170 'HTML/QuickForm/Rule/Required.php',
2173 'html_quickform_rule_range',
2174 'HTML/QuickForm/Rule/Range.php',
2177 'html_quickform_rule_range',
2178 'HTML/QuickForm/Rule/Range.php',
2181 'html_quickform_rule_range',
2182 'HTML/QuickForm/Rule/Range.php',
2185 'html_quickform_rule_email',
2186 'HTML/QuickForm/Rule/Email.php',
2189 'html_quickform_rule_regex',
2190 'HTML/QuickForm/Rule/Regex.php',
2193 'html_quickform_rule_regex',
2194 'HTML/QuickForm/Rule/Regex.php',
2197 'html_quickform_rule_regex',
2198 'HTML/QuickForm/Rule/Regex.php',
2201 'html_quickform_rule_regex',
2202 'HTML/QuickForm/Rule/Regex.php',
2204 'nopunctuation' => [
2205 'html_quickform_rule_regex',
2206 'HTML/QuickForm/Rule/Regex.php',
2209 'html_quickform_rule_regex',
2210 'HTML/QuickForm/Rule/Regex.php',
2213 'html_quickform_rule_callback',
2214 'HTML/QuickForm/Rule/Callback.php',
2217 'html_quickform_rule_compare',
2218 'HTML/QuickForm/Rule/Compare.php',
2221 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2222 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2224 'HTML/QuickForm/group.php',
2225 'HTML_QuickForm_group',
2228 'HTML/QuickForm/hidden.php',
2229 'HTML_QuickForm_hidden',
2232 'HTML/QuickForm/reset.php',
2233 'HTML_QuickForm_reset',
2236 'HTML/QuickForm/checkbox.php',
2237 'HTML_QuickForm_checkbox',
2240 'HTML/QuickForm/file.php',
2241 'HTML_QuickForm_file',
2244 'HTML/QuickForm/image.php',
2245 'HTML_QuickForm_image',
2248 'HTML/QuickForm/password.php',
2249 'HTML_QuickForm_password',
2252 'HTML/QuickForm/radio.php',
2253 'HTML_QuickForm_radio',
2256 'HTML/QuickForm/button.php',
2257 'HTML_QuickForm_button',
2260 'HTML/QuickForm/submit.php',
2261 'HTML_QuickForm_submit',
2264 'HTML/QuickForm/select.php',
2265 'HTML_QuickForm_select',
2268 'HTML/QuickForm/hiddenselect.php',
2269 'HTML_QuickForm_hiddenselect',
2272 'HTML/QuickForm/text.php',
2273 'HTML_QuickForm_text',
2276 'HTML/QuickForm/textarea.php',
2277 'HTML_QuickForm_textarea',
2280 'HTML/QuickForm/fckeditor.php',
2281 'HTML_QuickForm_FCKEditor',
2284 'HTML/QuickForm/tinymce.php',
2285 'HTML_QuickForm_TinyMCE',
2288 'HTML/QuickForm/dojoeditor.php',
2289 'HTML_QuickForm_dojoeditor',
2292 'HTML/QuickForm/link.php',
2293 'HTML_QuickForm_link',
2296 'HTML/QuickForm/advcheckbox.php',
2297 'HTML_QuickForm_advcheckbox',
2300 'HTML/QuickForm/date.php',
2301 'HTML_QuickForm_date',
2304 'HTML/QuickForm/static.php',
2305 'HTML_QuickForm_static',
2308 'HTML/QuickForm/header.php',
2309 'HTML_QuickForm_header',
2312 'HTML/QuickForm/html.php',
2313 'HTML_QuickForm_html',
2316 'HTML/QuickForm/hierselect.php',
2317 'HTML_QuickForm_hierselect',
2320 'HTML/QuickForm/autocomplete.php',
2321 'HTML_QuickForm_autocomplete',
2324 'HTML/QuickForm/xbutton.php',
2325 'HTML_QuickForm_xbutton',
2327 'advmultiselect' => [
2328 'HTML/QuickForm/advmultiselect.php',
2329 'HTML_QuickForm_advmultiselect',
2335 * Set up an acl allowing contact to see 2 specified groups
2336 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2338 * You need to have pre-created these groups & created the user e.g
2339 * $this->createLoggedInUser();
2340 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2341 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2343 * @param bool $isProfile
2345 public function setupACL($isProfile = FALSE) {
2347 $_REQUEST = $this->_params
;
2349 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2350 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2351 $ov = new CRM_Core_DAO_OptionValue();
2352 $ov->option_group_id
= $optionGroupID;
2354 if ($ov->find(TRUE)) {
2355 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2357 $optionValue = $this->callAPISuccess('option_value', 'create', [
2358 'option_group_id' => $optionGroupID,
2359 'label' => 'pick me',
2363 CRM_Core_DAO
::executeQuery("
2364 TRUNCATE civicrm_acl_cache
2367 CRM_Core_DAO
::executeQuery("
2368 TRUNCATE civicrm_acl_contact_cache
2371 CRM_Core_DAO
::executeQuery("
2372 INSERT INTO civicrm_acl_entity_role (
2373 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2374 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2378 CRM_Core_DAO
::executeQuery("
2379 INSERT INTO civicrm_acl (
2380 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2383 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2388 CRM_Core_DAO
::executeQuery("
2389 INSERT INTO civicrm_acl (
2390 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2393 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2397 CRM_Core_DAO
::executeQuery("
2398 INSERT INTO civicrm_acl (
2399 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2402 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2407 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2408 $this->callAPISuccess('group_contact', 'create', [
2409 'group_id' => $this->_permissionedGroup
,
2410 'contact_id' => $this->_loggedInUser
,
2415 CRM_ACL_BAO_Cache
::resetCache();
2416 CRM_ACL_API
::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2421 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2423 public function offsetDefaultPriceSet() {
2424 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2425 $firstID = $contributionPriceSet['id'];
2426 $this->callAPISuccess('price_set', 'create', [
2427 'id' => $contributionPriceSet['id'],
2431 unset($contributionPriceSet['id']);
2432 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2433 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2434 'price_set_id' => $firstID,
2435 'options' => ['limit' => 1],
2437 unset($priceField['id']);
2438 $priceField['price_set_id'] = $newPriceSet['id'];
2439 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2440 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2441 'price_set_id' => $firstID,
2443 'options' => ['limit' => 1],
2446 unset($priceFieldValue['id']);
2447 //create some padding to use up ids
2448 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2449 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2450 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2454 * Create an instance of the paypal processor.
2456 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2457 * this parent class & we don't have a structure for that yet
2458 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2459 * & the best protection against that is the functions this class affords
2461 * @param array $params
2463 * @return int $result['id'] payment processor id
2465 public function paymentProcessorCreate($params = []) {
2466 $params = array_merge([
2468 'domain_id' => CRM_Core_Config
::domainID(),
2469 'payment_processor_type_id' => 'PayPal',
2473 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2474 'password' => '1183377788',
2475 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2476 'url_site' => 'https://www.sandbox.paypal.com/',
2477 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2478 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2479 'class_name' => 'Payment_PayPalImpl',
2480 'billing_mode' => 3,
2481 'financial_type_id' => 1,
2482 'financial_account_id' => 12,
2483 // Credit card = 1 so can pass 'by accident'.
2484 'payment_instrument_id' => 'Debit Card',
2486 if (!is_numeric($params['payment_processor_type_id'])) {
2487 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2489 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2490 'name' => $params['payment_processor_type_id'],
2494 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2495 return $result['id'];
2499 * Set up initial recurring payment allowing subsequent IPN payments.
2501 * @param array $recurParams (Optional)
2502 * @param array $contributionParams (Optional)
2504 * @throws \CRM_Core_Exception
2506 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2507 $this->ids
['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
2508 $contributionParams = array_merge([
2509 'total_amount' => '200',
2510 'invoice_id' => $this->_invoiceID
,
2511 'financial_type_id' => 'Donation',
2512 'contribution_status_id' => 'Pending',
2513 'contact_id' => $this->_contactID
,
2514 'contribution_page_id' => $this->_contributionPageID
,
2515 'payment_processor_id' => $this->_paymentProcessorID
,
2517 'receive_date' => '2019-07-25 07:34:23',
2518 'skipCleanMoney' => TRUE,
2519 'amount_level' => 'expensive',
2520 'campaign_id' => $this->ids
['campaign'][0],
2521 ], $contributionParams);
2522 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2523 'contact_id' => $this->_contactID
,
2526 'installments' => 5,
2527 'frequency_unit' => 'Month',
2528 'frequency_interval' => 1,
2529 'invoice_id' => $this->_invoiceID
,
2530 'contribution_status_id' => 2,
2531 'payment_processor_id' => $this->_paymentProcessorID
,
2532 // processor provided ID - use contact ID as proxy.
2533 'processor_id' => $this->_contactID
,
2534 'api.Order.create' => $contributionParams,
2535 ], $recurParams))['values'][0];
2536 $this->_contributionRecurID
= $contributionRecur['id'];
2537 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2538 $this->ids
['Contribution'][0] = $this->_contributionID
;
2542 * 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
2544 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2546 * @throws \CRM_Core_Exception
2548 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2549 $membershipParams = $recurParams = [];
2550 if (!empty($params['duration_unit'])) {
2551 $membershipParams['duration_unit'] = $params['duration_unit'];
2553 if (!empty($params['frequency_unit'])) {
2554 $recurParams['frequency_unit'] = $params['frequency_unit'];
2557 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2558 //create a contribution so our membership & contribution don't both have id = 1
2559 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2560 $this->contributionCreate([
2561 'contact_id' => $this->_contactID
,
2563 'financial_type_id' => 1,
2564 'invoice_id' => 'abcd',
2566 'receive_date' => '2019-07-25 07:34:23',
2570 $this->ids
['membership'] = $this->callAPISuccess('Membership', 'create', [
2571 'contact_id' => $this->_contactID
,
2572 'membership_type_id' => $this->ids
['membership_type'],
2573 'format.only_id' => TRUE,
2574 'source' => 'Payment',
2575 'skipLineItem' => TRUE,
2577 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2582 'entity_table' => 'civicrm_membership',
2583 'entity_id' => $this->ids
['membership'],
2584 'label' => 'General',
2586 'unit_price' => 200,
2587 'line_total' => 200,
2588 'financial_type_id' => 1,
2589 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2591 'label' => 'Membership Amount',
2592 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2594 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2596 'label' => 'General',
2597 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2604 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids
['membership'], 'contribution_recur_id' => $this->_contributionRecurID
]);
2612 public function CiviUnitTestCase_fatalErrorHandler($message) {
2613 throw new Exception("{$message['message']}: {$message['code']}");
2617 * Wrap the entire test case in a transaction.
2619 * Only subsequent DB statements will be wrapped in TX -- this cannot
2620 * retroactively wrap old DB statements. Therefore, it makes sense to
2621 * call this at the beginning of setUp().
2623 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2624 * this option does not work with, e.g., custom-data.
2626 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2627 * if TRUNCATE or ALTER is called while using a transaction.
2630 * Whether to use nesting or reference-counting.
2632 public function useTransaction($nest = TRUE) {
2634 $this->tx
= new CRM_Core_Transaction($nest);
2635 $this->tx
->rollback();
2640 * Assert the attachment exists.
2642 * @param bool $exists
2643 * @param array $apiResult
2645 protected function assertAttachmentExistence($exists, $apiResult) {
2646 $fileId = $apiResult['id'];
2647 $this->assertTrue(is_numeric($fileId));
2648 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2649 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2650 1 => [$fileId, 'Int'],
2652 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2653 1 => [$fileId, 'Int'],
2658 * Assert 2 sql strings are the same, ignoring double spaces.
2660 * @param string $expectedSQL
2661 * @param string $actualSQL
2662 * @param string $message
2664 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2665 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2666 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2667 $this->assertEquals($expected, $actual, $message);
2671 * Create a price set for an event.
2673 * @param int $feeTotal
2674 * @param int $minAmt
2675 * @param string $type
2677 * @param array $options
2681 * @throws \CRM_Core_Exception
2683 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2684 // creating price set, price field
2685 $paramsSet['title'] = 'Price Set';
2686 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2687 $paramsSet['is_active'] = FALSE;
2688 $paramsSet['extends'] = 1;
2689 $paramsSet['min_amount'] = $minAmt;
2691 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2692 $this->_ids
['price_set'] = $priceSet->id
;
2695 'label' => 'Price Field',
2696 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2697 'html_type' => $type,
2698 'price' => $feeTotal,
2699 'option_label' => ['1' => 'Price Field'],
2700 'option_value' => ['1' => $feeTotal],
2701 'option_name' => ['1' => $feeTotal],
2702 'option_weight' => ['1' => 1],
2703 'option_amount' => ['1' => 1],
2704 'is_display_amounts' => 1,
2706 'options_per_line' => 1,
2707 'is_active' => ['1' => 1],
2708 'price_set_id' => $this->_ids
['price_set'],
2709 'is_enter_qty' => 1,
2710 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2712 if ($type === 'Radio') {
2713 foreach ($options as $index => $option) {
2714 $paramsField['is_enter_qty'] = 0;
2715 $optionID = $index +
2;
2716 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2717 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2721 $this->callAPISuccess('PriceField', 'create', $paramsField);
2722 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2723 $this->_ids
['price_field'] = array_keys($fields['values']);
2724 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2725 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2727 return $this->_ids
['price_set'];
2731 * Add a profile to a contribution page.
2733 * @param string $name
2734 * @param int $contributionPageID
2735 * @param string $module
2737 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2739 'uf_group_id' => $name,
2740 'module' => $module,
2741 'entity_table' => 'civicrm_contribution_page',
2742 'entity_id' => $contributionPageID,
2745 if ($module !== 'CiviContribute') {
2746 $params['module_data'] = [$module => []];
2748 $this->callAPISuccess('UFJoin', 'create', $params);
2752 * Add participant with contribution
2756 * @throws \CRM_Core_Exception
2758 protected function createPartiallyPaidParticipantOrder() {
2759 $orderParams = $this->getParticipantOrderParams();
2760 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2761 return $this->callAPISuccess('Order', 'create', $orderParams);
2767 * @param string $component
2768 * @param int $componentId
2769 * @param array $priceFieldOptions
2773 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2774 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2775 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2776 $paramsSet['is_active'] = TRUE;
2777 $paramsSet['financial_type_id'] = 'Event Fee';
2778 $paramsSet['extends'] = 1;
2779 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2780 $priceSetId = $priceSet['id'];
2781 //Checking for priceset added in the table.
2782 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2783 'id', $paramsSet['title'], 'Check DB for created priceset'
2785 $paramsField = array_merge([
2786 'label' => 'Price Field',
2787 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2788 'html_type' => 'CheckBox',
2789 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2790 'option_value' => ['1' => 100, '2' => 200],
2791 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2792 'option_weight' => ['1' => 1, '2' => 2],
2793 'option_amount' => ['1' => 100, '2' => 200],
2794 'is_display_amounts' => 1,
2796 'options_per_line' => 1,
2797 'is_active' => ['1' => 1, '2' => 1],
2798 'price_set_id' => $priceSet['id'],
2799 'is_enter_qty' => 1,
2800 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2801 ], $priceFieldOptions);
2803 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2805 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSetId);
2807 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2811 * Replace the template with a test-oriented template designed to show all the variables.
2813 * @param string $templateName
2814 * @param string $type
2816 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2817 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2818 CRM_Core_DAO
::executeQuery(
2819 "UPDATE civicrm_msg_template
2820 SET msg_{$type} = %1
2821 WHERE workflow_name = '{$templateName}'
2822 AND is_default = 1", [1 => [$testTemplate, 'String']]
2827 * Reinstate the default template.
2829 * @param string $templateName
2830 * @param string $type
2832 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2833 CRM_Core_DAO
::executeQuery(
2834 "UPDATE civicrm_option_group og
2835 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2836 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2837 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2838 SET m.msg_{$type} = m2.msg_{$type}
2839 WHERE og.name = 'msg_tpl_workflow_contribution'
2840 AND ov.name = '{$templateName}'
2841 AND m.is_default = 1"
2846 * Flush statics relating to financial type.
2848 protected function flushFinancialTypeStatics() {
2849 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2850 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2852 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2853 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2855 CRM_Contribute_PseudoConstant
::flush('financialType');
2856 CRM_Contribute_PseudoConstant
::flush('membershipType');
2857 // Pseudoconstants may be saved to the cache table.
2858 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2859 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2860 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2864 * Set the permissions to the supplied array.
2866 * @param array $permissions
2868 protected function setPermissions($permissions) {
2869 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2870 $this->flushFinancialTypeStatics();
2874 * @param array $params
2877 public function _checkFinancialRecords($params, $context) {
2879 'entity_id' => $params['id'],
2880 'entity_table' => 'civicrm_contribution',
2882 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2883 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2884 if ($context == 'pending') {
2885 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2886 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2889 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2891 'id' => $trxn['financial_trxn_id'],
2893 if ($context != 'online' && $context != 'payLater') {
2895 'to_financial_account_id' => 6,
2896 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2900 if ($context == 'feeAmount') {
2901 $compareParams['fee_amount'] = 50;
2903 elseif ($context == 'online') {
2905 'to_financial_account_id' => 12,
2906 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2908 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2911 elseif ($context == 'payLater') {
2913 'to_financial_account_id' => 7,
2914 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2918 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2920 'financial_trxn_id' => $trxn['financial_trxn_id'],
2921 'entity_table' => 'civicrm_financial_item',
2923 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2925 'id' => $entityTrxn['entity_id'],
2928 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2930 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2932 if ($context == 'payLater') {
2934 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2936 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2939 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2940 if ($context == 'feeAmount') {
2942 'entity_id' => $params['id'],
2943 'entity_table' => 'civicrm_contribution',
2945 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2947 'id' => $maxTrxn['financial_trxn_id'],
2950 'to_financial_account_id' => 5,
2951 'from_financial_account_id' => 6,
2952 'total_amount' => 50,
2955 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2956 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2958 'entity_id' => $trxnId['financialTrxnId'],
2959 'entity_table' => 'civicrm_financial_trxn',
2964 'financial_account_id' => 5,
2966 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2968 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2969 // line should be copied into all the functions that call this function & evaluated there
2970 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2971 // when calling completeTransaction or repeatTransaction.
2972 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2976 * Return financial type id on basis of name
2978 * @param string $name Financial type m/c name
2982 public function getFinancialTypeId($name) {
2983 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2987 * Cleanup function for contents of $this->ids.
2989 * This is a best effort cleanup to use in tear downs etc.
2991 * It will not fail if the data has already been removed (some tests may do
2992 * their own cleanup).
2994 protected function cleanUpSetUpIDs() {
2995 foreach ($this->setupIDs
as $entity => $id) {
2997 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
2999 catch (CiviCRM_API3_Exception
$e) {
3000 // This is a best-effort cleanup function, ignore.
3006 * Create Financial Type.
3008 * @param array $params
3012 protected function createFinancialType($params = []) {
3013 $params = array_merge($params,
3015 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
3019 return $this->callAPISuccess('FinancialType', 'create', $params);
3023 * Create Payment Instrument.
3025 * @param array $params
3026 * @param string $financialAccountName
3030 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3031 $params = array_merge([
3032 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3033 'option_group_id' => 'payment_instrument',
3036 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3038 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3040 $financialAccountParams = [
3041 'entity_table' => 'civicrm_option_value',
3042 'entity_id' => $newPaymentInstrument,
3043 'account_relationship' => $relationTypeID,
3044 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3046 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3048 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3052 * Enable Tax and Invoicing
3054 * @param array $params
3056 * @return \Civi\Core\SettingsBag
3058 protected function enableTaxAndInvoicing($params = []) {
3059 // Enable component contribute setting
3060 $contributeSetting = array_merge($params,
3063 'invoice_prefix' => 'INV_',
3065 'due_date_period' => 'days',
3067 'is_email_pdf' => 1,
3068 'tax_term' => 'Sales Tax',
3069 'tax_display_settings' => 'Inclusive',
3072 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3076 * Enable Tax and Invoicing
3078 * @throws \CRM_Core_Exception
3080 protected function disableTaxAndInvoicing() {
3081 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3082 foreach ($accounts as $account) {
3083 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3084 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3087 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3088 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3090 return Civi
::settings()->set('invoicing', FALSE);
3094 * Add Sales Tax relation for financial type with financial account.
3096 * @param int $financialTypeId
3100 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
3102 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3103 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3104 'is_deductible' => 1,
3109 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3111 'entity_table' => 'civicrm_financial_type',
3112 'entity_id' => $financialTypeId,
3113 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3116 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3117 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3119 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3120 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3121 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3122 $dao->copyValues($entityParams);
3124 if ($dao->fetch()) {
3125 $entityParams['id'] = $dao->id
;
3127 $entityParams['financial_account_id'] = $account->id
;
3129 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3133 * Create price set with contribution test for test setup.
3135 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3136 * on parent class at some point (fn is not in 4.4).
3139 * @param array $params
3141 public function createPriceSetWithPage($entity = NULL, $params = []) {
3142 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3143 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3144 'title' => "Test Contribution Page",
3145 'financial_type_id' => 1,
3146 'currency' => 'NZD',
3147 'goal_amount' => 50,
3148 'is_pay_later' => 1,
3149 'is_monetary' => TRUE,
3150 'is_email_receipt' => FALSE,
3152 $priceSet = $this->callAPISuccess('price_set', 'create', [
3153 'is_quick_config' => 0,
3154 'extends' => 'CiviMember',
3155 'financial_type_id' => 1,
3156 'title' => 'my Page',
3158 $priceSetID = $priceSet['id'];
3160 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3161 $priceField = $this->callAPISuccess('price_field', 'create', [
3162 'price_set_id' => $priceSetID,
3163 'label' => 'Goat Breed',
3164 'html_type' => 'Radio',
3166 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3167 'price_set_id' => $priceSetID,
3168 'price_field_id' => $priceField['id'],
3169 'label' => 'Long Haired Goat',
3171 'financial_type_id' => 'Donation',
3172 'membership_type_id' => $membershipTypeID,
3173 'membership_num_terms' => 1,
3175 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3176 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3177 'price_set_id' => $priceSetID,
3178 'price_field_id' => $priceField['id'],
3179 'label' => 'Shoe-eating Goat',
3181 'financial_type_id' => 'Donation',
3182 'membership_type_id' => $membershipTypeID,
3183 'membership_num_terms' => 2,
3185 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3187 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3188 'price_set_id' => $priceSetID,
3189 'price_field_id' => $priceField['id'],
3190 'label' => 'Shoe-eating Goat',
3192 'financial_type_id' => 'Donation',
3194 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3196 $this->_ids
['price_set'] = $priceSetID;
3197 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3198 $this->_ids
['price_field'] = [$priceField['id']];
3200 $this->_ids
['membership_type'] = $membershipTypeID;
3204 * Only specified contact returned.
3206 * @implements CRM_Utils_Hook::aclWhereClause
3210 * @param $whereTables
3214 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3215 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3219 * @implements CRM_Utils_Hook::selectWhereClause
3221 * @param string $entity
3222 * @param array $clauses
3224 public function selectWhereClauseHook($entity, &$clauses) {
3225 if ($entity == 'Event') {
3226 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3231 * An implementation of hook_civicrm_post used with all our test cases.
3234 * @param string $objectName
3235 * @param int $objectId
3238 public function onPost($op, $objectName, $objectId, &$objectRef) {
3239 if ($op == 'create' && $objectName == 'Individual') {
3240 CRM_Core_DAO
::executeQuery(
3241 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3243 1 => [$objectId, 'Integer'],
3248 if ($op == 'edit' && $objectName == 'Participant') {
3250 1 => [$objectId, 'Integer'],
3252 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3253 CRM_Core_DAO
::executeQuery($query, $params);
3258 * Instantiate form object.
3260 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3262 * @param string $class
3263 * Name of form class.
3265 * @param array $formValues
3267 * @param string $pageName
3269 * @return \CRM_Core_Form
3270 * @throws \CRM_Core_Exception
3272 public function getFormObject($class, $formValues = [], $pageName = '') {
3273 $_POST = $formValues;
3274 /* @var CRM_Core_Form $form */
3275 $form = new $class();
3276 $_SERVER['REQUEST_METHOD'] = 'GET';
3278 case 'CRM_Event_Cart_Form_Checkout_Payment':
3279 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3280 $form->controller
= new CRM_Event_Cart_Controller_Checkout();
3284 $form->controller
= new CRM_Core_Controller();
3287 $pageName = $form->getName();
3289 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3290 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3295 * Get possible thousand separators.
3299 public function getThousandSeparators() {
3300 return [['.'], [',']];
3304 * Get the boolean options as a provider.
3308 public function getBooleanDataProvider() {
3309 return [[TRUE], [FALSE]];
3313 * Set the separators for thousands and decimal points.
3315 * Note that this only covers some common scenarios.
3317 * It does not cater for a situation where the thousand separator is a [space]
3318 * Latter is the Norwegian localization. At least some tests need to
3319 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3320 * to provide broader coverage.
3322 * @param string $thousandSeparator
3324 protected function setCurrencySeparators($thousandSeparator) {
3325 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3326 Civi
::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3330 * Sets the thousand separator.
3332 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3334 * @param $thousandSeparator
3336 protected function setMonetaryThousandSeparator($thousandSeparator) {
3337 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3341 * Sets the decimal separator.
3343 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3345 * @param $decimalPoint
3347 protected function setMonetaryDecimalPoint($decimalPoint) {
3348 Civi
::settings()->set('monetaryDecimalPoint', $decimalPoint);
3352 * Sets the default currency.
3356 protected function setDefaultCurrency($currency) {
3357 Civi
::settings()->set('defaultCurrency', $currency);
3361 * Format money as it would be input.
3363 * @param string $amount
3367 protected function formatMoneyInput($amount) {
3368 return CRM_Utils_Money
::format($amount, NULL, '%a');
3372 * Get the contribution object.
3374 * @param int $contributionID
3376 * @return \CRM_Contribute_BAO_Contribution
3378 protected function getContributionObject($contributionID) {
3379 $contributionObj = new CRM_Contribute_BAO_Contribution();
3380 $contributionObj->id
= $contributionID;
3381 $contributionObj->find(TRUE);
3382 return $contributionObj;
3386 * Enable multilingual.
3388 public function enableMultilingual() {
3389 $this->callAPISuccess('Setting', 'create', [
3390 'lcMessages' => 'en_US',
3391 'languageLimit' => [
3396 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3399 $dbLocale = '_en_US';
3403 * Setup or clean up SMS tests
3405 * @param bool $teardown
3407 * @throws \CiviCRM_API3_Exception
3409 public function setupForSmsTests($teardown = FALSE) {
3410 require_once 'CiviTest/CiviTestSMSProvider.php';
3412 // Option value params for CiviTestSMSProvider
3413 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3415 'option_group_id' => $groupID,
3416 'label' => 'unittestSMS',
3417 'value' => 'unit.test.sms',
3418 'name' => 'CiviTestSMSProvider',
3425 // Test completed, delete provider
3426 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3427 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3431 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3432 // in CRM/Sms/Provider.php even though it is not an extension.
3433 return civicrm_api3('option_value', 'create', $params);
3437 * Start capturing browser output.
3439 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3441 protected function startCapturingOutput() {
3443 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3447 * Stop capturing browser output and return as a csv.
3449 * @param bool $isFirstRowHeaders
3451 * @return \League\Csv\Reader
3453 * @throws \League\Csv\Exception
3455 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3456 $output = ob_get_flush();
3457 $stream = fopen('php://memory', 'r+');
3458 fwrite($stream, $output);
3460 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3461 $csv = Reader
::createFromString($output);
3462 if ($isFirstRowHeaders) {
3463 $csv->setHeaderOffset(0);
3470 * Rename various labels to not match the names.
3472 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3473 * code that expects it to.
3475 protected function renameLabels() {
3476 $replacements = ['Pending', 'Refunded'];
3477 foreach ($replacements as $name) {
3478 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3483 * Undo any label renaming.
3485 protected function resetLabels() {
3486 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3490 * Get parameters to set up a multi-line participant order.
3493 * @throws \CRM_Core_Exception
3495 protected function getParticipantOrderParams(): array {
3496 $this->_contactId
= $this->individualCreate();
3497 $event = $this->eventCreate();
3498 $this->_eventId
= $event['id'];
3500 'id' => $this->_eventId
,
3501 'financial_type_id' => 4,
3504 $this->callAPISuccess('event', 'create', $eventParams);
3505 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3506 $participantParams = [
3507 'financial_type_id' => 4,
3508 'event_id' => $this->_eventId
,
3511 'fee_currency' => 'USD',
3512 'contact_id' => $this->_contactId
,
3514 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3516 'total_amount' => 300,
3517 'currency' => 'USD',
3518 'contact_id' => $this->_contactId
,
3519 'financial_type_id' => 4,
3520 'contribution_status_id' => 'Pending',
3521 'contribution_mode' => 'participant',
3522 'participant_id' => $participant['id'],
3524 foreach ($priceFields['values'] as $key => $priceField) {
3525 $orderParams['line_items'][] = [
3528 'price_field_id' => $priceField['price_field_id'],
3529 'price_field_value_id' => $priceField['id'],
3530 'label' => $priceField['label'],
3531 'field_title' => $priceField['label'],
3533 'unit_price' => $priceField['amount'],
3534 'line_total' => $priceField['amount'],
3535 'financial_type_id' => $priceField['financial_type_id'],
3536 'entity_table' => 'civicrm_participant',
3539 'params' => $participant,
3542 return $orderParams;
3548 * @throws \CRM_Core_Exception
3550 protected function validatePayments($payments) {
3551 foreach ($payments as $payment) {
3552 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3553 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3554 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3555 // This is a pretty rough way at guessing which payment is the overpayment - but
3556 // for the test suite it should be enough.
3559 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3560 'financial_trxn_id' => $payment['id'],
3561 'entity_table' => 'civicrm_financial_item',
3562 'return' => ['amount'],
3565 foreach ($items as $item) {
3566 $itemTotal +
= $item['amount'];
3568 $this->assertEquals($payment['total_amount'], $itemTotal);
3573 * Validate all created payments.
3575 * @throws \CRM_Core_Exception
3577 protected function validateAllPayments() {
3578 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3579 $this->validatePayments($payments);
3583 * Validate all created contributions.
3585 * @throws \CRM_Core_Exception
3587 protected function validateAllContributions() {
3588 $contributions = $this->callAPISuccess('Contribution', 'get', ['return' => ['tax_amount', 'total_amount']])['values'];
3589 foreach ($contributions as $contribution) {
3590 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3593 foreach ($lineItems as $lineItem) {
3594 $total +
= $lineItem['line_total'];
3595 $taxTotal +
= (float) ($lineItem['tax_amount'] ??
0);
3597 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ??
0));
3598 $this->assertEquals($total, $contribution['total_amount']);
3604 * @throws \CRM_Core_Exception
3606 protected function createRuleGroup() {
3607 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3608 'contact_type' => 'Individual',
3610 'used' => 'General',
3611 'name' => 'TestRule',
3612 'title' => 'TestRule',
3619 * Generic create test.
3621 * @param int $version
3623 * @throws \CRM_Core_Exception
3625 protected function basicCreateTest(int $version) {
3626 $this->_apiversion
= $version;
3627 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
3628 $this->assertEquals(1, $result['count']);
3629 $this->assertNotNull($result['values'][$result['id']]['id']);
3630 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
3634 * Generic delete test.
3636 * @param int $version
3638 * @throws \CRM_Core_Exception
3640 protected function basicDeleteTest($version) {
3641 $this->_apiversion
= $version;
3642 $result = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
3643 $deleteParams = ['id' => $result['id']];
3644 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
3645 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
3646 $this->assertEquals(0, $checkDeleted['count']);
3650 * Create and return a case object for the given Client ID.
3652 * @param int $clientId
3653 * @param int $loggedInUser
3654 * Omit or pass NULL to use the same as clientId
3655 * @param array $extra
3656 * Optional specific parameters such as start_date
3658 * @return CRM_Case_BAO_Case
3660 public function createCase($clientId, $loggedInUser = NULL, $extra = NULL) {
3661 if (empty($loggedInUser)) {
3662 // backwards compatibility - but it's more typical that the creator is a different person than the client
3663 $loggedInUser = $clientId;
3666 'activity_subject' => 'Case Subject',
3667 'client_id' => $clientId,
3668 'case_type_id' => 1,
3670 'case_type' => 'housing_support',
3671 'subject' => 'Case Subject',
3672 'start_date' => ($extra['start_date'] ??
date("Y-m-d")),
3673 'start_date_time' => ($extra['start_date_time'] ??
date("YmdHis")),
3675 'activity_details' => '',
3677 $form = new CRM_Case_Form_Case();
3678 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3682 * Validate that all location entities have exactly one primary.
3684 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3686 public function assertLocationValidity() {
3687 $this->assertEquals(0, CRM_Core_DAO
::singleValueQuery('SELECT COUNT(*) FROM
3689 (SELECT a1.contact_id
3690 FROM civicrm_address a1
3691 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3692 AND a1.contact_id = a2.contact_id
3695 AND a2.id IS NOT NULL
3696 AND a1.contact_id IS NOT NULL
3698 SELECT a1.contact_id
3699 FROM civicrm_address a1
3700 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3701 AND a1.contact_id = a2.contact_id
3702 WHERE a1.is_primary = 0
3704 AND a1.contact_id IS NOT NULL
3708 SELECT a1.contact_id
3709 FROM civicrm_email a1
3710 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3711 AND a1.contact_id = a2.contact_id
3714 AND a2.id IS NOT NULL
3715 AND a1.contact_id IS NOT NULL
3717 SELECT a1.contact_id
3718 FROM civicrm_email a1
3719 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3720 AND a1.contact_id = a2.contact_id
3721 WHERE a1.is_primary = 0
3723 AND a1.contact_id IS NOT NULL
3727 SELECT a1.contact_id
3728 FROM civicrm_phone a1
3729 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3730 AND a1.contact_id = a2.contact_id
3733 AND a2.id IS NOT NULL
3734 AND a1.contact_id IS NOT NULL
3736 SELECT a1.contact_id
3737 FROM civicrm_phone a1
3738 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3739 AND a1.contact_id = a2.contact_id
3740 WHERE a1.is_primary = 0
3742 AND a1.contact_id IS NOT NULL
3746 SELECT a1.contact_id
3748 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3749 AND a1.contact_id = a2.contact_id
3752 AND a2.id IS NOT NULL
3753 AND a1.contact_id IS NOT NULL
3755 SELECT a1.contact_id
3757 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3758 AND a1.contact_id = a2.contact_id
3759 WHERE a1.is_primary = 0
3761 AND a1.contact_id IS NOT NULL
3765 SELECT a1.contact_id
3766 FROM civicrm_openid a1
3767 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3768 AND a1.contact_id = a2.contact_id
3769 WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3772 SELECT a1.contact_id
3773 FROM civicrm_openid a1
3774 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3775 AND a1.contact_id = a2.contact_id
3778 AND a2.id IS NOT NULL
3779 AND a1.contact_id IS NOT NULL
3781 SELECT a1.contact_id
3782 FROM civicrm_openid a1
3783 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3784 AND a1.contact_id = a2.contact_id
3785 WHERE a1.is_primary = 0
3787 AND a1.contact_id IS NOT NULL) as primary_descrepancies