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 * Class used for hooks during tests.
140 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
142 * $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
144 * @var \CRM_Utils_Hook_UnitTests
146 public $hookClass = NULL;
150 * Common values to be re-used multiple times within a class - usually to create the relevant entity
152 protected $_params = [];
155 * @var CRM_Extension_System
157 protected $origExtensionSystem;
160 * Array of IDs created during test setup routine.
162 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
166 public $setupIDs = [];
171 * Because we are overriding the parent class constructor, we
172 * need to show the same arguments as exist in the constructor of
173 * PHPUnit_Framework_TestCase, since
174 * PHPUnit_Framework_TestSuite::createTest() creates a
175 * ReflectionClass of the Test class and checks the constructor
176 * of that class to decide how to set up the test.
178 * @param string $name
180 * @param string $dataName
182 public function __construct($name = NULL, array $data = [], $dataName = '') {
183 parent
::__construct($name, $data, $dataName);
185 // we need full error reporting
186 error_reporting(E_ALL
& ~E_NOTICE
);
188 self
::$_dbName = self
::getDBName();
190 // also load the class loader
191 require_once 'CRM/Core/ClassLoader.php';
192 CRM_Core_ClassLoader
::singleton()->register();
193 if (function_exists('_civix_phpunit_setUp')) {
194 // FIXME: loosen coupling
195 _civix_phpunit_setUp();
200 * Override to run the test and assert its state.
204 * @throws \PHPUnit_Framework_IncompleteTest
205 * @throws \PHPUnit_Framework_SkippedTest
207 protected function runTest() {
209 return parent
::runTest();
211 catch (PEAR_Exception
$e) {
212 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
213 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
220 public function requireDBReset() {
221 return $this->DBResetRequired
;
227 public static function getDBName() {
228 static $dbName = NULL;
229 if ($dbName === NULL) {
230 require_once "DB.php";
231 $dsninfo = DB
::parseDSN(CIVICRM_DSN
);
232 $dbName = $dsninfo['database'];
238 * Create database connection for this instance.
240 * Initialize the test database if it hasn't been initialized
243 protected function getConnection() {
244 if (!self
::$dbInit) {
245 $dbName = self
::getDBName();
247 // install test database
248 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
250 static::_populateDB(FALSE, $this);
252 self
::$dbInit = TRUE;
258 * Required implementation of abstract method.
260 protected function getDataSet() {
264 * @param bool $perClass
265 * @param null $object
268 * TRUE if the populate logic runs; FALSE if it is skipped
270 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
271 if (CIVICRM_UF
!== 'UnitTests') {
272 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
275 if ($perClass ||
$object == NULL) {
279 $dbreset = $object->requireDBReset();
282 if (self
::$populateOnce ||
!$dbreset) {
285 self
::$populateOnce = NULL;
287 Civi\Test
::data()->populate();
292 public static function setUpBeforeClass() {
293 static::_populateDB(TRUE);
295 // also set this global hack
296 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
300 * Common setup functions for all unit tests.
302 protected function setUp() {
303 $session = CRM_Core_Session
::singleton();
304 $session->set('userID', NULL);
306 $this->_apiversion
= 3;
309 $this->errorScope
= CRM_Core_TemporaryErrorScope
::useException();
310 // Use a temporary file for STDIN
311 $GLOBALS['stdin'] = tmpfile();
312 if ($GLOBALS['stdin'] === FALSE) {
313 echo "Couldn't open temporary file\n";
317 // Get and save a connection to the database
318 $this->_dbconn
= $this->getConnection();
320 // reload database before each test
321 // $this->_populateDB();
323 // "initialize" CiviCRM to avoid problems when running single tests
324 // FIXME: look at it closer in second stage
326 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
327 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
329 // disable any left-over test extensions
330 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
332 // reset all the caches
333 CRM_Utils_System
::flushCache();
335 // initialize the object once db is loaded
336 \Civi
::$statics = [];
338 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
340 // when running unit tests, use mockup user framework
341 $this->hookClass
= CRM_Utils_Hook
::singleton();
343 // Make sure the DB connection is setup properly
344 $config->userSystem
->setMySQLTimeZone();
345 $env = new CRM_Utils_Check_Component_Env();
346 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
348 // clear permissions stub to not check permissions
349 $config->userPermissionClass
->permissions
= NULL;
351 //flush component settings
352 CRM_Core_Component
::getEnabledComponents(TRUE);
354 $_REQUEST = $_GET = $_POST = [];
355 error_reporting(E_ALL
);
357 $this->renameLabels();
358 $this->_sethtmlGlobals();
362 * Read everything from the datasets directory and insert into the db.
364 public function loadAllFixtures() {
365 $fixturesDir = __DIR__
. '/../../fixtures';
367 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
369 $jsonFiles = glob($fixturesDir . '/*.json');
370 foreach ($jsonFiles as $jsonFixture) {
371 $json = json_decode(file_get_contents($jsonFixture));
372 foreach ($json as $tableName => $vars) {
373 if ($tableName === 'civicrm_contact') {
374 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');
377 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
379 foreach ($vars as $entity) {
380 $keys = $values = [];
381 foreach ($entity as $key => $value) {
383 $values[] = is_numeric($value) ?
$value : "'{$value}'";
385 CRM_Core_DAO
::executeQuery("
386 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
393 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
397 * Load the data that used to be handled by the discontinued dbunit class.
399 * This could do with further tidy up - the initial priority is simply to get rid of
400 * the dbunity package which is no longer supported.
402 * @param string $fileName
404 protected function loadXMLDataSet($fileName) {
405 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
406 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
407 foreach ($xml as $tableName => $element) {
408 if (!empty($element)) {
409 foreach ($element as $row) {
410 $keys = $values = [];
411 if (isset($row['@attributes'])) {
412 foreach ($row['@attributes'] as $key => $value) {
414 $values[] = is_numeric($value) ?
$value : "'{$value}'";
417 elseif (!empty($row)) {
418 // cos we copied it & it is inconsistent....
419 foreach ($row as $key => $value) {
421 $values[] = is_numeric($value) ?
$value : "'{$value}'";
425 if (!empty($values)) {
426 CRM_Core_DAO
::executeQuery("
427 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
433 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
437 * Create default domain contacts for the two domains added during test class.
438 * database population.
440 public function createDomainContacts() {
441 $this->organizationCreate();
442 $this->organizationCreate(['organization_name' => 'Second Domain']);
446 * Common teardown functions for all unit tests.
448 protected function tearDown() {
449 $this->_apiversion
= 3;
450 $this->resetLabels();
452 error_reporting(E_ALL
& ~E_NOTICE
);
453 CRM_Utils_Hook
::singleton()->reset();
454 if ($this->hookClass
) {
455 $this->hookClass
->reset();
457 CRM_Core_Session
::singleton()->reset(1);
460 $this->tx
->rollback()->commit();
463 CRM_Core_Transaction
::forceRollbackIfEnabled();
464 \Civi\Core\Transaction\Manager
::singleton(TRUE);
467 CRM_Core_Transaction
::forceRollbackIfEnabled();
468 \Civi\Core\Transaction\Manager
::singleton(TRUE);
470 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match'];
471 $this->quickCleanup($tablesToTruncate);
472 $this->createDomainContacts();
475 $this->cleanTempDirs();
476 $this->unsetExtensionSystem();
477 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
478 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
482 * Create a batch of external API calls which can
483 * be executed concurrently.
486 * $calls = $this->createExternalAPI()
487 * ->addCall('Contact', 'get', ...)
488 * ->addCall('Contact', 'get', ...)
494 * @return \Civi\API\ExternalBatch
495 * @throws PHPUnit_Framework_SkippedTestError
497 public function createExternalAPI() {
498 global $civicrm_root;
500 'version' => $this->_apiversion
,
504 $calls = new \Civi\API\
ExternalBatch($defaultParams);
506 if (!$calls->isSupported()) {
507 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
514 * Create required data based on $this->entity & $this->params
515 * This is just a way to set up the test data for delete & get functions
516 * so the distinction between set
517 * up & tested functions is clearer
522 public function createTestEntity() {
523 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
527 * @param int $contactTypeId
531 public function contactTypeDelete($contactTypeId) {
532 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
534 throw new Exception('Could not delete contact type');
539 * @param array $params
543 public function membershipTypeCreate($params = []) {
544 CRM_Member_PseudoConstant
::flush('membershipType');
545 CRM_Core_Config
::clearDBCache();
546 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
547 $params = array_merge([
549 'duration_unit' => 'year',
550 'duration_interval' => 1,
551 'period_type' => 'rolling',
552 'member_of_contact_id' => $memberOfOrganization,
554 'financial_type_id' => 2,
557 'visibility' => 'Public',
560 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
562 CRM_Member_PseudoConstant
::flush('membershipType');
563 CRM_Utils_Cache
::singleton()->flush();
565 return $result['id'];
571 * @param array $params
574 * @throws \CRM_Core_Exception
576 public function contactMembershipCreate($params) {
577 $params = array_merge([
578 'join_date' => '2007-01-21',
579 'start_date' => '2007-01-21',
580 'end_date' => '2007-12-21',
581 'source' => 'Payment',
582 'membership_type_id' => 'General',
584 if (!is_numeric($params['membership_type_id'])) {
585 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
586 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
587 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
591 $result = $this->callAPISuccess('Membership', 'create', $params);
592 return $result['id'];
596 * Delete Membership Type.
598 * @param array $params
600 public function membershipTypeDelete($params) {
601 $this->callAPISuccess('MembershipType', 'Delete', $params);
605 * @param int $membershipID
607 public function membershipDelete($membershipID) {
608 $deleteParams = ['id' => $membershipID];
609 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
613 * @param string $name
617 * @throws \CRM_Core_Exception
619 public function membershipStatusCreate($name = 'test member status') {
620 $params['name'] = $name;
621 $params['start_event'] = 'start_date';
622 $params['end_event'] = 'end_date';
623 $params['is_current_member'] = 1;
624 $params['is_active'] = 1;
626 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
627 CRM_Member_PseudoConstant
::flush('membershipStatus');
628 return (int) $result['id'];
632 * Delete the given membership status, deleting any memberships of the status first.
634 * @param int $membershipStatusID
636 * @throws \CRM_Core_Exception
638 public function membershipStatusDelete(int $membershipStatusID) {
639 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
640 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
643 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
644 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
645 $renewedMembershipEndDate = new DateTime($membershipEndDate);
646 switch ($durationUnit) {
648 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
652 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
653 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
654 $renewedMembershipEndDate->add(new DateInterval('P1D'));
655 $renewedMembershipEndDate->add(new DateInterval('P1M'));
656 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
659 return $renewedMembershipEndDate->format('Y-m-d');
663 * Create a relationship type.
665 * @param array $params
669 * @throws \CRM_Core_Exception
671 public function relationshipTypeCreate($params = []) {
672 $params = array_merge([
673 'name_a_b' => 'Relation 1 for relationship type create',
674 'name_b_a' => 'Relation 2 for relationship type create',
675 'contact_type_a' => 'Individual',
676 'contact_type_b' => 'Organization',
681 $result = $this->callAPISuccess('relationship_type', 'create', $params);
682 CRM_Core_PseudoConstant
::flush('relationshipType');
684 return $result['id'];
688 * Delete Relatinship Type.
690 * @param int $relationshipTypeID
692 public function relationshipTypeDelete($relationshipTypeID) {
693 $params['id'] = $relationshipTypeID;
694 $check = $this->callAPISuccess('relationship_type', 'get', $params);
695 if (!empty($check['count'])) {
696 $this->callAPISuccess('relationship_type', 'delete', $params);
701 * @param array $params
704 * @throws \CRM_Core_Exception
706 public function paymentProcessorTypeCreate($params = NULL) {
707 if (is_null($params)) {
709 'name' => 'API_Test_PP',
710 'title' => 'API Test Payment Processor',
711 'class_name' => 'CRM_Core_Payment_APITest',
712 'billing_mode' => 'form',
718 $result = $this->callAPISuccess('payment_processor_type', 'create', $params);
720 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
722 return $result['id'];
726 * Create test Authorize.net instance.
728 * @param array $params
732 public function paymentProcessorAuthorizeNetCreate($params = []) {
733 $params = array_merge([
734 'name' => 'Authorize',
735 'domain_id' => CRM_Core_Config
::domainID(),
736 'payment_processor_type_id' => 'AuthNet',
737 'title' => 'AuthNet',
742 'user_name' => '4y5BfuW7jm',
743 'password' => '4cAmW927n8uLf5J8',
744 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
745 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
746 'class_name' => 'Payment_AuthorizeNet',
750 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
751 return $result['id'];
755 * Create Participant.
757 * @param array $params
758 * Array of contact id and event id values.
761 * $id of participant created
763 public function participantCreate($params = []) {
764 if (empty($params['contact_id'])) {
765 $params['contact_id'] = $this->individualCreate();
767 if (empty($params['event_id'])) {
768 $event = $this->eventCreate();
769 $params['event_id'] = $event['id'];
774 'register_date' => 20070219,
775 'source' => 'Wimbeldon',
776 'event_level' => 'Payment',
780 $params = array_merge($defaults, $params);
781 $result = $this->callAPISuccess('Participant', 'create', $params);
782 return $result['id'];
786 * Create Payment Processor.
789 * Id Payment Processor
791 public function processorCreate($params = []) {
795 'payment_processor_type_id' => 'Dummy',
796 'financial_account_id' => 12,
800 'url_site' => 'http://dummy.com',
801 'url_recur' => 'http://dummy.com',
804 'payment_instrument_id' => 'Debit Card',
806 $processorParams = array_merge($processorParams, $params);
807 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
808 return $processor['id'];
812 * Create Payment Processor.
814 * @param array $processorParams
816 * @return \CRM_Core_Payment_Dummy
817 * Instance of Dummy Payment Processor
819 * @throws \CiviCRM_API3_Exception
821 public function dummyProcessorCreate($processorParams = []) {
822 $paymentProcessorID = $this->processorCreate($processorParams);
823 // 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
824 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
825 $processorParams['is_test'] = FALSE;
826 $this->processorCreate($processorParams);
827 return System
::singleton()->getById($paymentProcessorID);
831 * Create contribution page.
833 * @param array $params
836 * Array of contribution page
838 public function contributionPageCreate($params = []) {
839 $this->_pageParams
= array_merge([
840 'title' => 'Test Contribution Page',
841 'financial_type_id' => 1,
843 'financial_account_id' => 1,
845 'is_allow_other_amount' => 1,
847 'max_amount' => 1000,
849 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
853 * Create a sample batch.
855 public function batchCreate() {
856 $params = $this->_params
;
857 $params['name'] = $params['title'] = 'Batch_433397';
858 $params['status_id'] = 1;
859 $result = $this->callAPISuccess('batch', 'create', $params);
860 return $result['id'];
866 * @param array $params
869 * result of created tag
871 public function tagCreate($params = []) {
873 'name' => 'New Tag3',
874 'description' => 'This is description for Our New Tag ',
877 $params = array_merge($defaults, $params);
878 $result = $this->callAPISuccess('Tag', 'create', $params);
879 return $result['values'][$result['id']];
886 * Id of the tag to be deleted.
890 public function tagDelete($tagId) {
891 require_once 'api/api.php';
895 $result = $this->callAPISuccess('Tag', 'delete', $params);
896 return $result['id'];
900 * Add entity(s) to the tag
902 * @param array $params
906 public function entityTagAdd($params) {
907 $result = $this->callAPISuccess('entity_tag', 'create', $params);
914 * @param array $params
918 * id of created pledge
920 * @throws \CRM_Core_Exception
922 public function pledgeCreate($params) {
923 $params = array_merge([
924 'pledge_create_date' => date('Ymd'),
925 'start_date' => date('Ymd'),
926 'scheduled_date' => date('Ymd'),
928 'pledge_status_id' => '2',
929 'financial_type_id' => '1',
930 'pledge_original_installment_amount' => 20,
931 'frequency_interval' => 5,
932 'frequency_unit' => 'year',
933 'frequency_day' => 15,
938 $result = $this->callAPISuccess('Pledge', 'create', $params);
939 return $result['id'];
943 * Delete contribution.
945 * @param int $pledgeId
947 * @throws \CRM_Core_Exception
949 public function pledgeDelete($pledgeId) {
951 'pledge_id' => $pledgeId,
953 $this->callAPISuccess('Pledge', 'delete', $params);
957 * Create contribution.
959 * @param array $params
960 * Array of parameters.
963 * id of created contribution
964 * @throws \CRM_Core_Exception
966 public function contributionCreate($params) {
968 $params = array_merge([
970 'receive_date' => date('Ymd'),
971 'total_amount' => 100.00,
972 'fee_amount' => 5.00,
973 'financial_type_id' => 1,
974 'payment_instrument_id' => 1,
975 'non_deductible_amount' => 10.00,
977 'contribution_status_id' => 1,
980 $result = $this->callAPISuccess('contribution', 'create', $params);
981 return $result['id'];
985 * Delete contribution.
987 * @param int $contributionId
990 * @throws \CRM_Core_Exception
992 public function contributionDelete($contributionId) {
994 'contribution_id' => $contributionId,
996 $result = $this->callAPISuccess('contribution', 'delete', $params);
1003 * @param array $params
1004 * Name-value pair for an event.
1007 * @throws \CRM_Core_Exception
1009 public function eventCreate($params = []) {
1010 // if no contact was passed, make up a dummy event creator
1011 if (!isset($params['contact_id'])) {
1012 $params['contact_id'] = $this->_contactCreate([
1013 'contact_type' => 'Individual',
1014 'first_name' => 'Event',
1015 'last_name' => 'Creator',
1019 // set defaults for missing params
1020 $params = array_merge([
1021 'title' => 'Annual CiviCRM meet',
1022 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1023 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1024 'event_type_id' => 1,
1026 'start_date' => 20081021,
1027 'end_date' => 20081023,
1028 'is_online_registration' => 1,
1029 'registration_start_date' => 20080601,
1030 'registration_end_date' => 20081015,
1031 'max_participants' => 100,
1032 'event_full_text' => 'Sorry! We are already full',
1035 'is_show_location' => 0,
1036 'is_email_confirm' => 1,
1039 return $this->callAPISuccess('Event', 'create', $params);
1043 * Create a paid event.
1045 * @param array $params
1047 * @param array $options
1049 * @param string $key
1050 * Index for storing event ID in ids array.
1054 * @throws \CRM_Core_Exception
1056 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1057 $params['is_monetary'] = TRUE;
1058 $event = $this->eventCreate($params);
1059 $this->ids
['Event'][$key] = (int) $event['id'];
1060 $this->ids
['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1061 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->ids
['PriceSet'][$key]);
1062 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->ids
['PriceSet'][$key], TRUE, FALSE);
1063 $priceSet = $priceSet[$this->ids
['PriceSet'][$key]] ??
NULL;
1064 $this->eventFeeBlock
= $priceSet['fields'] ??
NULL;
1076 public function eventDelete($id) {
1080 return $this->callAPISuccess('event', 'delete', $params);
1084 * Delete participant.
1086 * @param int $participantID
1090 public function participantDelete($participantID) {
1092 'id' => $participantID,
1094 $check = $this->callAPISuccess('Participant', 'get', $params);
1095 if ($check['count'] > 0) {
1096 return $this->callAPISuccess('Participant', 'delete', $params);
1101 * Create participant payment.
1103 * @param int $participantID
1104 * @param int $contributionID
1107 * $id of created payment
1109 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1110 //Create Participant Payment record With Values
1112 'participant_id' => $participantID,
1113 'contribution_id' => $contributionID,
1116 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1117 return $result['id'];
1121 * Delete participant payment.
1123 * @param int $paymentID
1125 public function participantPaymentDelete($paymentID) {
1129 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1135 * @param int $contactID
1138 * location id of created location
1140 public function locationAdd($contactID) {
1143 'location_type' => 'New Location Type',
1145 'name' => 'Saint Helier St',
1146 'county' => 'Marin',
1147 'country' => 'UNITED STATES',
1148 'state_province' => 'Michigan',
1149 'supplemental_address_1' => 'Hallmark Ct',
1150 'supplemental_address_2' => 'Jersey Village',
1151 'supplemental_address_3' => 'My Town',
1156 'contact_id' => $contactID,
1157 'address' => $address,
1158 'location_format' => '2.0',
1159 'location_type' => 'New Location Type',
1162 $result = $this->callAPISuccess('Location', 'create', $params);
1167 * Delete Locations of contact.
1169 * @param array $params
1172 public function locationDelete($params) {
1173 $this->callAPISuccess('Location', 'delete', $params);
1177 * Add a Location Type.
1179 * @param array $params
1181 * @return CRM_Core_DAO_LocationType
1182 * location id of created location
1184 public function locationTypeCreate($params = NULL) {
1185 if ($params === NULL) {
1187 'name' => 'New Location Type',
1188 'vcard_name' => 'New Location Type',
1189 'description' => 'Location Type for Delete',
1194 $locationType = new CRM_Core_DAO_LocationType();
1195 $locationType->copyValues($params);
1196 $locationType->save();
1197 // clear getfields cache
1198 CRM_Core_PseudoConstant
::flush();
1199 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1200 return $locationType;
1204 * Delete a Location Type.
1206 * @param int $locationTypeId
1208 public function locationTypeDelete($locationTypeId) {
1209 $locationType = new CRM_Core_DAO_LocationType();
1210 $locationType->id
= $locationTypeId;
1211 $locationType->delete();
1217 * @param array $params
1219 * @return CRM_Core_DAO_Mapping
1220 * Mapping id of created mapping
1222 public function mappingCreate($params = NULL) {
1223 if ($params === NULL) {
1225 'name' => 'Mapping name',
1226 'description' => 'Mapping description',
1227 // 'Export Contact' mapping.
1228 'mapping_type_id' => 7,
1232 $mapping = new CRM_Core_DAO_Mapping();
1233 $mapping->copyValues($params);
1235 // clear getfields cache
1236 CRM_Core_PseudoConstant
::flush();
1237 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1244 * @param int $mappingId
1246 public function mappingDelete($mappingId) {
1247 $mapping = new CRM_Core_DAO_Mapping();
1248 $mapping->id
= $mappingId;
1253 * Prepare class for ACLs.
1255 protected function prepareForACLs() {
1256 $config = CRM_Core_Config
::singleton();
1257 $config->userPermissionClass
->permissions
= [];
1263 protected function cleanUpAfterACLs() {
1264 CRM_Utils_Hook
::singleton()->reset();
1265 $tablesToTruncate = [
1267 'civicrm_acl_cache',
1268 'civicrm_acl_entity_role',
1269 'civicrm_acl_contact_cache',
1271 $this->quickCleanup($tablesToTruncate);
1272 $config = CRM_Core_Config
::singleton();
1273 unset($config->userPermissionClass
->permissions
);
1277 * Create a smart group.
1279 * By default it will be a group of households.
1281 * @param array $smartGroupParams
1282 * @param array $groupParams
1283 * @param string $contactType
1287 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1288 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1289 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1291 $groupParams['saved_search_id'] = $savedSearch->id
;
1292 return $this->groupCreate($groupParams);
1298 * @param array $params
1300 public function uFFieldCreate($params = []) {
1301 $params = array_merge([
1303 'field_name' => 'first_name',
1306 'visibility' => 'Public Pages and Listings',
1307 'is_searchable' => '1',
1308 'label' => 'first_name',
1309 'field_type' => 'Individual',
1312 $this->callAPISuccess('uf_field', 'create', $params);
1316 * Add a UF Join Entry.
1318 * @param array $params
1321 * $id of created UF Join
1323 public function ufjoinCreate($params = NULL) {
1324 if ($params === NULL) {
1327 'module' => 'CiviEvent',
1328 'entity_table' => 'civicrm_event',
1334 $result = $this->callAPISuccess('uf_join', 'create', $params);
1339 * @param array $params
1340 * Optional parameters.
1341 * @param bool $reloadConfig
1342 * While enabling CiviCampaign component, we shouldn't always forcibly
1343 * reload config as this hinder hook call in test environment
1348 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1349 $this->enableCiviCampaign($reloadConfig);
1350 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1351 'name' => 'big_campaign',
1352 'title' => 'Campaign',
1354 return $campaign['id'];
1358 * Create Group for a contact.
1360 * @param int $contactId
1362 public function contactGroupCreate($contactId) {
1364 'contact_id.1' => $contactId,
1368 $this->callAPISuccess('GroupContact', 'Create', $params);
1372 * Delete Group for a contact.
1374 * @param int $contactId
1376 public function contactGroupDelete($contactId) {
1378 'contact_id.1' => $contactId,
1381 $this->civicrm_api('GroupContact', 'Delete', $params);
1387 * @param array $params
1391 * @throws \CRM_Core_Exception
1392 * @throws \CiviCRM_API3_Exception
1394 public function activityCreate($params = []) {
1395 $params = array_merge([
1396 'subject' => 'Discussion on warm beer',
1397 'activity_date_time' => date('Ymd'),
1399 'location' => 'Baker Street',
1400 'details' => 'Lets schedule a meeting',
1402 'activity_type_id' => 'Meeting',
1404 if (!isset($params['source_contact_id'])) {
1405 $params['source_contact_id'] = $this->individualCreate();
1407 if (!isset($params['target_contact_id'])) {
1408 $params['target_contact_id'] = $this->individualCreate([
1409 'first_name' => 'Julia',
1410 'last_name' => 'Anderson',
1412 'email' => 'julia_anderson@civicrm.org',
1413 'contact_type' => 'Individual',
1416 if (!isset($params['assignee_contact_id'])) {
1417 $params['assignee_contact_id'] = $params['target_contact_id'];
1420 $result = civicrm_api3('Activity', 'create', $params);
1422 $result['target_contact_id'] = $params['target_contact_id'];
1423 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1428 * Create an activity type.
1430 * @param array $params
1435 public function activityTypeCreate($params) {
1436 return $this->callAPISuccess('ActivityType', 'create', $params);
1440 * Delete activity type.
1442 * @param int $activityTypeId
1443 * Id of the activity type.
1447 public function activityTypeDelete($activityTypeId) {
1448 $params['activity_type_id'] = $activityTypeId;
1449 return $this->callAPISuccess('ActivityType', 'delete', $params);
1453 * Create custom group.
1455 * @param array $params
1459 public function customGroupCreate($params = []) {
1461 'title' => 'new custom group',
1462 'extends' => 'Contact',
1464 'style' => 'Inline',
1468 $params = array_merge($defaults, $params);
1470 return $this->callAPISuccess('custom_group', 'create', $params);
1474 * Existing function doesn't allow params to be over-ridden so need a new one
1475 * this one allows you to only pass in the params you want to change
1477 * @param array $params
1481 public function CustomGroupCreateByParams($params = []) {
1483 'title' => "API Custom Group",
1484 'extends' => 'Contact',
1486 'style' => 'Inline',
1489 $params = array_merge($defaults, $params);
1490 return $this->callAPISuccess('custom_group', 'create', $params);
1494 * Create custom group with multi fields.
1496 * @param array $params
1500 public function CustomGroupMultipleCreateByParams($params = []) {
1505 $params = array_merge($defaults, $params);
1506 return $this->CustomGroupCreateByParams($params);
1510 * Create custom group with multi fields.
1512 * @param array $params
1516 public function CustomGroupMultipleCreateWithFields($params = []) {
1517 // also need to pass on $params['custom_field'] if not set but not in place yet
1519 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1520 $ids['custom_group_id'] = $customGroup['id'];
1522 $customField = $this->customFieldCreate([
1523 'custom_group_id' => $ids['custom_group_id'],
1524 'label' => 'field_1' . $ids['custom_group_id'],
1528 $ids['custom_field_id'][] = $customField['id'];
1530 $customField = $this->customFieldCreate([
1531 'custom_group_id' => $ids['custom_group_id'],
1532 'default_value' => '',
1533 'label' => 'field_2' . $ids['custom_group_id'],
1536 $ids['custom_field_id'][] = $customField['id'];
1538 $customField = $this->customFieldCreate([
1539 'custom_group_id' => $ids['custom_group_id'],
1540 'default_value' => '',
1541 'label' => 'field_3' . $ids['custom_group_id'],
1544 $ids['custom_field_id'][] = $customField['id'];
1550 * Create a custom group with a single text custom field. See
1551 * participant:testCreateWithCustom for how to use this
1553 * @param string $function
1555 * @param string $filename
1559 * ids of created objects
1561 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1562 $params = ['title' => $function];
1563 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1564 $params['extends'] = $entity ?
$entity : 'Contact';
1565 $customGroup = $this->customGroupCreate($params);
1566 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1567 CRM_Core_PseudoConstant
::flush();
1569 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1573 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1574 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1576 * @param string $function
1578 * @param string $filename
1582 * ids of created objects
1584 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1585 $params = ['title' => $function];
1586 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1587 $params['extends'] = $entity ?
$entity : 'Contact';
1588 $customGroup = $this->customGroupCreate($params);
1589 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1590 CRM_Core_PseudoConstant
::flush();
1592 'defaultValue' => 'Default Value',
1593 'lowercasevalue' => 'Lowercase Value',
1594 1 => 'Integer Value',
1597 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1598 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1599 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1600 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1601 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1602 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1603 foreach ($options as $option_value => $option_label) {
1604 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1605 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1609 'custom_group_id' => $customGroup['id'],
1610 'custom_field_id' => $customField['id'],
1611 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1612 'custom_field_group_options' => $options,
1617 * Delete custom group.
1619 * @param int $customGroupID
1623 public function customGroupDelete($customGroupID) {
1624 $params['id'] = $customGroupID;
1625 return $this->callAPISuccess('custom_group', 'delete', $params);
1629 * Create custom field.
1631 * @param array $params
1632 * (custom_group_id) is required.
1636 public function customFieldCreate($params) {
1637 $params = array_merge([
1638 'label' => 'Custom Field',
1639 'data_type' => 'String',
1640 'html_type' => 'Text',
1641 'is_searchable' => 1,
1643 'default_value' => 'defaultValue',
1646 $result = $this->callAPISuccess('custom_field', 'create', $params);
1647 // these 2 functions are called with force to flush static caches
1648 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1649 CRM_Core_Component
::getEnabledComponents(1);
1654 * Delete custom field.
1656 * @param int $customFieldID
1660 public function customFieldDelete($customFieldID) {
1662 $params['id'] = $customFieldID;
1663 return $this->callAPISuccess('custom_field', 'delete', $params);
1673 public function noteCreate($cId) {
1675 'entity_table' => 'civicrm_contact',
1676 'entity_id' => $cId,
1677 'note' => 'hello I am testing Note',
1678 'contact_id' => $cId,
1679 'modified_date' => date('Ymd'),
1680 'subject' => 'Test Note',
1683 return $this->callAPISuccess('Note', 'create', $params);
1687 * Enable CiviCampaign Component.
1689 * @param bool $reloadConfig
1690 * Force relaod config or not
1692 public function enableCiviCampaign($reloadConfig = TRUE) {
1693 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1694 if ($reloadConfig) {
1695 // force reload of config object
1696 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1698 //flush cache by calling with reset
1699 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1703 * Create custom field with Option Values.
1705 * @param array $customGroup
1706 * @param string $name
1707 * Name of custom field.
1708 * @param array $extraParams
1709 * Additional parameters to pass through.
1713 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1715 'custom_group_id' => $customGroup['id'],
1716 'name' => 'test_custom_group',
1717 'label' => 'Country',
1718 'html_type' => 'Select',
1719 'data_type' => 'String',
1722 'is_searchable' => 0,
1728 'name' => 'option_group1',
1729 'label' => 'option_group_label1',
1733 'option_label' => ['Label1', 'Label2'],
1734 'option_value' => ['value1', 'value2'],
1735 'option_name' => [$name . '_1', $name . '_2'],
1736 'option_weight' => [1, 2],
1737 'option_status' => [1, 1],
1740 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1742 return $this->callAPISuccess('custom_field', 'create', $params);
1750 public function confirmEntitiesDeleted($entities) {
1751 foreach ($entities as $entity) {
1753 $result = $this->callAPISuccess($entity, 'Get', []);
1754 if ($result['error'] == 1 ||
$result['count'] > 0) {
1755 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1763 * Quick clean by emptying tables created for the test.
1765 * @param array $tablesToTruncate
1766 * @param bool $dropCustomValueTables
1768 * @throws \CRM_Core_Exception
1770 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1772 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1774 if ($dropCustomValueTables) {
1775 $optionGroupResult = CRM_Core_DAO
::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1776 while ($optionGroupResult->fetch()) {
1777 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1778 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1779 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1780 // generic cleanup routine.
1781 if (!empty($optionGroupResult->option_group_id
) && $optionGroupResult->option_group_id
> 50) {
1782 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id
);
1785 $tablesToTruncate[] = 'civicrm_custom_group';
1786 $tablesToTruncate[] = 'civicrm_custom_field';
1789 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1791 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1792 foreach ($tablesToTruncate as $table) {
1793 $sql = "TRUNCATE TABLE $table";
1794 CRM_Core_DAO
::executeQuery($sql);
1796 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1798 if ($dropCustomValueTables) {
1799 $dbName = self
::getDBName();
1801 SELECT TABLE_NAME as tableName
1802 FROM INFORMATION_SCHEMA.TABLES
1803 WHERE TABLE_SCHEMA = '{$dbName}'
1804 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1807 $tableDAO = CRM_Core_DAO
::executeQuery($query);
1808 while ($tableDAO->fetch()) {
1809 $sql = "DROP TABLE {$tableDAO->tableName}";
1810 CRM_Core_DAO
::executeQuery($sql);
1816 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1818 * @throws \CRM_Core_Exception
1820 public function quickCleanUpFinancialEntities() {
1821 $tablesToTruncate = [
1823 'civicrm_activity_contact',
1824 'civicrm_contribution',
1825 'civicrm_contribution_soft',
1826 'civicrm_contribution_product',
1827 'civicrm_financial_trxn',
1828 'civicrm_financial_item',
1829 'civicrm_contribution_recur',
1830 'civicrm_line_item',
1831 'civicrm_contribution_page',
1832 'civicrm_payment_processor',
1833 'civicrm_entity_financial_trxn',
1834 'civicrm_membership',
1835 'civicrm_membership_type',
1836 'civicrm_membership_payment',
1837 'civicrm_membership_log',
1838 'civicrm_membership_block',
1840 'civicrm_participant',
1841 'civicrm_participant_payment',
1843 'civicrm_pcp_block',
1845 'civicrm_pledge_block',
1846 'civicrm_pledge_payment',
1847 'civicrm_price_set_entity',
1848 'civicrm_price_field_value',
1849 'civicrm_price_field',
1851 $this->quickCleanup($tablesToTruncate);
1852 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1853 $this->restoreDefaultPriceSetConfig();
1854 $this->disableTaxAndInvoicing();
1855 $this->setCurrencySeparators(',');
1856 CRM_Core_PseudoConstant
::flush('taxRates');
1857 System
::singleton()->flushProcessors();
1858 // @fixme this parameter is leaking - it should not be defined as a class static
1859 // but for now we just handle in tear down.
1860 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1864 * Reset the price set config so results exist.
1866 public function restoreDefaultPriceSetConfig() {
1867 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1868 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1869 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)");
1870 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)");
1874 * Recreate default membership types.
1876 public function restoreMembershipTypes() {
1877 CRM_Core_DAO
::executeQuery(
1878 "REPLACE INTO civicrm_membership_type
1879 (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)
1881 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1882 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1883 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1888 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1889 * Default behaviour is to also delete the entity
1890 * @param array $params
1891 * Params array to check against.
1893 * Id of the entity concerned.
1894 * @param string $entity
1895 * Name of entity concerned (e.g. membership).
1896 * @param bool $delete
1897 * Should the entity be deleted as part of this check.
1898 * @param string $errorText
1899 * Text to print on error.
1903 * @param array $params
1906 * @param int $delete
1907 * @param string $errorText
1909 * @throws CRM_Core_Exception
1911 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1913 $result = $this->callAPISuccessGetSingle($entity, [
1918 $this->callAPISuccess($entity, 'Delete', [
1922 $dateFields = $keys = $dateTimeFields = [];
1923 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1924 foreach ($fields['values'] as $field => $settings) {
1925 if (array_key_exists($field, $result)) {
1926 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
1929 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
1931 $type = $settings['type'] ??
NULL;
1932 if ($type == CRM_Utils_Type
::T_DATE
) {
1933 $dateFields[] = $settings['name'];
1934 // we should identify both real names & unique names as dates
1935 if ($field != $settings['name']) {
1936 $dateFields[] = $field;
1939 if ($type == CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
1940 $dateTimeFields[] = $settings['name'];
1941 // we should identify both real names & unique names as dates
1942 if ($field != $settings['name']) {
1943 $dateTimeFields[] = $field;
1948 if (strtolower($entity) == 'contribution') {
1949 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1950 // this is not returned in id format
1951 unset($params['payment_instrument_id']);
1952 $params['contribution_source'] = $params['source'];
1953 unset($params['source']);
1956 foreach ($params as $key => $value) {
1957 if ($key == 'version' ||
substr($key, 0, 3) == 'api' ||
!array_key_exists($keys[$key], $result)) {
1960 if (in_array($key, $dateFields)) {
1961 $value = date('Y-m-d', strtotime($value));
1962 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1964 if (in_array($key, $dateTimeFields)) {
1965 $value = date('Y-m-d H:i:s', strtotime($value));
1966 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
1968 $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);
1973 * Get formatted values in the actual and expected result.
1975 * @param array $actual
1976 * Actual calculated values.
1977 * @param array $expected
1980 public function checkArrayEquals(&$actual, &$expected) {
1981 self
::unsetId($actual);
1982 self
::unsetId($expected);
1983 $this->assertEquals($expected, $actual);
1987 * Unset the key 'id' from the array
1989 * @param array $unformattedArray
1990 * The array from which the 'id' has to be unset.
1992 public static function unsetId(&$unformattedArray) {
1993 $formattedArray = [];
1994 if (array_key_exists('id', $unformattedArray)) {
1995 unset($unformattedArray['id']);
1997 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
1998 foreach ($unformattedArray['values'] as $key => $value) {
1999 if (is_array($value)) {
2000 foreach ($value as $k => $v) {
2006 elseif ($key == 'id') {
2007 $unformattedArray[$key];
2009 $formattedArray = [$value];
2011 $unformattedArray['values'] = $formattedArray;
2016 * Helper to enable/disable custom directory support
2018 * @param array $customDirs
2020 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2021 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2023 public function customDirectories($customDirs) {
2024 $config = CRM_Core_Config
::singleton();
2026 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2027 unset($config->customPHPPathDir
);
2029 elseif ($customDirs['php_path'] === TRUE) {
2030 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2033 $config->customPHPPathDir
= $php_path;
2036 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2037 unset($config->customTemplateDir
);
2039 elseif ($customDirs['template_path'] === TRUE) {
2040 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2043 $config->customTemplateDir
= $template_path;
2048 * Generate a temporary folder.
2050 * @param string $prefix
2054 public function createTempDir($prefix = 'test-') {
2055 $tempDir = CRM_Utils_File
::tempdir($prefix);
2056 $this->tempDirs
[] = $tempDir;
2060 public function cleanTempDirs() {
2061 if (!is_array($this->tempDirs
)) {
2062 // fix test errors where this is not set
2065 foreach ($this->tempDirs
as $tempDir) {
2066 if (is_dir($tempDir)) {
2067 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2073 * Temporarily replace the singleton extension with a different one.
2075 * @param \CRM_Extension_System $system
2077 public function setExtensionSystem(CRM_Extension_System
$system) {
2078 if ($this->origExtensionSystem
== NULL) {
2079 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2081 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2084 public function unsetExtensionSystem() {
2085 if ($this->origExtensionSystem
!== NULL) {
2086 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2087 $this->origExtensionSystem
= NULL;
2092 * Temporarily alter the settings-metadata to add a mock setting.
2094 * WARNING: The setting metadata will disappear on the next cache-clear.
2100 public function setMockSettingsMetaData($extras) {
2101 CRM_Utils_Hook
::singleton()
2102 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2103 $metadata = array_merge($metadata, $extras);
2106 Civi
::service('settings_manager')->flush();
2108 $fields = $this->callAPISuccess('setting', 'getfields', []);
2109 foreach ($extras as $key => $spec) {
2110 $this->assertNotEmpty($spec['title']);
2111 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2116 * @param string $name
2118 public function financialAccountDelete($name) {
2119 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2120 $financialAccount->name
= $name;
2121 if ($financialAccount->find(TRUE)) {
2122 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2123 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2124 $entityFinancialType->delete();
2125 $financialAccount->delete();
2130 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2131 * (NB unclear if this is still required)
2133 public function _sethtmlGlobals() {
2134 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2136 'html_quickform_rule_required',
2137 'HTML/QuickForm/Rule/Required.php',
2140 'html_quickform_rule_range',
2141 'HTML/QuickForm/Rule/Range.php',
2144 'html_quickform_rule_range',
2145 'HTML/QuickForm/Rule/Range.php',
2148 'html_quickform_rule_range',
2149 'HTML/QuickForm/Rule/Range.php',
2152 'html_quickform_rule_email',
2153 'HTML/QuickForm/Rule/Email.php',
2156 'html_quickform_rule_regex',
2157 'HTML/QuickForm/Rule/Regex.php',
2160 'html_quickform_rule_regex',
2161 'HTML/QuickForm/Rule/Regex.php',
2164 'html_quickform_rule_regex',
2165 'HTML/QuickForm/Rule/Regex.php',
2168 'html_quickform_rule_regex',
2169 'HTML/QuickForm/Rule/Regex.php',
2171 'nopunctuation' => [
2172 'html_quickform_rule_regex',
2173 'HTML/QuickForm/Rule/Regex.php',
2176 'html_quickform_rule_regex',
2177 'HTML/QuickForm/Rule/Regex.php',
2180 'html_quickform_rule_callback',
2181 'HTML/QuickForm/Rule/Callback.php',
2184 'html_quickform_rule_compare',
2185 'HTML/QuickForm/Rule/Compare.php',
2188 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2189 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2191 'HTML/QuickForm/group.php',
2192 'HTML_QuickForm_group',
2195 'HTML/QuickForm/hidden.php',
2196 'HTML_QuickForm_hidden',
2199 'HTML/QuickForm/reset.php',
2200 'HTML_QuickForm_reset',
2203 'HTML/QuickForm/checkbox.php',
2204 'HTML_QuickForm_checkbox',
2207 'HTML/QuickForm/file.php',
2208 'HTML_QuickForm_file',
2211 'HTML/QuickForm/image.php',
2212 'HTML_QuickForm_image',
2215 'HTML/QuickForm/password.php',
2216 'HTML_QuickForm_password',
2219 'HTML/QuickForm/radio.php',
2220 'HTML_QuickForm_radio',
2223 'HTML/QuickForm/button.php',
2224 'HTML_QuickForm_button',
2227 'HTML/QuickForm/submit.php',
2228 'HTML_QuickForm_submit',
2231 'HTML/QuickForm/select.php',
2232 'HTML_QuickForm_select',
2235 'HTML/QuickForm/hiddenselect.php',
2236 'HTML_QuickForm_hiddenselect',
2239 'HTML/QuickForm/text.php',
2240 'HTML_QuickForm_text',
2243 'HTML/QuickForm/textarea.php',
2244 'HTML_QuickForm_textarea',
2247 'HTML/QuickForm/fckeditor.php',
2248 'HTML_QuickForm_FCKEditor',
2251 'HTML/QuickForm/tinymce.php',
2252 'HTML_QuickForm_TinyMCE',
2255 'HTML/QuickForm/dojoeditor.php',
2256 'HTML_QuickForm_dojoeditor',
2259 'HTML/QuickForm/link.php',
2260 'HTML_QuickForm_link',
2263 'HTML/QuickForm/advcheckbox.php',
2264 'HTML_QuickForm_advcheckbox',
2267 'HTML/QuickForm/date.php',
2268 'HTML_QuickForm_date',
2271 'HTML/QuickForm/static.php',
2272 'HTML_QuickForm_static',
2275 'HTML/QuickForm/header.php',
2276 'HTML_QuickForm_header',
2279 'HTML/QuickForm/html.php',
2280 'HTML_QuickForm_html',
2283 'HTML/QuickForm/hierselect.php',
2284 'HTML_QuickForm_hierselect',
2287 'HTML/QuickForm/autocomplete.php',
2288 'HTML_QuickForm_autocomplete',
2291 'HTML/QuickForm/xbutton.php',
2292 'HTML_QuickForm_xbutton',
2294 'advmultiselect' => [
2295 'HTML/QuickForm/advmultiselect.php',
2296 'HTML_QuickForm_advmultiselect',
2302 * Set up an acl allowing contact to see 2 specified groups
2303 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2305 * You need to have pre-created these groups & created the user e.g
2306 * $this->createLoggedInUser();
2307 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2308 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2310 * @param bool $isProfile
2312 public function setupACL($isProfile = FALSE) {
2314 $_REQUEST = $this->_params
;
2316 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2317 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2318 $ov = new CRM_Core_DAO_OptionValue();
2319 $ov->option_group_id
= $optionGroupID;
2321 if ($ov->find(TRUE)) {
2322 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2324 $optionValue = $this->callAPISuccess('option_value', 'create', [
2325 'option_group_id' => $optionGroupID,
2326 'label' => 'pick me',
2330 CRM_Core_DAO
::executeQuery("
2331 TRUNCATE civicrm_acl_cache
2334 CRM_Core_DAO
::executeQuery("
2335 TRUNCATE civicrm_acl_contact_cache
2338 CRM_Core_DAO
::executeQuery("
2339 INSERT INTO civicrm_acl_entity_role (
2340 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2341 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2345 CRM_Core_DAO
::executeQuery("
2346 INSERT INTO civicrm_acl (
2347 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2350 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2355 CRM_Core_DAO
::executeQuery("
2356 INSERT INTO civicrm_acl (
2357 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2360 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2364 CRM_Core_DAO
::executeQuery("
2365 INSERT INTO civicrm_acl (
2366 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2369 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2374 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2375 $this->callAPISuccess('group_contact', 'create', [
2376 'group_id' => $this->_permissionedGroup
,
2377 'contact_id' => $this->_loggedInUser
,
2382 CRM_ACL_BAO_Cache
::resetCache();
2383 CRM_ACL_API
::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2388 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2390 public function offsetDefaultPriceSet() {
2391 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2392 $firstID = $contributionPriceSet['id'];
2393 $this->callAPISuccess('price_set', 'create', [
2394 'id' => $contributionPriceSet['id'],
2398 unset($contributionPriceSet['id']);
2399 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2400 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2401 'price_set_id' => $firstID,
2402 'options' => ['limit' => 1],
2404 unset($priceField['id']);
2405 $priceField['price_set_id'] = $newPriceSet['id'];
2406 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2407 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2408 'price_set_id' => $firstID,
2410 'options' => ['limit' => 1],
2413 unset($priceFieldValue['id']);
2414 //create some padding to use up ids
2415 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2416 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2417 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2421 * Create an instance of the paypal processor.
2423 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2424 * this parent class & we don't have a structure for that yet
2425 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2426 * & the best protection against that is the functions this class affords
2428 * @param array $params
2430 * @return int $result['id'] payment processor id
2432 public function paymentProcessorCreate($params = []) {
2433 $params = array_merge([
2435 'domain_id' => CRM_Core_Config
::domainID(),
2436 'payment_processor_type_id' => 'PayPal',
2440 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2441 'password' => '1183377788',
2442 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2443 'url_site' => 'https://www.sandbox.paypal.com/',
2444 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2445 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2446 'class_name' => 'Payment_PayPalImpl',
2447 'billing_mode' => 3,
2448 'financial_type_id' => 1,
2449 'financial_account_id' => 12,
2450 // Credit card = 1 so can pass 'by accident'.
2451 'payment_instrument_id' => 'Debit Card',
2453 if (!is_numeric($params['payment_processor_type_id'])) {
2454 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2456 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2457 'name' => $params['payment_processor_type_id'],
2461 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2462 return $result['id'];
2466 * Set up initial recurring payment allowing subsequent IPN payments.
2468 * @param array $recurParams (Optional)
2469 * @param array $contributionParams (Optional)
2471 * @throws \CRM_Core_Exception
2473 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2474 $contributionParams = array_merge([
2475 'total_amount' => '200',
2476 'invoice_id' => $this->_invoiceID
,
2477 'financial_type_id' => 'Donation',
2478 'contribution_status_id' => 'Pending',
2479 'contact_id' => $this->_contactID
,
2480 'contribution_page_id' => $this->_contributionPageID
,
2481 'payment_processor_id' => $this->_paymentProcessorID
,
2483 'receive_date' => '2019-07-25 07:34:23',
2484 'skipCleanMoney' => TRUE,
2485 ], $contributionParams);
2486 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2487 'contact_id' => $this->_contactID
,
2490 'installments' => 5,
2491 'frequency_unit' => 'Month',
2492 'frequency_interval' => 1,
2493 'invoice_id' => $this->_invoiceID
,
2494 'contribution_status_id' => 2,
2495 'payment_processor_id' => $this->_paymentProcessorID
,
2496 // processor provided ID - use contact ID as proxy.
2497 'processor_id' => $this->_contactID
,
2498 'api.Order.create' => $contributionParams,
2499 ], $recurParams))['values'][0];
2500 $this->_contributionRecurID
= $contributionRecur['id'];
2501 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2502 $this->ids
['Contribution'][0] = $this->_contributionID
;
2506 * 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
2508 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2510 * @throws \CRM_Core_Exception
2512 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2513 $membershipParams = $recurParams = [];
2514 if (!empty($params['duration_unit'])) {
2515 $membershipParams['duration_unit'] = $params['duration_unit'];
2517 if (!empty($params['frequency_unit'])) {
2518 $recurParams['frequency_unit'] = $params['frequency_unit'];
2521 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2522 //create a contribution so our membership & contribution don't both have id = 1
2523 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2524 $this->contributionCreate([
2525 'contact_id' => $this->_contactID
,
2527 'financial_type_id' => 1,
2528 'invoice_id' => 'abcd',
2530 'receive_date' => '2019-07-25 07:34:23',
2534 $this->ids
['membership'] = $this->callAPISuccess('Membership', 'create', [
2535 'contact_id' => $this->_contactID
,
2536 'membership_type_id' => $this->ids
['membership_type'],
2537 'format.only_id' => TRUE,
2538 'source' => 'Payment',
2539 'skipLineItem' => TRUE,
2541 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2546 'entity_table' => 'civicrm_membership',
2547 'entity_id' => $this->ids
['membership'],
2548 'label' => 'General',
2550 'unit_price' => 200,
2551 'line_total' => 200,
2552 'financial_type_id' => 1,
2553 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2555 'label' => 'Membership Amount',
2556 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2558 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2560 'label' => 'General',
2561 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2568 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids
['membership'], 'contribution_recur_id' => $this->_contributionRecurID
]);
2576 public function CiviUnitTestCase_fatalErrorHandler($message) {
2577 throw new Exception("{$message['message']}: {$message['code']}");
2581 * Wrap the entire test case in a transaction.
2583 * Only subsequent DB statements will be wrapped in TX -- this cannot
2584 * retroactively wrap old DB statements. Therefore, it makes sense to
2585 * call this at the beginning of setUp().
2587 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2588 * this option does not work with, e.g., custom-data.
2590 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2591 * if TRUNCATE or ALTER is called while using a transaction.
2594 * Whether to use nesting or reference-counting.
2596 public function useTransaction($nest = TRUE) {
2598 $this->tx
= new CRM_Core_Transaction($nest);
2599 $this->tx
->rollback();
2604 * Assert the attachment exists.
2606 * @param bool $exists
2607 * @param array $apiResult
2609 protected function assertAttachmentExistence($exists, $apiResult) {
2610 $fileId = $apiResult['id'];
2611 $this->assertTrue(is_numeric($fileId));
2612 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2613 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2614 1 => [$fileId, 'Int'],
2616 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2617 1 => [$fileId, 'Int'],
2622 * Assert 2 sql strings are the same, ignoring double spaces.
2624 * @param string $expectedSQL
2625 * @param string $actualSQL
2626 * @param string $message
2628 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2629 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2630 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2631 $this->assertEquals($expected, $actual, $message);
2635 * Create a price set for an event.
2637 * @param int $feeTotal
2638 * @param int $minAmt
2639 * @param string $type
2641 * @param array $options
2645 * @throws \CRM_Core_Exception
2647 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2648 // creating price set, price field
2649 $paramsSet['title'] = 'Price Set';
2650 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2651 $paramsSet['is_active'] = FALSE;
2652 $paramsSet['extends'] = 1;
2653 $paramsSet['min_amount'] = $minAmt;
2655 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2656 $this->_ids
['price_set'] = $priceSet->id
;
2659 'label' => 'Price Field',
2660 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2661 'html_type' => $type,
2662 'price' => $feeTotal,
2663 'option_label' => ['1' => 'Price Field'],
2664 'option_value' => ['1' => $feeTotal],
2665 'option_name' => ['1' => $feeTotal],
2666 'option_weight' => ['1' => 1],
2667 'option_amount' => ['1' => 1],
2668 'is_display_amounts' => 1,
2670 'options_per_line' => 1,
2671 'is_active' => ['1' => 1],
2672 'price_set_id' => $this->_ids
['price_set'],
2673 'is_enter_qty' => 1,
2674 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2676 if ($type === 'Radio') {
2677 foreach ($options as $index => $option) {
2678 $paramsField['is_enter_qty'] = 0;
2679 $optionID = $index +
2;
2680 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2681 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2685 $this->callAPISuccess('PriceField', 'create', $paramsField);
2686 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2687 $this->_ids
['price_field'] = array_keys($fields['values']);
2688 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2689 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2691 return $this->_ids
['price_set'];
2695 * Add a profile to a contribution page.
2697 * @param string $name
2698 * @param int $contributionPageID
2699 * @param string $module
2701 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2703 'uf_group_id' => $name,
2704 'module' => $module,
2705 'entity_table' => 'civicrm_contribution_page',
2706 'entity_id' => $contributionPageID,
2709 if ($module !== 'CiviContribute') {
2710 $params['module_data'] = [$module => []];
2712 $this->callAPISuccess('UFJoin', 'create', $params);
2716 * Add participant with contribution
2720 * @throws \CRM_Core_Exception
2722 protected function createPartiallyPaidParticipantOrder() {
2723 $orderParams = $this->getParticipantOrderParams();
2724 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2725 return $this->callAPISuccess('Order', 'create', $orderParams);
2731 * @param string $component
2732 * @param int $componentId
2733 * @param array $priceFieldOptions
2737 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2738 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2739 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2740 $paramsSet['is_active'] = TRUE;
2741 $paramsSet['financial_type_id'] = 'Event Fee';
2742 $paramsSet['extends'] = 1;
2743 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2744 $priceSetId = $priceSet['id'];
2745 //Checking for priceset added in the table.
2746 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2747 'id', $paramsSet['title'], 'Check DB for created priceset'
2749 $paramsField = array_merge([
2750 'label' => 'Price Field',
2751 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2752 'html_type' => 'CheckBox',
2753 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2754 'option_value' => ['1' => 100, '2' => 200],
2755 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2756 'option_weight' => ['1' => 1, '2' => 2],
2757 'option_amount' => ['1' => 100, '2' => 200],
2758 'is_display_amounts' => 1,
2760 'options_per_line' => 1,
2761 'is_active' => ['1' => 1, '2' => 1],
2762 'price_set_id' => $priceSet['id'],
2763 'is_enter_qty' => 1,
2764 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2765 ], $priceFieldOptions);
2767 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2769 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSetId);
2771 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2775 * Replace the template with a test-oriented template designed to show all the variables.
2777 * @param string $templateName
2778 * @param string $type
2780 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2781 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2782 CRM_Core_DAO
::executeQuery(
2783 "UPDATE civicrm_msg_template
2784 SET msg_{$type} = %1
2785 WHERE workflow_name = '{$templateName}'
2786 AND is_default = 1", [1 => [$testTemplate, 'String']]
2791 * Reinstate the default template.
2793 * @param string $templateName
2794 * @param string $type
2796 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2797 CRM_Core_DAO
::executeQuery(
2798 "UPDATE civicrm_option_group og
2799 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2800 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2801 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2802 SET m.msg_{$type} = m2.msg_{$type}
2803 WHERE og.name = 'msg_tpl_workflow_contribution'
2804 AND ov.name = '{$templateName}'
2805 AND m.is_default = 1"
2810 * Flush statics relating to financial type.
2812 protected function flushFinancialTypeStatics() {
2813 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2814 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2816 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2817 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2819 CRM_Contribute_PseudoConstant
::flush('financialType');
2820 CRM_Contribute_PseudoConstant
::flush('membershipType');
2821 // Pseudoconstants may be saved to the cache table.
2822 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2823 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2824 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2828 * Set the permissions to the supplied array.
2830 * @param array $permissions
2832 protected function setPermissions($permissions) {
2833 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2834 $this->flushFinancialTypeStatics();
2838 * @param array $params
2841 public function _checkFinancialRecords($params, $context) {
2843 'entity_id' => $params['id'],
2844 'entity_table' => 'civicrm_contribution',
2846 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2847 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2848 if ($context == 'pending') {
2849 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2850 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2853 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2855 'id' => $trxn['financial_trxn_id'],
2857 if ($context != 'online' && $context != 'payLater') {
2859 'to_financial_account_id' => 6,
2860 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2864 if ($context == 'feeAmount') {
2865 $compareParams['fee_amount'] = 50;
2867 elseif ($context == 'online') {
2869 'to_financial_account_id' => 12,
2870 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2872 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2875 elseif ($context == 'payLater') {
2877 'to_financial_account_id' => 7,
2878 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2882 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2884 'financial_trxn_id' => $trxn['financial_trxn_id'],
2885 'entity_table' => 'civicrm_financial_item',
2887 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2889 'id' => $entityTrxn['entity_id'],
2892 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2894 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2896 if ($context == 'payLater') {
2898 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2900 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2903 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2904 if ($context == 'feeAmount') {
2906 'entity_id' => $params['id'],
2907 'entity_table' => 'civicrm_contribution',
2909 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2911 'id' => $maxTrxn['financial_trxn_id'],
2914 'to_financial_account_id' => 5,
2915 'from_financial_account_id' => 6,
2916 'total_amount' => 50,
2919 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2920 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2922 'entity_id' => $trxnId['financialTrxnId'],
2923 'entity_table' => 'civicrm_financial_trxn',
2928 'financial_account_id' => 5,
2930 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2932 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2933 // line should be copied into all the functions that call this function & evaluated there
2934 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2935 // when calling completeTransaction or repeatTransaction.
2936 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2940 * Return financial type id on basis of name
2942 * @param string $name Financial type m/c name
2946 public function getFinancialTypeId($name) {
2947 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2951 * Cleanup function for contents of $this->ids.
2953 * This is a best effort cleanup to use in tear downs etc.
2955 * It will not fail if the data has already been removed (some tests may do
2956 * their own cleanup).
2958 protected function cleanUpSetUpIDs() {
2959 foreach ($this->setupIDs
as $entity => $id) {
2961 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
2963 catch (CiviCRM_API3_Exception
$e) {
2964 // This is a best-effort cleanup function, ignore.
2970 * Create Financial Type.
2972 * @param array $params
2976 protected function createFinancialType($params = []) {
2977 $params = array_merge($params,
2979 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
2983 return $this->callAPISuccess('FinancialType', 'create', $params);
2987 * Create Payment Instrument.
2989 * @param array $params
2990 * @param string $financialAccountName
2994 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
2995 $params = array_merge([
2996 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
2997 'option_group_id' => 'payment_instrument',
3000 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3002 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3004 $financialAccountParams = [
3005 'entity_table' => 'civicrm_option_value',
3006 'entity_id' => $newPaymentInstrument,
3007 'account_relationship' => $relationTypeID,
3008 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3010 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3012 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3016 * Enable Tax and Invoicing
3018 * @param array $params
3020 * @return \Civi\Core\SettingsBag
3022 protected function enableTaxAndInvoicing($params = []) {
3023 // Enable component contribute setting
3024 $contributeSetting = array_merge($params,
3027 'invoice_prefix' => 'INV_',
3029 'due_date_period' => 'days',
3031 'is_email_pdf' => 1,
3032 'tax_term' => 'Sales Tax',
3033 'tax_display_settings' => 'Inclusive',
3036 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3040 * Enable Tax and Invoicing
3042 * @throws \CRM_Core_Exception
3044 protected function disableTaxAndInvoicing() {
3045 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3046 foreach ($accounts as $account) {
3047 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3048 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3051 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3052 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3054 return Civi
::settings()->set('invoicing', FALSE);
3058 * Add Sales Tax relation for financial type with financial account.
3060 * @param int $financialTypeId
3064 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
3066 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3067 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3068 'is_deductible' => 1,
3073 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3075 'entity_table' => 'civicrm_financial_type',
3076 'entity_id' => $financialTypeId,
3077 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3080 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3081 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3083 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3084 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3085 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3086 $dao->copyValues($entityParams);
3088 if ($dao->fetch()) {
3089 $entityParams['id'] = $dao->id
;
3091 $entityParams['financial_account_id'] = $account->id
;
3093 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3097 * Create price set with contribution test for test setup.
3099 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3100 * on parent class at some point (fn is not in 4.4).
3103 * @param array $params
3105 public function createPriceSetWithPage($entity = NULL, $params = []) {
3106 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3107 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3108 'title' => "Test Contribution Page",
3109 'financial_type_id' => 1,
3110 'currency' => 'NZD',
3111 'goal_amount' => 50,
3112 'is_pay_later' => 1,
3113 'is_monetary' => TRUE,
3114 'is_email_receipt' => FALSE,
3116 $priceSet = $this->callAPISuccess('price_set', 'create', [
3117 'is_quick_config' => 0,
3118 'extends' => 'CiviMember',
3119 'financial_type_id' => 1,
3120 'title' => 'my Page',
3122 $priceSetID = $priceSet['id'];
3124 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3125 $priceField = $this->callAPISuccess('price_field', 'create', [
3126 'price_set_id' => $priceSetID,
3127 'label' => 'Goat Breed',
3128 'html_type' => 'Radio',
3130 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3131 'price_set_id' => $priceSetID,
3132 'price_field_id' => $priceField['id'],
3133 'label' => 'Long Haired Goat',
3135 'financial_type_id' => 'Donation',
3136 'membership_type_id' => $membershipTypeID,
3137 'membership_num_terms' => 1,
3139 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3140 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3141 'price_set_id' => $priceSetID,
3142 'price_field_id' => $priceField['id'],
3143 'label' => 'Shoe-eating Goat',
3145 'financial_type_id' => 'Donation',
3146 'membership_type_id' => $membershipTypeID,
3147 'membership_num_terms' => 2,
3149 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3151 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3152 'price_set_id' => $priceSetID,
3153 'price_field_id' => $priceField['id'],
3154 'label' => 'Shoe-eating Goat',
3156 'financial_type_id' => 'Donation',
3158 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3160 $this->_ids
['price_set'] = $priceSetID;
3161 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3162 $this->_ids
['price_field'] = [$priceField['id']];
3164 $this->_ids
['membership_type'] = $membershipTypeID;
3168 * Only specified contact returned.
3170 * @implements CRM_Utils_Hook::aclWhereClause
3174 * @param $whereTables
3178 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3179 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3183 * @implements CRM_Utils_Hook::selectWhereClause
3185 * @param string $entity
3186 * @param array $clauses
3188 public function selectWhereClauseHook($entity, &$clauses) {
3189 if ($entity == 'Event') {
3190 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3195 * An implementation of hook_civicrm_post used with all our test cases.
3198 * @param string $objectName
3199 * @param int $objectId
3202 public function onPost($op, $objectName, $objectId, &$objectRef) {
3203 if ($op == 'create' && $objectName == 'Individual') {
3204 CRM_Core_DAO
::executeQuery(
3205 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3207 1 => [$objectId, 'Integer'],
3212 if ($op == 'edit' && $objectName == 'Participant') {
3214 1 => [$objectId, 'Integer'],
3216 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3217 CRM_Core_DAO
::executeQuery($query, $params);
3222 * Instantiate form object.
3224 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3226 * @param string $class
3227 * Name of form class.
3229 * @param array $formValues
3231 * @param string $pageName
3233 * @return \CRM_Core_Form
3234 * @throws \CRM_Core_Exception
3236 public function getFormObject($class, $formValues = [], $pageName = '') {
3237 $_POST = $formValues;
3238 $form = new $class();
3239 $_SERVER['REQUEST_METHOD'] = 'GET';
3241 case 'CRM_Event_Cart_Form_Checkout_Payment':
3242 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3243 $form->controller
= new CRM_Event_Cart_Controller_Checkout();
3247 $form->controller
= new CRM_Core_Controller();
3250 $formParts = explode('_', $class);
3251 $pageName = array_pop($formParts);
3253 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3254 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3259 * Get possible thousand separators.
3263 public function getThousandSeparators() {
3264 return [['.'], [',']];
3268 * Get the boolean options as a provider.
3272 public function getBooleanDataProvider() {
3273 return [[TRUE], [FALSE]];
3277 * Set the separators for thousands and decimal points.
3279 * Note that this only covers some common scenarios.
3281 * It does not cater for a situation where the thousand separator is a [space]
3282 * Latter is the Norwegian localization. At least some tests need to
3283 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3284 * to provide broader coverage.
3286 * @param string $thousandSeparator
3288 protected function setCurrencySeparators($thousandSeparator) {
3289 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3290 Civi
::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3294 * Sets the thousand separator.
3296 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3298 * @param $thousandSeparator
3300 protected function setMonetaryThousandSeparator($thousandSeparator) {
3301 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3305 * Sets the decimal separator.
3307 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3309 * @param $decimalPoint
3311 protected function setMonetaryDecimalPoint($decimalPoint) {
3312 Civi
::settings()->set('monetaryDecimalPoint', $decimalPoint);
3316 * Sets the default currency.
3320 protected function setDefaultCurrency($currency) {
3321 Civi
::settings()->set('defaultCurrency', $currency);
3325 * Format money as it would be input.
3327 * @param string $amount
3331 protected function formatMoneyInput($amount) {
3332 return CRM_Utils_Money
::format($amount, NULL, '%a');
3336 * Get the contribution object.
3338 * @param int $contributionID
3340 * @return \CRM_Contribute_BAO_Contribution
3342 protected function getContributionObject($contributionID) {
3343 $contributionObj = new CRM_Contribute_BAO_Contribution();
3344 $contributionObj->id
= $contributionID;
3345 $contributionObj->find(TRUE);
3346 return $contributionObj;
3350 * Enable multilingual.
3352 public function enableMultilingual() {
3353 $this->callAPISuccess('Setting', 'create', [
3354 'lcMessages' => 'en_US',
3355 'languageLimit' => [
3360 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3363 $dbLocale = '_en_US';
3367 * Setup or clean up SMS tests
3369 * @param bool $teardown
3371 * @throws \CiviCRM_API3_Exception
3373 public function setupForSmsTests($teardown = FALSE) {
3374 require_once 'CiviTest/CiviTestSMSProvider.php';
3376 // Option value params for CiviTestSMSProvider
3377 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3379 'option_group_id' => $groupID,
3380 'label' => 'unittestSMS',
3381 'value' => 'unit.test.sms',
3382 'name' => 'CiviTestSMSProvider',
3389 // Test completed, delete provider
3390 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3391 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3395 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3396 // in CRM/Sms/Provider.php even though it is not an extension.
3397 return civicrm_api3('option_value', 'create', $params);
3401 * Start capturing browser output.
3403 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3405 protected function startCapturingOutput() {
3407 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3411 * Stop capturing browser output and return as a csv.
3413 * @param bool $isFirstRowHeaders
3415 * @return \League\Csv\Reader
3417 * @throws \League\Csv\Exception
3419 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3420 $output = ob_get_flush();
3421 $stream = fopen('php://memory', 'r+');
3422 fwrite($stream, $output);
3424 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3425 $csv = Reader
::createFromString($output);
3426 if ($isFirstRowHeaders) {
3427 $csv->setHeaderOffset(0);
3434 * Rename various labels to not match the names.
3436 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3437 * code that expects it to.
3439 protected function renameLabels() {
3440 $replacements = ['Pending', 'Refunded'];
3441 foreach ($replacements as $name) {
3442 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3447 * Undo any label renaming.
3449 protected function resetLabels() {
3450 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3454 * Get parameters to set up a multi-line participant order.
3457 * @throws \CRM_Core_Exception
3459 protected function getParticipantOrderParams(): array {
3460 $this->_contactId
= $this->individualCreate();
3461 $event = $this->eventCreate();
3462 $this->_eventId
= $event['id'];
3464 'id' => $this->_eventId
,
3465 'financial_type_id' => 4,
3468 $this->callAPISuccess('event', 'create', $eventParams);
3469 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3470 $participantParams = [
3471 'financial_type_id' => 4,
3472 'event_id' => $this->_eventId
,
3475 'fee_currency' => 'USD',
3476 'contact_id' => $this->_contactId
,
3478 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3480 'total_amount' => 300,
3481 'currency' => 'USD',
3482 'contact_id' => $this->_contactId
,
3483 'financial_type_id' => 4,
3484 'contribution_status_id' => 'Pending',
3485 'contribution_mode' => 'participant',
3486 'participant_id' => $participant['id'],
3488 foreach ($priceFields['values'] as $key => $priceField) {
3489 $orderParams['line_items'][] = [
3492 'price_field_id' => $priceField['price_field_id'],
3493 'price_field_value_id' => $priceField['id'],
3494 'label' => $priceField['label'],
3495 'field_title' => $priceField['label'],
3497 'unit_price' => $priceField['amount'],
3498 'line_total' => $priceField['amount'],
3499 'financial_type_id' => $priceField['financial_type_id'],
3500 'entity_table' => 'civicrm_participant',
3503 'params' => $participant,
3506 return $orderParams;
3512 * @throws \CRM_Core_Exception
3514 protected function validatePayments($payments) {
3515 foreach ($payments as $payment) {
3516 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3517 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3518 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3519 // This is a pretty rough way at guessing which payment is the overpayment - but
3520 // for the test suite it should be enough.
3523 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3524 'financial_trxn_id' => $payment['id'],
3525 'entity_table' => 'civicrm_financial_item',
3526 'return' => ['amount'],
3529 foreach ($items as $item) {
3530 $itemTotal +
= $item['amount'];
3532 $this->assertEquals($payment['total_amount'], $itemTotal);
3537 * Validate all created payments.
3539 * @throws \CRM_Core_Exception
3541 protected function validateAllPayments() {
3542 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3543 $this->validatePayments($payments);
3547 * Validate all created contributions.
3549 * @throws \CRM_Core_Exception
3551 protected function validateAllContributions() {
3552 $contributions = $this->callAPISuccess('Contribution', 'get', [])['values'];
3553 foreach ($contributions as $contribution) {
3554 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3556 foreach ($lineItems as $lineItem) {
3557 $total +
= $lineItem['line_total'];
3559 $this->assertEquals($total, $contribution['total_amount']);
3565 * @throws \CRM_Core_Exception
3567 protected function createRuleGroup() {
3568 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3569 'contact_type' => 'Individual',
3571 'used' => 'General',
3572 'name' => 'TestRule',
3573 'title' => 'TestRule',
3580 * Generic create test.
3582 * @param int $version
3584 * @throws \CRM_Core_Exception
3586 protected function basicCreateTest(int $version) {
3587 $this->_apiversion
= $version;
3588 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
3589 $this->assertEquals(1, $result['count']);
3590 $this->assertNotNull($result['values'][$result['id']]['id']);
3591 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
3595 * Generic delete test.
3597 * @param int $version
3599 * @throws \CRM_Core_Exception
3601 protected function basicDeleteTest($version) {
3602 $this->_apiversion
= $version;
3603 $result = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
3604 $deleteParams = ['id' => $result['id']];
3605 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
3606 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
3607 $this->assertEquals(0, $checkDeleted['count']);
3611 * Create and return a case object for the given Client ID.
3613 * @param int $clientId
3614 * @param int $loggedInUser
3615 * Omit or pass NULL to use the same as clientId
3616 * @param array $extra
3617 * Optional specific parameters such as start_date
3619 * @return CRM_Case_BAO_Case
3621 public function createCase($clientId, $loggedInUser = NULL, $extra = NULL) {
3622 if (empty($loggedInUser)) {
3623 // backwards compatibility - but it's more typical that the creator is a different person than the client
3624 $loggedInUser = $clientId;
3627 'activity_subject' => 'Case Subject',
3628 'client_id' => $clientId,
3629 'case_type_id' => 1,
3631 'case_type' => 'housing_support',
3632 'subject' => 'Case Subject',
3633 'start_date' => ($extra['start_date'] ??
date("Y-m-d")),
3634 'start_date_time' => ($extra['start_date_time'] ??
date("YmdHis")),
3636 'activity_details' => '',
3638 $form = new CRM_Case_Form_Case();
3639 $caseObj = $form->testSubmit($caseParams, "OpenCase", $loggedInUser, "standalone");