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 = [];
169 * PHPUnit Mock Method to use.
173 public $mockMethod = 'getMock';
178 * Because we are overriding the parent class constructor, we
179 * need to show the same arguments as exist in the constructor of
180 * PHPUnit_Framework_TestCase, since
181 * PHPUnit_Framework_TestSuite::createTest() creates a
182 * ReflectionClass of the Test class and checks the constructor
183 * of that class to decide how to set up the test.
185 * @param string $name
187 * @param string $dataName
189 public function __construct($name = NULL, array $data = [], $dataName = '') {
190 parent
::__construct($name, $data, $dataName);
192 // we need full error reporting
193 error_reporting(E_ALL
& ~E_NOTICE
);
195 self
::$_dbName = self
::getDBName();
197 // also load the class loader
198 require_once 'CRM/Core/ClassLoader.php';
199 CRM_Core_ClassLoader
::singleton()->register();
200 if (function_exists('_civix_phpunit_setUp')) {
201 // FIXME: loosen coupling
202 _civix_phpunit_setUp();
204 if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version
::id(), '5', '>=')) {
205 $this->mockMethod
= 'createMock';
207 elseif (class_exists('PHPUnit\Runner\Version') && version_compare(PHPUnit\Runner\Version
::id(), '6', '>=')) {
208 $this->mockMethod
= 'createMock';
213 * Override to run the test and assert its state.
217 * @throws \PHPUnit_Framework_IncompleteTest
218 * @throws \PHPUnit_Framework_SkippedTest
220 protected function runTest() {
222 return parent
::runTest();
224 catch (PEAR_Exception
$e) {
225 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
226 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
233 public function requireDBReset() {
234 return $this->DBResetRequired
;
240 public static function getDBName() {
241 static $dbName = NULL;
242 if ($dbName === NULL) {
243 require_once "DB.php";
244 $dsninfo = DB
::parseDSN(CIVICRM_DSN
);
245 $dbName = $dsninfo['database'];
251 * Create database connection for this instance.
253 * Initialize the test database if it hasn't been initialized
256 protected function getConnection() {
257 if (!self
::$dbInit) {
258 $dbName = self
::getDBName();
260 // install test database
261 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
263 static::_populateDB(FALSE, $this);
265 self
::$dbInit = TRUE;
271 * Required implementation of abstract method.
273 protected function getDataSet() {
277 * @param bool $perClass
278 * @param null $object
281 * TRUE if the populate logic runs; FALSE if it is skipped
283 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
284 if (CIVICRM_UF
!== 'UnitTests') {
285 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
288 if ($perClass ||
$object == NULL) {
292 $dbreset = $object->requireDBReset();
295 if (self
::$populateOnce ||
!$dbreset) {
298 self
::$populateOnce = NULL;
300 Civi\Test
::data()->populate();
305 public static function setUpBeforeClass() {
306 static::_populateDB(TRUE);
308 // also set this global hack
309 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
313 * Common setup functions for all unit tests.
315 protected function setUp() {
316 $session = CRM_Core_Session
::singleton();
317 $session->set('userID', NULL);
319 $this->_apiversion
= 3;
322 $this->errorScope
= CRM_Core_TemporaryErrorScope
::useException();
323 // Use a temporary file for STDIN
324 $GLOBALS['stdin'] = tmpfile();
325 if ($GLOBALS['stdin'] === FALSE) {
326 echo "Couldn't open temporary file\n";
330 // Get and save a connection to the database
331 $this->_dbconn
= $this->getConnection();
333 // reload database before each test
334 // $this->_populateDB();
336 // "initialize" CiviCRM to avoid problems when running single tests
337 // FIXME: look at it closer in second stage
339 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
340 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
342 // disable any left-over test extensions
343 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
345 // reset all the caches
346 CRM_Utils_System
::flushCache();
348 // initialize the object once db is loaded
349 \Civi
::$statics = [];
351 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
353 // when running unit tests, use mockup user framework
354 $this->hookClass
= CRM_Utils_Hook
::singleton();
356 // Make sure the DB connection is setup properly
357 $config->userSystem
->setMySQLTimeZone();
358 $env = new CRM_Utils_Check_Component_Env();
359 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
361 // clear permissions stub to not check permissions
362 $config->userPermissionClass
->permissions
= NULL;
364 //flush component settings
365 CRM_Core_Component
::getEnabledComponents(TRUE);
367 $_REQUEST = $_GET = $_POST = [];
368 error_reporting(E_ALL
);
370 $this->renameLabels();
371 $this->_sethtmlGlobals();
375 * Read everything from the datasets directory and insert into the db.
377 public function loadAllFixtures() {
378 $fixturesDir = __DIR__
. '/../../fixtures';
380 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
382 $jsonFiles = glob($fixturesDir . '/*.json');
383 foreach ($jsonFiles as $jsonFixture) {
384 $json = json_decode(file_get_contents($jsonFixture));
385 foreach ($json as $tableName => $vars) {
386 if ($tableName === 'civicrm_contact') {
387 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');
390 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
392 foreach ($vars as $entity) {
393 $keys = $values = [];
394 foreach ($entity as $key => $value) {
396 $values[] = is_numeric($value) ?
$value : "'{$value}'";
398 CRM_Core_DAO
::executeQuery("
399 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
406 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
410 * Load the data that used to be handled by the discontinued dbunit class.
412 * This could do with further tidy up - the initial priority is simply to get rid of
413 * the dbunity package which is no longer supported.
415 * @param string $fileName
417 protected function loadXMLDataSet($fileName) {
418 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
419 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
420 foreach ($xml as $tableName => $element) {
421 if (!empty($element)) {
422 foreach ($element as $row) {
423 $keys = $values = [];
424 if (isset($row['@attributes'])) {
425 foreach ($row['@attributes'] as $key => $value) {
427 $values[] = is_numeric($value) ?
$value : "'{$value}'";
430 elseif (!empty($row)) {
431 // cos we copied it & it is inconsistent....
432 foreach ($row as $key => $value) {
434 $values[] = is_numeric($value) ?
$value : "'{$value}'";
438 if (!empty($values)) {
439 CRM_Core_DAO
::executeQuery("
440 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
446 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
450 * Create default domain contacts for the two domains added during test class.
451 * database population.
453 public function createDomainContacts() {
454 $this->organizationCreate();
455 $this->organizationCreate(['organization_name' => 'Second Domain']);
459 * Common teardown functions for all unit tests.
461 protected function tearDown() {
462 $this->_apiversion
= 3;
463 $this->resetLabels();
465 error_reporting(E_ALL
& ~E_NOTICE
);
466 CRM_Utils_Hook
::singleton()->reset();
467 if ($this->hookClass
) {
468 $this->hookClass
->reset();
470 CRM_Core_Session
::singleton()->reset(1);
473 $this->tx
->rollback()->commit();
476 CRM_Core_Transaction
::forceRollbackIfEnabled();
477 \Civi\Core\Transaction\Manager
::singleton(TRUE);
480 CRM_Core_Transaction
::forceRollbackIfEnabled();
481 \Civi\Core\Transaction\Manager
::singleton(TRUE);
483 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match'];
484 $this->quickCleanup($tablesToTruncate);
485 $this->createDomainContacts();
488 $this->cleanTempDirs();
489 $this->unsetExtensionSystem();
490 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
491 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
495 * Create a batch of external API calls which can
496 * be executed concurrently.
499 * $calls = $this->createExternalAPI()
500 * ->addCall('Contact', 'get', ...)
501 * ->addCall('Contact', 'get', ...)
507 * @return \Civi\API\ExternalBatch
508 * @throws PHPUnit_Framework_SkippedTestError
510 public function createExternalAPI() {
511 global $civicrm_root;
513 'version' => $this->_apiversion
,
517 $calls = new \Civi\API\
ExternalBatch($defaultParams);
519 if (!$calls->isSupported()) {
520 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
527 * Create required data based on $this->entity & $this->params
528 * This is just a way to set up the test data for delete & get functions
529 * so the distinction between set
530 * up & tested functions is clearer
535 public function createTestEntity() {
536 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
540 * @param int $contactTypeId
544 public function contactTypeDelete($contactTypeId) {
545 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
547 throw new Exception('Could not delete contact type');
552 * @param array $params
556 public function membershipTypeCreate($params = []) {
557 CRM_Member_PseudoConstant
::flush('membershipType');
558 CRM_Core_Config
::clearDBCache();
559 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
560 $params = array_merge([
562 'duration_unit' => 'year',
563 'duration_interval' => 1,
564 'period_type' => 'rolling',
565 'member_of_contact_id' => $memberOfOrganization,
567 'financial_type_id' => 2,
570 'visibility' => 'Public',
573 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
575 CRM_Member_PseudoConstant
::flush('membershipType');
576 CRM_Utils_Cache
::singleton()->flush();
578 return $result['id'];
584 * @param array $params
587 * @throws \CRM_Core_Exception
589 public function contactMembershipCreate($params) {
590 $params = array_merge([
591 'join_date' => '2007-01-21',
592 'start_date' => '2007-01-21',
593 'end_date' => '2007-12-21',
594 'source' => 'Payment',
595 'membership_type_id' => 'General',
597 if (!is_numeric($params['membership_type_id'])) {
598 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
599 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
600 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
604 $result = $this->callAPISuccess('Membership', 'create', $params);
605 return $result['id'];
609 * Delete Membership Type.
611 * @param array $params
613 public function membershipTypeDelete($params) {
614 $this->callAPISuccess('MembershipType', 'Delete', $params);
618 * @param int $membershipID
620 public function membershipDelete($membershipID) {
621 $deleteParams = ['id' => $membershipID];
622 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
626 * @param string $name
630 public function membershipStatusCreate($name = 'test member status') {
631 $params['name'] = $name;
632 $params['start_event'] = 'start_date';
633 $params['end_event'] = 'end_date';
634 $params['is_current_member'] = 1;
635 $params['is_active'] = 1;
637 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
638 CRM_Member_PseudoConstant
::flush('membershipStatus');
639 return $result['id'];
643 * @param int $membershipStatusID
645 public function membershipStatusDelete($membershipStatusID) {
646 if (!$membershipStatusID) {
649 $result = $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
652 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
653 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
654 $renewedMembershipEndDate = new DateTime($membershipEndDate);
655 switch ($durationUnit) {
657 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
661 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
662 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
663 $renewedMembershipEndDate->add(new DateInterval('P1D'));
664 $renewedMembershipEndDate->add(new DateInterval('P1M'));
665 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
668 return $renewedMembershipEndDate->format('Y-m-d');
672 * Create a relationship type.
674 * @param array $params
678 * @throws \CRM_Core_Exception
680 public function relationshipTypeCreate($params = []) {
681 $params = array_merge([
682 'name_a_b' => 'Relation 1 for relationship type create',
683 'name_b_a' => 'Relation 2 for relationship type create',
684 'contact_type_a' => 'Individual',
685 'contact_type_b' => 'Organization',
690 $result = $this->callAPISuccess('relationship_type', 'create', $params);
691 CRM_Core_PseudoConstant
::flush('relationshipType');
693 return $result['id'];
697 * Delete Relatinship Type.
699 * @param int $relationshipTypeID
701 public function relationshipTypeDelete($relationshipTypeID) {
702 $params['id'] = $relationshipTypeID;
703 $check = $this->callAPISuccess('relationship_type', 'get', $params);
704 if (!empty($check['count'])) {
705 $this->callAPISuccess('relationship_type', 'delete', $params);
710 * @param array $params
713 * @throws \CRM_Core_Exception
715 public function paymentProcessorTypeCreate($params = NULL) {
716 if (is_null($params)) {
718 'name' => 'API_Test_PP',
719 'title' => 'API Test Payment Processor',
720 'class_name' => 'CRM_Core_Payment_APITest',
721 'billing_mode' => 'form',
727 $result = $this->callAPISuccess('payment_processor_type', 'create', $params);
729 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
731 return $result['id'];
735 * Create test Authorize.net instance.
737 * @param array $params
741 public function paymentProcessorAuthorizeNetCreate($params = []) {
742 $params = array_merge([
743 'name' => 'Authorize',
744 'domain_id' => CRM_Core_Config
::domainID(),
745 'payment_processor_type_id' => 'AuthNet',
746 'title' => 'AuthNet',
751 'user_name' => '4y5BfuW7jm',
752 'password' => '4cAmW927n8uLf5J8',
753 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
754 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
755 'class_name' => 'Payment_AuthorizeNet',
759 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
760 return $result['id'];
764 * Create Participant.
766 * @param array $params
767 * Array of contact id and event id values.
770 * $id of participant created
772 public function participantCreate($params = []) {
773 if (empty($params['contact_id'])) {
774 $params['contact_id'] = $this->individualCreate();
776 if (empty($params['event_id'])) {
777 $event = $this->eventCreate();
778 $params['event_id'] = $event['id'];
783 'register_date' => 20070219,
784 'source' => 'Wimbeldon',
785 'event_level' => 'Payment',
789 $params = array_merge($defaults, $params);
790 $result = $this->callAPISuccess('Participant', 'create', $params);
791 return $result['id'];
795 * Create Payment Processor.
798 * Id Payment Processor
800 public function processorCreate($params = []) {
804 'payment_processor_type_id' => 'Dummy',
805 'financial_account_id' => 12,
809 'url_site' => 'http://dummy.com',
810 'url_recur' => 'http://dummy.com',
813 'payment_instrument_id' => 'Debit Card',
815 $processorParams = array_merge($processorParams, $params);
816 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
817 return $processor['id'];
821 * Create Payment Processor.
823 * @param array $processorParams
825 * @return \CRM_Core_Payment_Dummy
826 * Instance of Dummy Payment Processor
828 * @throws \CiviCRM_API3_Exception
830 public function dummyProcessorCreate($processorParams = []) {
831 $paymentProcessorID = $this->processorCreate($processorParams);
832 // 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
833 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
834 $processorParams['is_test'] = FALSE;
835 $this->processorCreate($processorParams);
836 return System
::singleton()->getById($paymentProcessorID);
840 * Create contribution page.
842 * @param array $params
845 * Array of contribution page
847 public function contributionPageCreate($params = []) {
848 $this->_pageParams
= array_merge([
849 'title' => 'Test Contribution Page',
850 'financial_type_id' => 1,
852 'financial_account_id' => 1,
854 'is_allow_other_amount' => 1,
856 'max_amount' => 1000,
858 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
862 * Create a sample batch.
864 public function batchCreate() {
865 $params = $this->_params
;
866 $params['name'] = $params['title'] = 'Batch_433397';
867 $params['status_id'] = 1;
868 $result = $this->callAPISuccess('batch', 'create', $params);
869 return $result['id'];
875 * @param array $params
878 * result of created tag
880 public function tagCreate($params = []) {
882 'name' => 'New Tag3',
883 'description' => 'This is description for Our New Tag ',
886 $params = array_merge($defaults, $params);
887 $result = $this->callAPISuccess('Tag', 'create', $params);
888 return $result['values'][$result['id']];
895 * Id of the tag to be deleted.
899 public function tagDelete($tagId) {
900 require_once 'api/api.php';
904 $result = $this->callAPISuccess('Tag', 'delete', $params);
905 return $result['id'];
909 * Add entity(s) to the tag
911 * @param array $params
915 public function entityTagAdd($params) {
916 $result = $this->callAPISuccess('entity_tag', 'create', $params);
923 * @param array $params
927 * id of created pledge
929 public function pledgeCreate($params) {
930 $params = array_merge([
931 'pledge_create_date' => date('Ymd'),
932 'start_date' => date('Ymd'),
933 'scheduled_date' => date('Ymd'),
935 'pledge_status_id' => '2',
936 'financial_type_id' => '1',
937 'pledge_original_installment_amount' => 20,
938 'frequency_interval' => 5,
939 'frequency_unit' => 'year',
940 'frequency_day' => 15,
945 $result = $this->callAPISuccess('Pledge', 'create', $params);
946 return $result['id'];
950 * Delete contribution.
952 * @param int $pledgeId
954 public function pledgeDelete($pledgeId) {
956 'pledge_id' => $pledgeId,
958 $this->callAPISuccess('Pledge', 'delete', $params);
962 * Create contribution.
964 * @param array $params
965 * Array of parameters.
968 * id of created contribution
969 * @throws \CRM_Core_Exception
971 public function contributionCreate($params) {
973 $params = array_merge([
975 'receive_date' => date('Ymd'),
976 'total_amount' => 100.00,
977 'fee_amount' => 5.00,
978 'financial_type_id' => 1,
979 'payment_instrument_id' => 1,
980 'non_deductible_amount' => 10.00,
982 'contribution_status_id' => 1,
985 $result = $this->callAPISuccess('contribution', 'create', $params);
986 return $result['id'];
990 * Delete contribution.
992 * @param int $contributionId
995 * @throws \CRM_Core_Exception
997 public function contributionDelete($contributionId) {
999 'contribution_id' => $contributionId,
1001 $result = $this->callAPISuccess('contribution', 'delete', $params);
1008 * @param array $params
1009 * Name-value pair for an event.
1012 * @throws \CRM_Core_Exception
1014 public function eventCreate($params = []) {
1015 // if no contact was passed, make up a dummy event creator
1016 if (!isset($params['contact_id'])) {
1017 $params['contact_id'] = $this->_contactCreate([
1018 'contact_type' => 'Individual',
1019 'first_name' => 'Event',
1020 'last_name' => 'Creator',
1024 // set defaults for missing params
1025 $params = array_merge([
1026 'title' => 'Annual CiviCRM meet',
1027 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1028 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1029 'event_type_id' => 1,
1031 'start_date' => 20081021,
1032 'end_date' => 20081023,
1033 'is_online_registration' => 1,
1034 'registration_start_date' => 20080601,
1035 'registration_end_date' => 20081015,
1036 'max_participants' => 100,
1037 'event_full_text' => 'Sorry! We are already full',
1040 'is_show_location' => 0,
1041 'is_email_confirm' => 1,
1044 return $this->callAPISuccess('Event', 'create', $params);
1048 * Create a paid event.
1050 * @param array $params
1052 * @param array $options
1054 * @param string $key
1055 * Index for storing event ID in ids array.
1059 * @throws \CRM_Core_Exception
1061 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1062 $event = $this->eventCreate($params);
1063 $this->ids
['event'][$key] = (int) $event['id'];
1064 $this->priceSetID
= $this->ids
['PriceSet'][] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1065 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->priceSetID
);
1066 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->priceSetID
, TRUE, FALSE);
1067 $priceSet = CRM_Utils_Array
::value($this->priceSetID
, $priceSet);
1068 $this->eventFeeBlock
= CRM_Utils_Array
::value('fields', $priceSet);
1080 public function eventDelete($id) {
1084 return $this->callAPISuccess('event', 'delete', $params);
1088 * Delete participant.
1090 * @param int $participantID
1094 public function participantDelete($participantID) {
1096 'id' => $participantID,
1098 $check = $this->callAPISuccess('Participant', 'get', $params);
1099 if ($check['count'] > 0) {
1100 return $this->callAPISuccess('Participant', 'delete', $params);
1105 * Create participant payment.
1107 * @param int $participantID
1108 * @param int $contributionID
1111 * $id of created payment
1113 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1114 //Create Participant Payment record With Values
1116 'participant_id' => $participantID,
1117 'contribution_id' => $contributionID,
1120 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1121 return $result['id'];
1125 * Delete participant payment.
1127 * @param int $paymentID
1129 public function participantPaymentDelete($paymentID) {
1133 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1139 * @param int $contactID
1142 * location id of created location
1144 public function locationAdd($contactID) {
1147 'location_type' => 'New Location Type',
1149 'name' => 'Saint Helier St',
1150 'county' => 'Marin',
1151 'country' => 'UNITED STATES',
1152 'state_province' => 'Michigan',
1153 'supplemental_address_1' => 'Hallmark Ct',
1154 'supplemental_address_2' => 'Jersey Village',
1155 'supplemental_address_3' => 'My Town',
1160 'contact_id' => $contactID,
1161 'address' => $address,
1162 'location_format' => '2.0',
1163 'location_type' => 'New Location Type',
1166 $result = $this->callAPISuccess('Location', 'create', $params);
1171 * Delete Locations of contact.
1173 * @param array $params
1176 public function locationDelete($params) {
1177 $this->callAPISuccess('Location', 'delete', $params);
1181 * Add a Location Type.
1183 * @param array $params
1185 * @return CRM_Core_DAO_LocationType
1186 * location id of created location
1188 public function locationTypeCreate($params = NULL) {
1189 if ($params === NULL) {
1191 'name' => 'New Location Type',
1192 'vcard_name' => 'New Location Type',
1193 'description' => 'Location Type for Delete',
1198 $locationType = new CRM_Core_DAO_LocationType();
1199 $locationType->copyValues($params);
1200 $locationType->save();
1201 // clear getfields cache
1202 CRM_Core_PseudoConstant
::flush();
1203 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1204 return $locationType;
1208 * Delete a Location Type.
1210 * @param int $locationTypeId
1212 public function locationTypeDelete($locationTypeId) {
1213 $locationType = new CRM_Core_DAO_LocationType();
1214 $locationType->id
= $locationTypeId;
1215 $locationType->delete();
1221 * @param array $params
1223 * @return CRM_Core_DAO_Mapping
1224 * Mapping id of created mapping
1226 public function mappingCreate($params = NULL) {
1227 if ($params === NULL) {
1229 'name' => 'Mapping name',
1230 'description' => 'Mapping description',
1231 // 'Export Contact' mapping.
1232 'mapping_type_id' => 7,
1236 $mapping = new CRM_Core_DAO_Mapping();
1237 $mapping->copyValues($params);
1239 // clear getfields cache
1240 CRM_Core_PseudoConstant
::flush();
1241 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1248 * @param int $mappingId
1250 public function mappingDelete($mappingId) {
1251 $mapping = new CRM_Core_DAO_Mapping();
1252 $mapping->id
= $mappingId;
1257 * Prepare class for ACLs.
1259 protected function prepareForACLs() {
1260 $config = CRM_Core_Config
::singleton();
1261 $config->userPermissionClass
->permissions
= [];
1267 protected function cleanUpAfterACLs() {
1268 CRM_Utils_Hook
::singleton()->reset();
1269 $tablesToTruncate = [
1271 'civicrm_acl_cache',
1272 'civicrm_acl_entity_role',
1273 'civicrm_acl_contact_cache',
1275 $this->quickCleanup($tablesToTruncate);
1276 $config = CRM_Core_Config
::singleton();
1277 unset($config->userPermissionClass
->permissions
);
1281 * Create a smart group.
1283 * By default it will be a group of households.
1285 * @param array $smartGroupParams
1286 * @param array $groupParams
1287 * @param string $contactType
1291 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1292 $smartGroupParams = array_merge([
1293 'formValues' => ['contact_type' => ['IN' => [$contactType]]],
1296 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1298 $groupParams['saved_search_id'] = $savedSearch->id
;
1299 return $this->groupCreate($groupParams);
1305 * @param array $params
1307 public function uFFieldCreate($params = []) {
1308 $params = array_merge([
1310 'field_name' => 'first_name',
1313 'visibility' => 'Public Pages and Listings',
1314 'is_searchable' => '1',
1315 'label' => 'first_name',
1316 'field_type' => 'Individual',
1319 $this->callAPISuccess('uf_field', 'create', $params);
1323 * Add a UF Join Entry.
1325 * @param array $params
1328 * $id of created UF Join
1330 public function ufjoinCreate($params = NULL) {
1331 if ($params === NULL) {
1334 'module' => 'CiviEvent',
1335 'entity_table' => 'civicrm_event',
1341 $result = $this->callAPISuccess('uf_join', 'create', $params);
1346 * @param array $params
1347 * Optional parameters.
1348 * @param bool $reloadConfig
1349 * While enabling CiviCampaign component, we shouldn't always forcibly
1350 * reload config as this hinder hook call in test environment
1355 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1356 $this->enableCiviCampaign($reloadConfig);
1357 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1358 'name' => 'big_campaign',
1359 'title' => 'Campaign',
1361 return $campaign['id'];
1365 * Create Group for a contact.
1367 * @param int $contactId
1369 public function contactGroupCreate($contactId) {
1371 'contact_id.1' => $contactId,
1375 $this->callAPISuccess('GroupContact', 'Create', $params);
1379 * Delete Group for a contact.
1381 * @param int $contactId
1383 public function contactGroupDelete($contactId) {
1385 'contact_id.1' => $contactId,
1388 $this->civicrm_api('GroupContact', 'Delete', $params);
1394 * @param array $params
1398 public function activityCreate($params = []) {
1399 $params = array_merge([
1400 'subject' => 'Discussion on warm beer',
1401 'activity_date_time' => date('Ymd'),
1403 'location' => 'Baker Street',
1404 'details' => 'Lets schedule a meeting',
1406 'activity_type_id' => 'Meeting',
1408 if (!isset($params['source_contact_id'])) {
1409 $params['source_contact_id'] = $this->individualCreate();
1411 if (!isset($params['target_contact_id'])) {
1412 $params['target_contact_id'] = $this->individualCreate([
1413 'first_name' => 'Julia',
1414 'last_name' => 'Anderson',
1416 'email' => 'julia_anderson@civicrm.org',
1417 'contact_type' => 'Individual',
1420 if (!isset($params['assignee_contact_id'])) {
1421 $params['assignee_contact_id'] = $params['target_contact_id'];
1424 $result = civicrm_api3('Activity', 'create', $params);
1426 $result['target_contact_id'] = $params['target_contact_id'];
1427 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1432 * Create an activity type.
1434 * @param array $params
1439 public function activityTypeCreate($params) {
1440 return $this->callAPISuccess('ActivityType', 'create', $params);
1444 * Delete activity type.
1446 * @param int $activityTypeId
1447 * Id of the activity type.
1451 public function activityTypeDelete($activityTypeId) {
1452 $params['activity_type_id'] = $activityTypeId;
1453 return $this->callAPISuccess('ActivityType', 'delete', $params);
1457 * Create custom group.
1459 * @param array $params
1463 public function customGroupCreate($params = []) {
1465 'title' => 'new custom group',
1466 'extends' => 'Contact',
1468 'style' => 'Inline',
1472 $params = array_merge($defaults, $params);
1474 return $this->callAPISuccess('custom_group', 'create', $params);
1478 * Existing function doesn't allow params to be over-ridden so need a new one
1479 * this one allows you to only pass in the params you want to change
1481 * @param array $params
1485 public function CustomGroupCreateByParams($params = []) {
1487 'title' => "API Custom Group",
1488 'extends' => 'Contact',
1490 'style' => 'Inline',
1493 $params = array_merge($defaults, $params);
1494 return $this->callAPISuccess('custom_group', 'create', $params);
1498 * Create custom group with multi fields.
1500 * @param array $params
1504 public function CustomGroupMultipleCreateByParams($params = []) {
1509 $params = array_merge($defaults, $params);
1510 return $this->CustomGroupCreateByParams($params);
1514 * Create custom group with multi fields.
1516 * @param array $params
1520 public function CustomGroupMultipleCreateWithFields($params = []) {
1521 // also need to pass on $params['custom_field'] if not set but not in place yet
1523 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1524 $ids['custom_group_id'] = $customGroup['id'];
1526 $customField = $this->customFieldCreate([
1527 'custom_group_id' => $ids['custom_group_id'],
1528 'label' => 'field_1' . $ids['custom_group_id'],
1532 $ids['custom_field_id'][] = $customField['id'];
1534 $customField = $this->customFieldCreate([
1535 'custom_group_id' => $ids['custom_group_id'],
1536 'default_value' => '',
1537 'label' => 'field_2' . $ids['custom_group_id'],
1540 $ids['custom_field_id'][] = $customField['id'];
1542 $customField = $this->customFieldCreate([
1543 'custom_group_id' => $ids['custom_group_id'],
1544 'default_value' => '',
1545 'label' => 'field_3' . $ids['custom_group_id'],
1548 $ids['custom_field_id'][] = $customField['id'];
1554 * Create a custom group with a single text custom field. See
1555 * participant:testCreateWithCustom for how to use this
1557 * @param string $function
1559 * @param string $filename
1563 * ids of created objects
1565 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1566 $params = ['title' => $function];
1567 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1568 $params['extends'] = $entity ?
$entity : 'Contact';
1569 $customGroup = $this->customGroupCreate($params);
1570 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1571 CRM_Core_PseudoConstant
::flush();
1573 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1577 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1578 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1580 * @param string $function
1582 * @param string $filename
1586 * ids of created objects
1588 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1589 $params = ['title' => $function];
1590 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1591 $params['extends'] = $entity ?
$entity : 'Contact';
1592 $customGroup = $this->customGroupCreate($params);
1593 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1594 CRM_Core_PseudoConstant
::flush();
1596 'defaultValue' => 'Default Value',
1597 'lowercasevalue' => 'Lowercase Value',
1598 1 => 'Integer Value',
1601 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1602 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1603 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1604 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1605 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1606 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1607 foreach ($options as $option_value => $option_label) {
1608 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1609 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1613 'custom_group_id' => $customGroup['id'],
1614 'custom_field_id' => $customField['id'],
1615 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1616 'custom_field_group_options' => $options,
1621 * Delete custom group.
1623 * @param int $customGroupID
1627 public function customGroupDelete($customGroupID) {
1628 $params['id'] = $customGroupID;
1629 return $this->callAPISuccess('custom_group', 'delete', $params);
1633 * Create custom field.
1635 * @param array $params
1636 * (custom_group_id) is required.
1640 public function customFieldCreate($params) {
1641 $params = array_merge([
1642 'label' => 'Custom Field',
1643 'data_type' => 'String',
1644 'html_type' => 'Text',
1645 'is_searchable' => 1,
1647 'default_value' => 'defaultValue',
1650 $result = $this->callAPISuccess('custom_field', 'create', $params);
1651 // these 2 functions are called with force to flush static caches
1652 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1653 CRM_Core_Component
::getEnabledComponents(1);
1658 * Delete custom field.
1660 * @param int $customFieldID
1664 public function customFieldDelete($customFieldID) {
1666 $params['id'] = $customFieldID;
1667 return $this->callAPISuccess('custom_field', 'delete', $params);
1677 public function noteCreate($cId) {
1679 'entity_table' => 'civicrm_contact',
1680 'entity_id' => $cId,
1681 'note' => 'hello I am testing Note',
1682 'contact_id' => $cId,
1683 'modified_date' => date('Ymd'),
1684 'subject' => 'Test Note',
1687 return $this->callAPISuccess('Note', 'create', $params);
1691 * Enable CiviCampaign Component.
1693 * @param bool $reloadConfig
1694 * Force relaod config or not
1696 public function enableCiviCampaign($reloadConfig = TRUE) {
1697 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1698 if ($reloadConfig) {
1699 // force reload of config object
1700 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1702 //flush cache by calling with reset
1703 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1707 * Create custom field with Option Values.
1709 * @param array $customGroup
1710 * @param string $name
1711 * Name of custom field.
1712 * @param array $extraParams
1713 * Additional parameters to pass through.
1717 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1719 'custom_group_id' => $customGroup['id'],
1720 'name' => 'test_custom_group',
1721 'label' => 'Country',
1722 'html_type' => 'Select',
1723 'data_type' => 'String',
1726 'is_searchable' => 0,
1732 'name' => 'option_group1',
1733 'label' => 'option_group_label1',
1737 'option_label' => ['Label1', 'Label2'],
1738 'option_value' => ['value1', 'value2'],
1739 'option_name' => [$name . '_1', $name . '_2'],
1740 'option_weight' => [1, 2],
1741 'option_status' => [1, 1],
1744 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1746 return $this->callAPISuccess('custom_field', 'create', $params);
1754 public function confirmEntitiesDeleted($entities) {
1755 foreach ($entities as $entity) {
1757 $result = $this->callAPISuccess($entity, 'Get', []);
1758 if ($result['error'] == 1 ||
$result['count'] > 0) {
1759 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1767 * Quick clean by emptying tables created for the test.
1769 * @param array $tablesToTruncate
1770 * @param bool $dropCustomValueTables
1772 * @throws \CRM_Core_Exception
1774 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1776 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1778 if ($dropCustomValueTables) {
1779 $optionGroupResult = CRM_Core_DAO
::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1780 while ($optionGroupResult->fetch()) {
1781 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1782 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1783 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1784 // generic cleanup routine.
1785 if (!empty($optionGroupResult->option_group_id
) && $optionGroupResult->option_group_id
> 50) {
1786 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id
);
1789 $tablesToTruncate[] = 'civicrm_custom_group';
1790 $tablesToTruncate[] = 'civicrm_custom_field';
1793 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1795 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1796 foreach ($tablesToTruncate as $table) {
1797 $sql = "TRUNCATE TABLE $table";
1798 CRM_Core_DAO
::executeQuery($sql);
1800 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1802 if ($dropCustomValueTables) {
1803 $dbName = self
::getDBName();
1805 SELECT TABLE_NAME as tableName
1806 FROM INFORMATION_SCHEMA.TABLES
1807 WHERE TABLE_SCHEMA = '{$dbName}'
1808 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1811 $tableDAO = CRM_Core_DAO
::executeQuery($query);
1812 while ($tableDAO->fetch()) {
1813 $sql = "DROP TABLE {$tableDAO->tableName}";
1814 CRM_Core_DAO
::executeQuery($sql);
1820 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1822 * @throws \CRM_Core_Exception
1824 public function quickCleanUpFinancialEntities() {
1825 $tablesToTruncate = [
1827 'civicrm_activity_contact',
1828 'civicrm_contribution',
1829 'civicrm_contribution_soft',
1830 'civicrm_contribution_product',
1831 'civicrm_financial_trxn',
1832 'civicrm_financial_item',
1833 'civicrm_contribution_recur',
1834 'civicrm_line_item',
1835 'civicrm_contribution_page',
1836 'civicrm_payment_processor',
1837 'civicrm_entity_financial_trxn',
1838 'civicrm_membership',
1839 'civicrm_membership_type',
1840 'civicrm_membership_payment',
1841 'civicrm_membership_log',
1842 'civicrm_membership_block',
1844 'civicrm_participant',
1845 'civicrm_participant_payment',
1847 'civicrm_pledge_block',
1848 'civicrm_pledge_payment',
1849 'civicrm_price_set_entity',
1850 'civicrm_price_field_value',
1851 'civicrm_price_field',
1853 $this->quickCleanup($tablesToTruncate);
1854 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1855 $this->restoreDefaultPriceSetConfig();
1856 $this->disableTaxAndInvoicing();
1857 $this->setCurrencySeparators(',');
1858 CRM_Core_PseudoConstant
::flush('taxRates');
1859 System
::singleton()->flushProcessors();
1860 // @fixme this parameter is leaking - it should not be defined as a class static
1861 // but for now we just handle in tear down.
1862 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1866 * Reset the price set config so results exist.
1868 public function restoreDefaultPriceSetConfig() {
1869 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1870 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1871 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)");
1872 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)");
1876 * Recreate default membership types.
1878 public function restoreMembershipTypes() {
1879 CRM_Core_DAO
::executeQuery(
1880 "REPLACE INTO civicrm_membership_type
1881 (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)
1883 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1884 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1885 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1890 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1891 * Default behaviour is to also delete the entity
1892 * @param array $params
1893 * Params array to check against.
1895 * Id of the entity concerned.
1896 * @param string $entity
1897 * Name of entity concerned (e.g. membership).
1898 * @param bool $delete
1899 * Should the entity be deleted as part of this check.
1900 * @param string $errorText
1901 * Text to print on error.
1905 * @param array $params
1908 * @param int $delete
1909 * @param string $errorText
1911 * @throws CRM_Core_Exception
1913 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1915 $result = $this->callAPISuccessGetSingle($entity, [
1920 $this->callAPISuccess($entity, 'Delete', [
1924 $dateFields = $keys = $dateTimeFields = [];
1925 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1926 foreach ($fields['values'] as $field => $settings) {
1927 if (array_key_exists($field, $result)) {
1928 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
1931 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
1933 $type = CRM_Utils_Array
::value('type', $settings);
1934 if ($type == CRM_Utils_Type
::T_DATE
) {
1935 $dateFields[] = $settings['name'];
1936 // we should identify both real names & unique names as dates
1937 if ($field != $settings['name']) {
1938 $dateFields[] = $field;
1941 if ($type == CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
1942 $dateTimeFields[] = $settings['name'];
1943 // we should identify both real names & unique names as dates
1944 if ($field != $settings['name']) {
1945 $dateTimeFields[] = $field;
1950 if (strtolower($entity) == 'contribution') {
1951 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1952 // this is not returned in id format
1953 unset($params['payment_instrument_id']);
1954 $params['contribution_source'] = $params['source'];
1955 unset($params['source']);
1958 foreach ($params as $key => $value) {
1959 if ($key == 'version' ||
substr($key, 0, 3) == 'api' ||
!array_key_exists($keys[$key], $result)) {
1962 if (in_array($key, $dateFields)) {
1963 $value = date('Y-m-d', strtotime($value));
1964 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1966 if (in_array($key, $dateTimeFields)) {
1967 $value = date('Y-m-d H:i:s', strtotime($value));
1968 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
1970 $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);
1975 * Get formatted values in the actual and expected result.
1977 * @param array $actual
1978 * Actual calculated values.
1979 * @param array $expected
1982 public function checkArrayEquals(&$actual, &$expected) {
1983 self
::unsetId($actual);
1984 self
::unsetId($expected);
1985 $this->assertEquals($expected, $actual);
1989 * Unset the key 'id' from the array
1991 * @param array $unformattedArray
1992 * The array from which the 'id' has to be unset.
1994 public static function unsetId(&$unformattedArray) {
1995 $formattedArray = [];
1996 if (array_key_exists('id', $unformattedArray)) {
1997 unset($unformattedArray['id']);
1999 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
2000 foreach ($unformattedArray['values'] as $key => $value) {
2001 if (is_array($value)) {
2002 foreach ($value as $k => $v) {
2008 elseif ($key == 'id') {
2009 $unformattedArray[$key];
2011 $formattedArray = [$value];
2013 $unformattedArray['values'] = $formattedArray;
2018 * Helper to enable/disable custom directory support
2020 * @param array $customDirs
2022 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2023 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2025 public function customDirectories($customDirs) {
2026 $config = CRM_Core_Config
::singleton();
2028 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2029 unset($config->customPHPPathDir
);
2031 elseif ($customDirs['php_path'] === TRUE) {
2032 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2035 $config->customPHPPathDir
= $php_path;
2038 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2039 unset($config->customTemplateDir
);
2041 elseif ($customDirs['template_path'] === TRUE) {
2042 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2045 $config->customTemplateDir
= $template_path;
2050 * Generate a temporary folder.
2052 * @param string $prefix
2056 public function createTempDir($prefix = 'test-') {
2057 $tempDir = CRM_Utils_File
::tempdir($prefix);
2058 $this->tempDirs
[] = $tempDir;
2062 public function cleanTempDirs() {
2063 if (!is_array($this->tempDirs
)) {
2064 // fix test errors where this is not set
2067 foreach ($this->tempDirs
as $tempDir) {
2068 if (is_dir($tempDir)) {
2069 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2075 * Temporarily replace the singleton extension with a different one.
2077 * @param \CRM_Extension_System $system
2079 public function setExtensionSystem(CRM_Extension_System
$system) {
2080 if ($this->origExtensionSystem
== NULL) {
2081 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2083 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2086 public function unsetExtensionSystem() {
2087 if ($this->origExtensionSystem
!== NULL) {
2088 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2089 $this->origExtensionSystem
= NULL;
2094 * Temporarily alter the settings-metadata to add a mock setting.
2096 * WARNING: The setting metadata will disappear on the next cache-clear.
2102 public function setMockSettingsMetaData($extras) {
2103 CRM_Utils_Hook
::singleton()
2104 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2105 $metadata = array_merge($metadata, $extras);
2108 Civi
::service('settings_manager')->flush();
2110 $fields = $this->callAPISuccess('setting', 'getfields', []);
2111 foreach ($extras as $key => $spec) {
2112 $this->assertNotEmpty($spec['title']);
2113 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2118 * @param string $name
2120 public function financialAccountDelete($name) {
2121 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2122 $financialAccount->name
= $name;
2123 if ($financialAccount->find(TRUE)) {
2124 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2125 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2126 $entityFinancialType->delete();
2127 $financialAccount->delete();
2132 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2133 * (NB unclear if this is still required)
2135 public function _sethtmlGlobals() {
2136 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2138 'html_quickform_rule_required',
2139 'HTML/QuickForm/Rule/Required.php',
2142 'html_quickform_rule_range',
2143 'HTML/QuickForm/Rule/Range.php',
2146 'html_quickform_rule_range',
2147 'HTML/QuickForm/Rule/Range.php',
2150 'html_quickform_rule_range',
2151 'HTML/QuickForm/Rule/Range.php',
2154 'html_quickform_rule_email',
2155 'HTML/QuickForm/Rule/Email.php',
2158 'html_quickform_rule_regex',
2159 'HTML/QuickForm/Rule/Regex.php',
2162 'html_quickform_rule_regex',
2163 'HTML/QuickForm/Rule/Regex.php',
2166 'html_quickform_rule_regex',
2167 'HTML/QuickForm/Rule/Regex.php',
2170 'html_quickform_rule_regex',
2171 'HTML/QuickForm/Rule/Regex.php',
2173 'nopunctuation' => [
2174 'html_quickform_rule_regex',
2175 'HTML/QuickForm/Rule/Regex.php',
2178 'html_quickform_rule_regex',
2179 'HTML/QuickForm/Rule/Regex.php',
2182 'html_quickform_rule_callback',
2183 'HTML/QuickForm/Rule/Callback.php',
2186 'html_quickform_rule_compare',
2187 'HTML/QuickForm/Rule/Compare.php',
2190 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2191 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2193 'HTML/QuickForm/group.php',
2194 'HTML_QuickForm_group',
2197 'HTML/QuickForm/hidden.php',
2198 'HTML_QuickForm_hidden',
2201 'HTML/QuickForm/reset.php',
2202 'HTML_QuickForm_reset',
2205 'HTML/QuickForm/checkbox.php',
2206 'HTML_QuickForm_checkbox',
2209 'HTML/QuickForm/file.php',
2210 'HTML_QuickForm_file',
2213 'HTML/QuickForm/image.php',
2214 'HTML_QuickForm_image',
2217 'HTML/QuickForm/password.php',
2218 'HTML_QuickForm_password',
2221 'HTML/QuickForm/radio.php',
2222 'HTML_QuickForm_radio',
2225 'HTML/QuickForm/button.php',
2226 'HTML_QuickForm_button',
2229 'HTML/QuickForm/submit.php',
2230 'HTML_QuickForm_submit',
2233 'HTML/QuickForm/select.php',
2234 'HTML_QuickForm_select',
2237 'HTML/QuickForm/hiddenselect.php',
2238 'HTML_QuickForm_hiddenselect',
2241 'HTML/QuickForm/text.php',
2242 'HTML_QuickForm_text',
2245 'HTML/QuickForm/textarea.php',
2246 'HTML_QuickForm_textarea',
2249 'HTML/QuickForm/fckeditor.php',
2250 'HTML_QuickForm_FCKEditor',
2253 'HTML/QuickForm/tinymce.php',
2254 'HTML_QuickForm_TinyMCE',
2257 'HTML/QuickForm/dojoeditor.php',
2258 'HTML_QuickForm_dojoeditor',
2261 'HTML/QuickForm/link.php',
2262 'HTML_QuickForm_link',
2265 'HTML/QuickForm/advcheckbox.php',
2266 'HTML_QuickForm_advcheckbox',
2269 'HTML/QuickForm/date.php',
2270 'HTML_QuickForm_date',
2273 'HTML/QuickForm/static.php',
2274 'HTML_QuickForm_static',
2277 'HTML/QuickForm/header.php',
2278 'HTML_QuickForm_header',
2281 'HTML/QuickForm/html.php',
2282 'HTML_QuickForm_html',
2285 'HTML/QuickForm/hierselect.php',
2286 'HTML_QuickForm_hierselect',
2289 'HTML/QuickForm/autocomplete.php',
2290 'HTML_QuickForm_autocomplete',
2293 'HTML/QuickForm/xbutton.php',
2294 'HTML_QuickForm_xbutton',
2296 'advmultiselect' => [
2297 'HTML/QuickForm/advmultiselect.php',
2298 'HTML_QuickForm_advmultiselect',
2304 * Set up an acl allowing contact to see 2 specified groups
2305 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2307 * You need to have pre-created these groups & created the user e.g
2308 * $this->createLoggedInUser();
2309 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2310 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2312 * @param bool $isProfile
2314 public function setupACL($isProfile = FALSE) {
2316 $_REQUEST = $this->_params
;
2318 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2319 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2320 $ov = new CRM_Core_DAO_OptionValue();
2321 $ov->option_group_id
= $optionGroupID;
2323 if ($ov->find(TRUE)) {
2324 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2326 $optionValue = $this->callAPISuccess('option_value', 'create', [
2327 'option_group_id' => $optionGroupID,
2328 'label' => 'pick me',
2332 CRM_Core_DAO
::executeQuery("
2333 TRUNCATE civicrm_acl_cache
2336 CRM_Core_DAO
::executeQuery("
2337 TRUNCATE civicrm_acl_contact_cache
2340 CRM_Core_DAO
::executeQuery("
2341 INSERT INTO civicrm_acl_entity_role (
2342 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2343 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2347 CRM_Core_DAO
::executeQuery("
2348 INSERT INTO civicrm_acl (
2349 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2352 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2357 CRM_Core_DAO
::executeQuery("
2358 INSERT INTO civicrm_acl (
2359 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2362 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2366 CRM_Core_DAO
::executeQuery("
2367 INSERT INTO civicrm_acl (
2368 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2371 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2376 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2377 $this->callAPISuccess('group_contact', 'create', [
2378 'group_id' => $this->_permissionedGroup
,
2379 'contact_id' => $this->_loggedInUser
,
2384 CRM_ACL_BAO_Cache
::resetCache();
2385 CRM_ACL_API
::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2390 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2392 public function offsetDefaultPriceSet() {
2393 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2394 $firstID = $contributionPriceSet['id'];
2395 $this->callAPISuccess('price_set', 'create', [
2396 'id' => $contributionPriceSet['id'],
2400 unset($contributionPriceSet['id']);
2401 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2402 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2403 'price_set_id' => $firstID,
2404 'options' => ['limit' => 1],
2406 unset($priceField['id']);
2407 $priceField['price_set_id'] = $newPriceSet['id'];
2408 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2409 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2410 'price_set_id' => $firstID,
2412 'options' => ['limit' => 1],
2415 unset($priceFieldValue['id']);
2416 //create some padding to use up ids
2417 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2418 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2419 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2423 * Create an instance of the paypal processor.
2425 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2426 * this parent class & we don't have a structure for that yet
2427 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2428 * & the best protection against that is the functions this class affords
2430 * @param array $params
2432 * @return int $result['id'] payment processor id
2434 public function paymentProcessorCreate($params = []) {
2435 $params = array_merge([
2437 'domain_id' => CRM_Core_Config
::domainID(),
2438 'payment_processor_type_id' => 'PayPal',
2442 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2443 'password' => '1183377788',
2444 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2445 'url_site' => 'https://www.sandbox.paypal.com/',
2446 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2447 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2448 'class_name' => 'Payment_PayPalImpl',
2449 'billing_mode' => 3,
2450 'financial_type_id' => 1,
2451 'financial_account_id' => 12,
2452 // Credit card = 1 so can pass 'by accident'.
2453 'payment_instrument_id' => 'Debit Card',
2455 if (!is_numeric($params['payment_processor_type_id'])) {
2456 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2458 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2459 'name' => $params['payment_processor_type_id'],
2463 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2464 return $result['id'];
2468 * Set up initial recurring payment allowing subsequent IPN payments.
2470 * @param array $recurParams (Optional)
2471 * @param array $contributionParams (Optional)
2473 * @throws \CRM_Core_Exception
2475 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2476 $contributionParams = array_merge([
2477 'total_amount' => '200',
2478 'invoice_id' => $this->_invoiceID
,
2479 'financial_type_id' => 'Donation',
2480 'contribution_status_id' => 'Pending',
2481 'contact_id' => $this->_contactID
,
2482 'contribution_page_id' => $this->_contributionPageID
,
2483 'payment_processor_id' => $this->_paymentProcessorID
,
2485 'receive_date' => '2019-07-25 07:34:23',
2486 'skipCleanMoney' => TRUE,
2487 ], $contributionParams);
2488 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2489 'contact_id' => $this->_contactID
,
2492 'installments' => 5,
2493 'frequency_unit' => 'Month',
2494 'frequency_interval' => 1,
2495 'invoice_id' => $this->_invoiceID
,
2496 'contribution_status_id' => 2,
2497 'payment_processor_id' => $this->_paymentProcessorID
,
2498 // processor provided ID - use contact ID as proxy.
2499 'processor_id' => $this->_contactID
,
2500 'api.Order.create' => $contributionParams,
2501 ], $recurParams))['values'][0];
2502 $this->_contributionRecurID
= $contributionRecur['id'];
2503 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2504 $this->ids
['Contribution'][0] = $this->_contributionID
;
2508 * 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
2510 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2512 * @throws \CRM_Core_Exception
2514 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2515 $membershipParams = $recurParams = [];
2516 if (!empty($params['duration_unit'])) {
2517 $membershipParams['duration_unit'] = $params['duration_unit'];
2519 if (!empty($params['frequency_unit'])) {
2520 $recurParams['frequency_unit'] = $params['frequency_unit'];
2523 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2524 //create a contribution so our membership & contribution don't both have id = 1
2525 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2526 $this->contributionCreate([
2527 'contact_id' => $this->_contactID
,
2529 'financial_type_id' => 1,
2530 'invoice_id' => 'abcd',
2532 'receive_date' => '2019-07-25 07:34:23',
2536 $this->ids
['membership'] = $this->callAPISuccess('Membership', 'create', [
2537 'contact_id' => $this->_contactID
,
2538 'membership_type_id' => $this->ids
['membership_type'],
2539 'format.only_id' => TRUE,
2540 'source' => 'Payment',
2541 'skipLineItem' => TRUE,
2543 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2548 'entity_table' => 'civicrm_membership',
2549 'entity_id' => $this->ids
['membership'],
2550 'label' => 'General',
2552 'unit_price' => 200,
2553 'line_total' => 200,
2554 'financial_type_id' => 1,
2555 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2557 'label' => 'Membership Amount',
2558 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2560 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2562 'label' => 'General',
2563 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2570 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids
['membership'], 'contribution_recur_id' => $this->_contributionRecurID
]);
2578 public function CiviUnitTestCase_fatalErrorHandler($message) {
2579 throw new Exception("{$message['message']}: {$message['code']}");
2583 * Wrap the entire test case in a transaction.
2585 * Only subsequent DB statements will be wrapped in TX -- this cannot
2586 * retroactively wrap old DB statements. Therefore, it makes sense to
2587 * call this at the beginning of setUp().
2589 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2590 * this option does not work with, e.g., custom-data.
2592 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2593 * if TRUNCATE or ALTER is called while using a transaction.
2596 * Whether to use nesting or reference-counting.
2598 public function useTransaction($nest = TRUE) {
2600 $this->tx
= new CRM_Core_Transaction($nest);
2601 $this->tx
->rollback();
2606 * Assert the attachment exists.
2608 * @param bool $exists
2609 * @param array $apiResult
2611 protected function assertAttachmentExistence($exists, $apiResult) {
2612 $fileId = $apiResult['id'];
2613 $this->assertTrue(is_numeric($fileId));
2614 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2615 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2616 1 => [$fileId, 'Int'],
2618 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2619 1 => [$fileId, 'Int'],
2624 * Assert 2 sql strings are the same, ignoring double spaces.
2626 * @param string $expectedSQL
2627 * @param string $actualSQL
2628 * @param string $message
2630 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2631 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2632 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2633 $this->assertEquals($expected, $actual, $message);
2637 * Create a price set for an event.
2639 * @param int $feeTotal
2640 * @param int $minAmt
2641 * @param string $type
2643 * @param array $options
2647 * @throws \CRM_Core_Exception
2649 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2650 // creating price set, price field
2651 $paramsSet['title'] = 'Price Set';
2652 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2653 $paramsSet['is_active'] = FALSE;
2654 $paramsSet['extends'] = 1;
2655 $paramsSet['min_amount'] = $minAmt;
2657 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2658 $this->_ids
['price_set'] = $priceSet->id
;
2661 'label' => 'Price Field',
2662 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2663 'html_type' => $type,
2664 'price' => $feeTotal,
2665 'option_label' => ['1' => 'Price Field'],
2666 'option_value' => ['1' => $feeTotal],
2667 'option_name' => ['1' => $feeTotal],
2668 'option_weight' => ['1' => 1],
2669 'option_amount' => ['1' => 1],
2670 'is_display_amounts' => 1,
2672 'options_per_line' => 1,
2673 'is_active' => ['1' => 1],
2674 'price_set_id' => $this->_ids
['price_set'],
2675 'is_enter_qty' => 1,
2676 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2678 if ($type === 'Radio') {
2679 foreach ($options as $index => $option) {
2680 $paramsField['is_enter_qty'] = 0;
2681 $optionID = $index +
2;
2682 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2683 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2687 $this->callAPISuccess('PriceField', 'create', $paramsField);
2688 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2689 $this->_ids
['price_field'] = array_keys($fields['values']);
2690 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2691 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2693 return $this->_ids
['price_set'];
2697 * Add a profile to a contribution page.
2699 * @param string $name
2700 * @param int $contributionPageID
2701 * @param string $module
2703 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2705 'uf_group_id' => $name,
2706 'module' => $module,
2707 'entity_table' => 'civicrm_contribution_page',
2708 'entity_id' => $contributionPageID,
2711 if ($module !== 'CiviContribute') {
2712 $params['module_data'] = [$module => []];
2714 $this->callAPISuccess('UFJoin', 'create', $params);
2718 * Add participant with contribution
2722 * @throws \CRM_Core_Exception
2724 protected function createPartiallyPaidParticipantOrder() {
2725 $orderParams = $this->getParticipantOrderParams();
2726 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2727 return $this->callAPISuccess('Order', 'create', $orderParams);
2733 * @param string $component
2734 * @param int $componentId
2735 * @param array $priceFieldOptions
2739 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2740 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2741 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2742 $paramsSet['is_active'] = TRUE;
2743 $paramsSet['financial_type_id'] = 'Event Fee';
2744 $paramsSet['extends'] = 1;
2745 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2746 $priceSetId = $priceSet['id'];
2747 //Checking for priceset added in the table.
2748 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2749 'id', $paramsSet['title'], 'Check DB for created priceset'
2751 $paramsField = array_merge([
2752 'label' => 'Price Field',
2753 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2754 'html_type' => 'CheckBox',
2755 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2756 'option_value' => ['1' => 100, '2' => 200],
2757 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2758 'option_weight' => ['1' => 1, '2' => 2],
2759 'option_amount' => ['1' => 100, '2' => 200],
2760 'is_display_amounts' => 1,
2762 'options_per_line' => 1,
2763 'is_active' => ['1' => 1, '2' => 1],
2764 'price_set_id' => $priceSet['id'],
2765 'is_enter_qty' => 1,
2766 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2767 ], $priceFieldOptions);
2769 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2771 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSetId);
2773 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2777 * Replace the template with a test-oriented template designed to show all the variables.
2779 * @param string $templateName
2780 * @param string $type
2782 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2783 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2784 CRM_Core_DAO
::executeQuery(
2785 "UPDATE civicrm_option_group og
2786 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2787 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2788 SET m.msg_{$type} = %1
2789 WHERE og.name LIKE 'msg_tpl_workflow_%'
2790 AND ov.name = '{$templateName}'
2791 AND m.is_default = 1", [1 => [$testTemplate, 'String']]
2796 * Reinstate the default template.
2798 * @param string $templateName
2799 * @param string $type
2801 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2802 CRM_Core_DAO
::executeQuery(
2803 "UPDATE civicrm_option_group og
2804 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2805 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2806 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2807 SET m.msg_{$type} = m2.msg_{$type}
2808 WHERE og.name = 'msg_tpl_workflow_contribution'
2809 AND ov.name = '{$templateName}'
2810 AND m.is_default = 1"
2815 * Flush statics relating to financial type.
2817 protected function flushFinancialTypeStatics() {
2818 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2819 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2821 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2822 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2824 CRM_Contribute_PseudoConstant
::flush('financialType');
2825 CRM_Contribute_PseudoConstant
::flush('membershipType');
2826 // Pseudoconstants may be saved to the cache table.
2827 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2828 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2829 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2833 * Set the permissions to the supplied array.
2835 * @param array $permissions
2837 protected function setPermissions($permissions) {
2838 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2839 $this->flushFinancialTypeStatics();
2843 * @param array $params
2846 public function _checkFinancialRecords($params, $context) {
2848 'entity_id' => $params['id'],
2849 'entity_table' => 'civicrm_contribution',
2851 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2852 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2853 if ($context == 'pending') {
2854 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2855 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2858 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2860 'id' => $trxn['financial_trxn_id'],
2862 if ($context != 'online' && $context != 'payLater') {
2864 'to_financial_account_id' => 6,
2865 'total_amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2869 if ($context == 'feeAmount') {
2870 $compareParams['fee_amount'] = 50;
2872 elseif ($context == 'online') {
2874 'to_financial_account_id' => 12,
2875 'total_amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2877 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2880 elseif ($context == 'payLater') {
2882 'to_financial_account_id' => 7,
2883 'total_amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2887 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2889 'financial_trxn_id' => $trxn['financial_trxn_id'],
2890 'entity_table' => 'civicrm_financial_item',
2892 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2894 'id' => $entityTrxn['entity_id'],
2897 'amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2899 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2901 if ($context == 'payLater') {
2903 'amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2905 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2908 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2909 if ($context == 'feeAmount') {
2911 'entity_id' => $params['id'],
2912 'entity_table' => 'civicrm_contribution',
2914 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2916 'id' => $maxTrxn['financial_trxn_id'],
2919 'to_financial_account_id' => 5,
2920 'from_financial_account_id' => 6,
2921 'total_amount' => 50,
2924 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2925 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2927 'entity_id' => $trxnId['financialTrxnId'],
2928 'entity_table' => 'civicrm_financial_trxn',
2933 'financial_account_id' => 5,
2935 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2937 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2938 // line should be copied into all the functions that call this function & evaluated there
2939 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2940 // when calling completeTransaction or repeatTransaction.
2941 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2945 * Return financial type id on basis of name
2947 * @param string $name Financial type m/c name
2951 public function getFinancialTypeId($name) {
2952 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2956 * Cleanup function for contents of $this->ids.
2958 * This is a best effort cleanup to use in tear downs etc.
2960 * It will not fail if the data has already been removed (some tests may do
2961 * their own cleanup).
2963 protected function cleanUpSetUpIDs() {
2964 foreach ($this->setupIDs
as $entity => $id) {
2966 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
2968 catch (CiviCRM_API3_Exception
$e) {
2969 // This is a best-effort cleanup function, ignore.
2975 * Create Financial Type.
2977 * @param array $params
2981 protected function createFinancialType($params = []) {
2982 $params = array_merge($params,
2984 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
2988 return $this->callAPISuccess('FinancialType', 'create', $params);
2992 * Create Payment Instrument.
2994 * @param array $params
2995 * @param string $financialAccountName
2999 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3000 $params = array_merge([
3001 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3002 'option_group_id' => 'payment_instrument',
3005 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3007 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3009 $financialAccountParams = [
3010 'entity_table' => 'civicrm_option_value',
3011 'entity_id' => $newPaymentInstrument,
3012 'account_relationship' => $relationTypeID,
3013 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3015 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3017 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3021 * Enable Tax and Invoicing
3023 * @param array $params
3025 * @return \Civi\Core\SettingsBag
3027 protected function enableTaxAndInvoicing($params = []) {
3028 // Enable component contribute setting
3029 $contributeSetting = array_merge($params,
3032 'invoice_prefix' => 'INV_',
3034 'due_date_period' => 'days',
3036 'is_email_pdf' => 1,
3037 'tax_term' => 'Sales Tax',
3038 'tax_display_settings' => 'Inclusive',
3041 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3045 * Enable Tax and Invoicing
3047 protected function disableTaxAndInvoicing($params = []) {
3048 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3049 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3051 // Enable component contribute setting
3052 $contributeSetting = array_merge($params,
3057 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3061 * Add Sales Tax relation for financial type with financial account.
3063 * @param int $financialTypeId
3067 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
3069 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3070 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3071 'is_deductible' => 1,
3076 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3078 'entity_table' => 'civicrm_financial_type',
3079 'entity_id' => $financialTypeId,
3080 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3083 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3084 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3086 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3087 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3088 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3089 $dao->copyValues($entityParams);
3091 if ($dao->fetch()) {
3092 $entityParams['id'] = $dao->id
;
3094 $entityParams['financial_account_id'] = $account->id
;
3096 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3100 * Create price set with contribution test for test setup.
3102 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3103 * on parent class at some point (fn is not in 4.4).
3106 * @param array $params
3108 public function createPriceSetWithPage($entity = NULL, $params = []) {
3109 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3110 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3111 'title' => "Test Contribution Page",
3112 'financial_type_id' => 1,
3113 'currency' => 'NZD',
3114 'goal_amount' => 50,
3115 'is_pay_later' => 1,
3116 'is_monetary' => TRUE,
3117 'is_email_receipt' => FALSE,
3119 $priceSet = $this->callAPISuccess('price_set', 'create', [
3120 'is_quick_config' => 0,
3121 'extends' => 'CiviMember',
3122 'financial_type_id' => 1,
3123 'title' => 'my Page',
3125 $priceSetID = $priceSet['id'];
3127 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3128 $priceField = $this->callAPISuccess('price_field', 'create', [
3129 'price_set_id' => $priceSetID,
3130 'label' => 'Goat Breed',
3131 'html_type' => 'Radio',
3133 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3134 'price_set_id' => $priceSetID,
3135 'price_field_id' => $priceField['id'],
3136 'label' => 'Long Haired Goat',
3138 'financial_type_id' => 'Donation',
3139 'membership_type_id' => $membershipTypeID,
3140 'membership_num_terms' => 1,
3142 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3143 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3144 'price_set_id' => $priceSetID,
3145 'price_field_id' => $priceField['id'],
3146 'label' => 'Shoe-eating Goat',
3148 'financial_type_id' => 'Donation',
3149 'membership_type_id' => $membershipTypeID,
3150 'membership_num_terms' => 2,
3152 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3154 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3155 'price_set_id' => $priceSetID,
3156 'price_field_id' => $priceField['id'],
3157 'label' => 'Shoe-eating Goat',
3159 'financial_type_id' => 'Donation',
3161 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3163 $this->_ids
['price_set'] = $priceSetID;
3164 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3165 $this->_ids
['price_field'] = [$priceField['id']];
3167 $this->_ids
['membership_type'] = $membershipTypeID;
3171 * Only specified contact returned.
3173 * @implements CRM_Utils_Hook::aclWhereClause
3177 * @param $whereTables
3181 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3182 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3186 * @implements CRM_Utils_Hook::selectWhereClause
3188 * @param string $entity
3189 * @param array $clauses
3191 public function selectWhereClauseHook($entity, &$clauses) {
3192 if ($entity == 'Event') {
3193 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3198 * An implementation of hook_civicrm_post used with all our test cases.
3201 * @param string $objectName
3202 * @param int $objectId
3205 public function onPost($op, $objectName, $objectId, &$objectRef) {
3206 if ($op == 'create' && $objectName == 'Individual') {
3207 CRM_Core_DAO
::executeQuery(
3208 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3210 1 => [$objectId, 'Integer'],
3215 if ($op == 'edit' && $objectName == 'Participant') {
3217 1 => [$objectId, 'Integer'],
3219 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3220 CRM_Core_DAO
::executeQuery($query, $params);
3225 * Instantiate form object.
3227 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3229 * @param string $class
3230 * Name of form class.
3232 * @param array $formValues
3234 * @param string $pageName
3236 * @return \CRM_Core_Form
3237 * @throws \CRM_Core_Exception
3239 public function getFormObject($class, $formValues = [], $pageName = '') {
3240 $form = new $class();
3241 $_SERVER['REQUEST_METHOD'] = 'GET';
3242 $form->controller
= new CRM_Core_Controller();
3243 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3244 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3249 * Get possible thousand separators.
3253 public function getThousandSeparators() {
3254 return [['.'], [',']];
3258 * Get the boolean options as a provider.
3262 public function getBooleanDataProvider() {
3263 return [[TRUE], [FALSE]];
3267 * Set the separators for thousands and decimal points.
3269 * Use setMonetaryDecimalPoint and setMonetaryThousandSeparator instead
3271 * @param string $thousandSeparator
3274 protected function setCurrencySeparators($thousandSeparator) {
3275 // Jaap Jansma: I do think the code below is a bit useless. It does an assumption
3276 // that when the thousand separator is a comma, the decimal is point. It
3277 // does not cater for a situation where the thousand separator is a [space]
3278 // Latter is the Norwegian localization.
3279 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3281 ->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3285 * Sets the thousand separator.
3287 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3289 * @param $thousandSeparator
3291 protected function setMonetaryThousandSeparator($thousandSeparator) {
3292 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3296 * Sets the decimal separator.
3298 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3300 * @param $decimalPoint
3302 protected function setMonetaryDecimalPoint($decimalPoint) {
3303 Civi
::settings()->set('monetaryDecimalPoint', $decimalPoint);
3307 * Sets the default currency.
3311 protected function setDefaultCurrency($currency) {
3312 Civi
::settings()->set('defaultCurrency', $currency);
3316 * Format money as it would be input.
3318 * @param string $amount
3322 protected function formatMoneyInput($amount) {
3323 return CRM_Utils_Money
::format($amount, NULL, '%a');
3327 * Get the contribution object.
3329 * @param int $contributionID
3331 * @return \CRM_Contribute_BAO_Contribution
3333 protected function getContributionObject($contributionID) {
3334 $contributionObj = new CRM_Contribute_BAO_Contribution();
3335 $contributionObj->id
= $contributionID;
3336 $contributionObj->find(TRUE);
3337 return $contributionObj;
3341 * Enable multilingual.
3343 public function enableMultilingual() {
3344 $this->callAPISuccess('Setting', 'create', [
3345 'lcMessages' => 'en_US',
3346 'languageLimit' => [
3351 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3354 $dbLocale = '_en_US';
3358 * Setup or clean up SMS tests
3360 * @param bool $teardown
3362 * @throws \CiviCRM_API3_Exception
3364 public function setupForSmsTests($teardown = FALSE) {
3365 require_once 'CiviTest/CiviTestSMSProvider.php';
3367 // Option value params for CiviTestSMSProvider
3368 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3370 'option_group_id' => $groupID,
3371 'label' => 'unittestSMS',
3372 'value' => 'unit.test.sms',
3373 'name' => 'CiviTestSMSProvider',
3380 // Test completed, delete provider
3381 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3382 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3386 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3387 // in CRM/Sms/Provider.php even though it is not an extension.
3388 return civicrm_api3('option_value', 'create', $params);
3392 * Start capturing browser output.
3394 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3396 protected function startCapturingOutput() {
3398 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3402 * Stop capturing browser output and return as a csv.
3404 * @param bool $isFirstRowHeaders
3406 * @return \League\Csv\Reader
3408 * @throws \League\Csv\Exception
3410 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3411 $output = ob_get_flush();
3412 $stream = fopen('php://memory', 'r+');
3413 fwrite($stream, $output);
3415 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3416 $csv = Reader
::createFromString($output);
3417 if ($isFirstRowHeaders) {
3418 $csv->setHeaderOffset(0);
3425 * Rename various labels to not match the names.
3427 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3428 * code that expects it to.
3430 protected function renameLabels() {
3431 $replacements = ['Pending', 'Refunded'];
3432 foreach ($replacements as $name) {
3433 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3438 * Undo any label renaming.
3440 protected function resetLabels() {
3441 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3445 * Get parameters to set up a multi-line participant order.
3448 * @throws \CRM_Core_Exception
3450 protected function getParticipantOrderParams(): array {
3451 $this->_contactId
= $this->individualCreate();
3452 $event = $this->eventCreate();
3453 $this->_eventId
= $event['id'];
3455 'id' => $this->_eventId
,
3456 'financial_type_id' => 4,
3459 $this->callAPISuccess('event', 'create', $eventParams);
3460 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3461 $participantParams = [
3462 'financial_type_id' => 4,
3463 'event_id' => $this->_eventId
,
3466 'fee_currency' => 'USD',
3467 'contact_id' => $this->_contactId
,
3469 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3471 'total_amount' => 300,
3472 'currency' => 'USD',
3473 'contact_id' => $this->_contactId
,
3474 'financial_type_id' => 4,
3475 'contribution_status_id' => 'Pending',
3476 'contribution_mode' => 'participant',
3477 'participant_id' => $participant['id'],
3479 foreach ($priceFields['values'] as $key => $priceField) {
3480 $orderParams['line_items'][] = [
3483 'price_field_id' => $priceField['price_field_id'],
3484 'price_field_value_id' => $priceField['id'],
3485 'label' => $priceField['label'],
3486 'field_title' => $priceField['label'],
3488 'unit_price' => $priceField['amount'],
3489 'line_total' => $priceField['amount'],
3490 'financial_type_id' => $priceField['financial_type_id'],
3491 'entity_table' => 'civicrm_participant',
3494 'params' => $participant,
3497 return $orderParams;
3503 * @throws \CRM_Core_Exception
3505 protected function validatePayments($payments) {
3506 foreach ($payments as $payment) {
3507 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3508 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3509 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3510 // This is a pretty rough way at guessing which payment is the overpayment - but
3511 // for the test suite it should be enough.
3514 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3515 'financial_trxn_id' => $payment['id'],
3516 'entity_table' => 'civicrm_financial_item',
3517 'return' => ['amount'],
3520 foreach ($items as $item) {
3521 $itemTotal +
= $item['amount'];
3523 $this->assertEquals($payment['total_amount'], $itemTotal);
3528 * Validate all created payments.
3530 * @throws \CRM_Core_Exception
3532 protected function validateAllPayments() {
3533 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3534 $this->validatePayments($payments);
3538 * Validate all created contributions.
3540 * @throws \CRM_Core_Exception
3542 protected function validateAllContributions() {
3543 $contributions = $this->callAPISuccess('Contribution', 'get', [])['values'];
3544 foreach ($contributions as $contribution) {
3545 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3547 foreach ($lineItems as $lineItem) {
3548 $total +
= $lineItem['line_total'];
3550 $this->assertEquals($total, $contribution['total_amount']);