3 * File for the CiviUnitTestCase class
7 * @copyright Copyright CiviCRM LLC (C) 2009
8 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html
9 * GNU Affero General Public License version 3
12 * This file is part of CiviCRM
14 * CiviCRM is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Affero General Public License
16 * as published by the Free Software Foundation; either version 3 of
17 * the License, or (at your option) any later version.
19 * CiviCRM is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Affero General Public License for more details.
24 * You should have received a copy of the GNU Affero General Public
25 * License along with this program. If not, see
26 * <http://www.gnu.org/licenses/>.
29 use Civi\Payment\System
;
30 use League\Csv\Reader
;
33 * Include class definitions
35 require_once 'api/api.php';
36 define('API_LATEST_VERSION', 3);
39 * Base class for CiviCRM unit tests
41 * This class supports two (mutually-exclusive) techniques for cleaning up test data. Subclasses
42 * may opt for one or neither:
44 * 1. quickCleanup() is a helper which truncates a series of tables. Call quickCleanup()
45 * as part of setUp() and/or tearDown(). quickCleanup() is thorough - but it can
46 * be cumbersome to use (b/c you must identify the tables to cleanup) and slow to execute.
47 * 2. useTransaction() executes the test inside a transaction. It's easier to use
48 * (because you don't need to identify specific tables), but it doesn't work for tests
49 * which manipulate schema or truncate data -- and could behave inconsistently
50 * for tests which specifically examine DB transactions.
52 * Common functions for unit tests
56 class CiviUnitTestCase
extends PHPUnit\Framework\TestCase
{
58 use \Civi\Test\Api3DocTrait
;
59 use \Civi\Test\GenericAssertionsTrait
;
60 use \Civi\Test\DbTestTrait
;
61 use \Civi\Test\ContactTestTrait
;
62 use \Civi\Test\MailingTestTrait
;
65 * Database has been initialized.
69 private static $dbInit = FALSE;
72 * Database connection.
74 * @var PHPUnit_Extensions_Database_DB_IDatabaseConnection
83 static protected $_dbName;
86 * Track tables we have modified during a test.
90 protected $_tablesToTruncate = [];
94 * Array of temporary directory names
100 * populateOnce allows to skip db resets in setUp
102 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
103 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
106 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
108 * @see http://forum.civicrm.org/index.php/topic,18065.0.html
110 public static $populateOnce = FALSE;
113 * DBResetRequired allows skipping DB reset
114 * in specific test case. If you still need
115 * to reset single test (method) of such case, call
116 * $this->cleanDB() in the first line of this
120 public $DBResetRequired = TRUE;
123 * @var CRM_Core_Transaction|null
128 * Array of IDs created to support the test.
131 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
138 * Should financials be checked after the test but before tear down.
140 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
141 * are some test data issues and some real bugs currently blockinng.
145 protected $isValidateFinancialsOnPostAssert = FALSE;
148 * Should location types be checked to ensure primary addresses are correctly assigned after each test.
152 protected $isLocationTypesOnPostAssert = TRUE;
155 * Class used for hooks during tests.
157 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
159 * $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
161 * @var \CRM_Utils_Hook_UnitTests
163 public $hookClass = NULL;
167 * Common values to be re-used multiple times within a class - usually to create the relevant entity
169 protected $_params = [];
172 * @var CRM_Extension_System
174 protected $origExtensionSystem;
177 * Array of IDs created during test setup routine.
179 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
183 public $setupIDs = [];
188 * Because we are overriding the parent class constructor, we
189 * need to show the same arguments as exist in the constructor of
190 * PHPUnit_Framework_TestCase, since
191 * PHPUnit_Framework_TestSuite::createTest() creates a
192 * ReflectionClass of the Test class and checks the constructor
193 * of that class to decide how to set up the test.
195 * @param string $name
197 * @param string $dataName
199 public function __construct($name = NULL, array $data = [], $dataName = '') {
200 parent
::__construct($name, $data, $dataName);
202 // we need full error reporting
203 error_reporting(E_ALL
& ~E_NOTICE
);
205 self
::$_dbName = self
::getDBName();
207 // also load the class loader
208 require_once 'CRM/Core/ClassLoader.php';
209 CRM_Core_ClassLoader
::singleton()->register();
210 if (function_exists('_civix_phpunit_setUp')) {
211 // FIXME: loosen coupling
212 _civix_phpunit_setUp();
217 * Override to run the test and assert its state.
221 * @throws \PHPUnit_Framework_IncompleteTest
222 * @throws \PHPUnit_Framework_SkippedTest
224 protected function runTest() {
226 return parent
::runTest();
228 catch (PEAR_Exception
$e) {
229 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
230 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
237 public function requireDBReset() {
238 return $this->DBResetRequired
;
244 public static function getDBName() {
245 static $dbName = NULL;
246 if ($dbName === NULL) {
247 require_once "DB.php";
248 $dsn = CRM_Utils_SQL
::autoSwitchDSN(CIVICRM_DSN
);
249 $dsninfo = DB
::parseDSN($dsn);
250 $dbName = $dsninfo['database'];
256 * Create database connection for this instance.
258 * Initialize the test database if it hasn't been initialized
261 protected function getConnection() {
262 if (!self
::$dbInit) {
263 $dbName = self
::getDBName();
265 // install test database
266 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
268 static::_populateDB(FALSE, $this);
270 self
::$dbInit = TRUE;
276 * Required implementation of abstract method.
278 protected function getDataSet() {
282 * @param bool $perClass
283 * @param null $object
286 * TRUE if the populate logic runs; FALSE if it is skipped
288 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
289 if (CIVICRM_UF
!== 'UnitTests') {
290 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
293 if ($perClass ||
$object == NULL) {
297 $dbreset = $object->requireDBReset();
300 if (self
::$populateOnce ||
!$dbreset) {
303 self
::$populateOnce = NULL;
305 Civi\Test
::data()->populate();
310 public static function setUpBeforeClass() {
311 static::_populateDB(TRUE);
313 // also set this global hack
314 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
318 * Common setup functions for all unit tests.
320 protected function setUp(): void
{
321 $session = CRM_Core_Session
::singleton();
322 $session->set('userID', NULL);
324 $this->_apiversion
= 3;
326 // Use a temporary file for STDIN
327 $GLOBALS['stdin'] = tmpfile();
328 if ($GLOBALS['stdin'] === FALSE) {
329 echo "Couldn't open temporary file\n";
333 // Get and save a connection to the database
334 $this->_dbconn
= $this->getConnection();
336 // reload database before each test
337 // $this->_populateDB();
339 // "initialize" CiviCRM to avoid problems when running single tests
340 // FIXME: look at it closer in second stage
342 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
343 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
345 // disable any left-over test extensions
346 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
348 // reset all the caches
349 CRM_Utils_System
::flushCache();
351 // initialize the object once db is loaded
352 \Civi
::$statics = [];
354 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
356 // when running unit tests, use mockup user framework
357 $this->hookClass
= CRM_Utils_Hook
::singleton();
359 // Make sure the DB connection is setup properly
360 $config->userSystem
->setMySQLTimeZone();
361 $env = new CRM_Utils_Check_Component_Env();
362 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
364 // clear permissions stub to not check permissions
365 $config->userPermissionClass
->permissions
= NULL;
367 //flush component settings
368 CRM_Core_Component
::getEnabledComponents(TRUE);
370 $_REQUEST = $_GET = $_POST = [];
371 error_reporting(E_ALL
);
373 $this->renameLabels();
374 $this->_sethtmlGlobals();
375 $this->ensureMySQLMode(['IGNORE_SPACE', 'ERROR_FOR_DIVISION_BY_ZERO', 'STRICT_TRANS_TABLES']);
379 * Read everything from the datasets directory and insert into the db.
381 public function loadAllFixtures(): void
{
382 $fixturesDir = __DIR__
. '/../../fixtures';
384 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
386 $jsonFiles = glob($fixturesDir . '/*.json');
387 foreach ($jsonFiles as $jsonFixture) {
388 $json = json_decode(file_get_contents($jsonFixture));
389 foreach ($json as $tableName => $vars) {
390 if ($tableName === 'civicrm_contact') {
391 CRM_Core_DAO
::executeQuery('DELETE c FROM civicrm_contact c LEFT JOIN civicrm_domain d ON d.contact_id = c.id WHERE d.id IS NULL');
394 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
396 foreach ($vars as $entity) {
397 $keys = $values = [];
398 foreach ($entity as $key => $value) {
400 $values[] = is_numeric($value) ?
$value : "'{$value}'";
402 CRM_Core_DAO
::executeQuery("
403 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
410 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
414 * Load the data that used to be handled by the discontinued dbunit class.
416 * This could do with further tidy up - the initial priority is simply to get rid of
417 * the dbunity package which is no longer supported.
419 * @param string $fileName
421 protected function loadXMLDataSet($fileName) {
422 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
423 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
424 foreach ($xml as $tableName => $element) {
425 if (!empty($element)) {
426 foreach ($element as $row) {
427 $keys = $values = [];
428 if (isset($row['@attributes'])) {
429 foreach ($row['@attributes'] as $key => $value) {
431 $values[] = is_numeric($value) ?
$value : "'{$value}'";
434 elseif (!empty($row)) {
435 // cos we copied it & it is inconsistent....
436 foreach ($row as $key => $value) {
438 $values[] = is_numeric($value) ?
$value : "'{$value}'";
442 if (!empty($values)) {
443 CRM_Core_DAO
::executeQuery("
444 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
450 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
454 * Create default domain contacts for the two domains added during test class.
455 * database population.
457 * @throws \CiviCRM_API3_Exception
459 public function createDomainContacts(): void
{
460 $this->organizationCreate(['api.Email.create' => ['email' => 'fixme.domainemail@example.org']]);
461 $this->organizationCreate([
462 'organization_name' => 'Second Domain',
463 'api.Email.create' => ['email' => 'domainemail2@example.org'],
464 'api.Address.create' => [
465 'street_address' => '15 Main St',
466 'location_type_id' => 1,
467 'city' => 'Collinsville',
468 'country_id' => 1228,
469 'state_province_id' => 1003,
470 'postal_code' => 6022,
476 * Common teardown functions for all unit tests.
478 * @throws \CiviCRM_API3_Exception
479 * @throws \CRM_Core_Exception
481 protected function tearDown(): void
{
482 $this->_apiversion
= 3;
483 $this->resetLabels();
485 error_reporting(E_ALL
& ~E_NOTICE
);
486 CRM_Utils_Hook
::singleton()->reset();
487 if ($this->hookClass
) {
488 $this->hookClass
->reset();
490 CRM_Core_Session
::singleton()->reset(1);
493 $this->tx
->rollback()->commit();
496 CRM_Core_Transaction
::forceRollbackIfEnabled();
497 \Civi\Core\Transaction\Manager
::singleton(TRUE);
500 CRM_Core_Transaction
::forceRollbackIfEnabled();
501 \Civi\Core\Transaction\Manager
::singleton(TRUE);
503 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match', 'civicrm_email', 'civicrm_address'];
504 $this->quickCleanup($tablesToTruncate);
505 $this->createDomainContacts();
508 $this->cleanTempDirs();
509 $this->unsetExtensionSystem();
510 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
511 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
515 * CHeck that all tests that have created payments have created them with the right financial entities.
517 * @throws \CRM_Core_Exception
519 protected function assertPostConditions() {
520 // Reset to version 3 as not all (e.g payments) work on v4
521 $this->_apiversion
= 3;
522 if ($this->isLocationTypesOnPostAssert
) {
523 $this->assertLocationValidity();
525 if (!$this->isValidateFinancialsOnPostAssert
) {
528 $this->validateAllPayments();
529 $this->validateAllContributions();
533 * Create a batch of external API calls which can
534 * be executed concurrently.
537 * $calls = $this->createExternalAPI()
538 * ->addCall('Contact', 'get', ...)
539 * ->addCall('Contact', 'get', ...)
545 * @return \Civi\API\ExternalBatch
546 * @throws PHPUnit_Framework_SkippedTestError
548 public function createExternalAPI() {
549 global $civicrm_root;
551 'version' => $this->_apiversion
,
555 $calls = new \Civi\API\
ExternalBatch($defaultParams);
557 if (!$calls->isSupported()) {
558 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
565 * Create required data based on $this->entity & $this->params
566 * This is just a way to set up the test data for delete & get functions
567 * so the distinction between set
568 * up & tested functions is clearer
573 public function createTestEntity() {
574 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
578 * @param int $contactTypeId
582 public function contactTypeDelete($contactTypeId) {
583 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
585 throw new Exception('Could not delete contact type');
590 * @param array $params
594 public function membershipTypeCreate($params = []) {
595 CRM_Member_PseudoConstant
::flush('membershipType');
596 CRM_Core_Config
::clearDBCache();
597 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
598 $params = array_merge([
600 'duration_unit' => 'year',
601 'duration_interval' => 1,
602 'period_type' => 'rolling',
603 'member_of_contact_id' => $memberOfOrganization,
605 'financial_type_id' => 2,
608 'visibility' => 'Public',
611 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
613 CRM_Member_PseudoConstant
::flush('membershipType');
614 CRM_Utils_Cache
::singleton()->flush();
616 return (int) $result['id'];
622 * @param array $params
625 * @throws \CRM_Core_Exception
627 public function contactMembershipCreate($params) {
628 $params = array_merge([
629 'join_date' => '2007-01-21',
630 'start_date' => '2007-01-21',
631 'end_date' => '2007-12-21',
632 'source' => 'Payment',
633 'membership_type_id' => 'General',
635 if (!is_numeric($params['membership_type_id'])) {
636 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
637 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
638 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
642 $result = $this->callAPISuccess('Membership', 'create', $params);
643 return $result['id'];
647 * Delete Membership Type.
649 * @param array $params
651 public function membershipTypeDelete($params) {
652 $this->callAPISuccess('MembershipType', 'Delete', $params);
656 * @param int $membershipID
658 public function membershipDelete($membershipID) {
659 $deleteParams = ['id' => $membershipID];
660 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
664 * @param string $name
668 * @throws \CRM_Core_Exception
670 public function membershipStatusCreate($name = 'test member status') {
671 $params['name'] = $name;
672 $params['start_event'] = 'start_date';
673 $params['end_event'] = 'end_date';
674 $params['is_current_member'] = 1;
675 $params['is_active'] = 1;
677 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
678 CRM_Member_PseudoConstant
::flush('membershipStatus');
679 return (int) $result['id'];
683 * Delete the given membership status, deleting any memberships of the status first.
685 * @param int $membershipStatusID
687 * @throws \CRM_Core_Exception
689 public function membershipStatusDelete(int $membershipStatusID) {
690 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
691 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
694 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
695 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
696 $renewedMembershipEndDate = new DateTime($membershipEndDate);
697 switch ($durationUnit) {
699 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
703 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
704 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
705 $renewedMembershipEndDate->add(new DateInterval('P1D'));
706 $renewedMembershipEndDate->add(new DateInterval('P1M'));
707 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
710 return $renewedMembershipEndDate->format('Y-m-d');
714 * Create a relationship type.
716 * @param array $params
720 * @throws \CRM_Core_Exception
722 public function relationshipTypeCreate($params = []) {
723 $params = array_merge([
724 'name_a_b' => 'Relation 1 for relationship type create',
725 'name_b_a' => 'Relation 2 for relationship type create',
726 'contact_type_a' => 'Individual',
727 'contact_type_b' => 'Organization',
732 $result = $this->callAPISuccess('relationship_type', 'create', $params);
733 CRM_Core_PseudoConstant
::flush('relationshipType');
735 return $result['id'];
739 * Delete Relatinship Type.
741 * @param int $relationshipTypeID
743 public function relationshipTypeDelete($relationshipTypeID) {
744 $params['id'] = $relationshipTypeID;
745 $check = $this->callAPISuccess('relationship_type', 'get', $params);
746 if (!empty($check['count'])) {
747 $this->callAPISuccess('relationship_type', 'delete', $params);
752 * @param array $params
755 * @throws \CRM_Core_Exception
757 public function paymentProcessorTypeCreate($params = []) {
758 $params = array_merge([
759 'name' => 'API_Test_PP',
760 'title' => 'API Test Payment Processor',
761 'class_name' => 'CRM_Core_Payment_APITest',
762 'billing_mode' => 'form',
767 $result = $this->callAPISuccess('PaymentProcessorType', 'create', $params);
769 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
771 return $result['id'];
775 * Create test Authorize.net instance.
777 * @param array $params
780 * @throws \CRM_Core_Exception
782 public function paymentProcessorAuthorizeNetCreate($params = []) {
783 $params = array_merge([
784 'name' => 'Authorize',
785 'domain_id' => CRM_Core_Config
::domainID(),
786 'payment_processor_type_id' => 'AuthNet',
787 'title' => 'AuthNet',
792 'user_name' => '4y5BfuW7jm',
793 'password' => '4cAmW927n8uLf5J8',
794 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
795 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
796 'class_name' => 'Payment_AuthorizeNet',
800 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
801 return (int) $result['id'];
805 * Create Participant.
807 * @param array $params
808 * Array of contact id and event id values.
811 * $id of participant created
813 public function participantCreate($params = []) {
814 if (empty($params['contact_id'])) {
815 $params['contact_id'] = $this->individualCreate();
817 if (empty($params['event_id'])) {
818 $event = $this->eventCreate();
819 $params['event_id'] = $event['id'];
824 'register_date' => 20070219,
825 'source' => 'Wimbeldon',
826 'event_level' => 'Payment',
830 $params = array_merge($defaults, $params);
831 $result = $this->callAPISuccess('Participant', 'create', $params);
832 return $result['id'];
836 * Create Payment Processor.
839 * Id Payment Processor
841 public function processorCreate($params = []) {
845 'payment_processor_type_id' => 'Dummy',
846 'financial_account_id' => 12,
850 'url_site' => 'http://dummy.com',
851 'url_recur' => 'http://dummy.com',
854 'payment_instrument_id' => 'Debit Card',
856 $processorParams = array_merge($processorParams, $params);
857 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
858 return $processor['id'];
862 * Create Payment Processor.
864 * @param array $processorParams
866 * @return \CRM_Core_Payment_Dummy
867 * Instance of Dummy Payment Processor
869 * @throws \CiviCRM_API3_Exception
871 public function dummyProcessorCreate($processorParams = []) {
872 $paymentProcessorID = $this->processorCreate($processorParams);
873 // For the tests we don't need a live processor, but as core ALWAYS creates a processor in live mode and one in test mode we do need to create both
874 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
875 $processorParams['is_test'] = FALSE;
876 $this->processorCreate($processorParams);
877 return System
::singleton()->getById($paymentProcessorID);
881 * Create contribution page.
883 * @param array $params
886 * Array of contribution page
888 public function contributionPageCreate($params = []) {
889 $this->_pageParams
= array_merge([
890 'title' => 'Test Contribution Page',
891 'financial_type_id' => 1,
893 'financial_account_id' => 1,
895 'is_allow_other_amount' => 1,
897 'max_amount' => 1000,
899 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
903 * Create a sample batch.
905 public function batchCreate() {
906 $params = $this->_params
;
907 $params['name'] = $params['title'] = 'Batch_433397';
908 $params['status_id'] = 1;
909 $result = $this->callAPISuccess('batch', 'create', $params);
910 return $result['id'];
916 * @param array $params
919 * result of created tag
921 public function tagCreate($params = []) {
923 'name' => 'New Tag3',
924 'description' => 'This is description for Our New Tag ',
927 $params = array_merge($defaults, $params);
928 $result = $this->callAPISuccess('Tag', 'create', $params);
929 return $result['values'][$result['id']];
936 * Id of the tag to be deleted.
940 public function tagDelete($tagId) {
941 require_once 'api/api.php';
945 $result = $this->callAPISuccess('Tag', 'delete', $params);
946 return $result['id'];
950 * Add entity(s) to the tag
952 * @param array $params
956 public function entityTagAdd($params) {
957 $result = $this->callAPISuccess('entity_tag', 'create', $params);
964 * @param array $params
968 * id of created pledge
970 * @throws \CRM_Core_Exception
972 public function pledgeCreate($params) {
973 $params = array_merge([
974 'pledge_create_date' => date('Ymd'),
975 'start_date' => date('Ymd'),
976 'scheduled_date' => date('Ymd'),
978 'pledge_status_id' => '2',
979 'financial_type_id' => '1',
980 'pledge_original_installment_amount' => 20,
981 'frequency_interval' => 5,
982 'frequency_unit' => 'year',
983 'frequency_day' => 15,
988 $result = $this->callAPISuccess('Pledge', 'create', $params);
989 return $result['id'];
993 * Delete contribution.
995 * @param int $pledgeId
997 * @throws \CRM_Core_Exception
999 public function pledgeDelete($pledgeId) {
1001 'pledge_id' => $pledgeId,
1003 $this->callAPISuccess('Pledge', 'delete', $params);
1007 * Create contribution.
1009 * @param array $params
1010 * Array of parameters.
1013 * id of created contribution
1014 * @throws \CRM_Core_Exception
1016 public function contributionCreate($params) {
1018 $params = array_merge([
1020 'receive_date' => date('Ymd'),
1021 'total_amount' => 100.00,
1022 'fee_amount' => 5.00,
1023 'financial_type_id' => 1,
1024 'payment_instrument_id' => 1,
1025 'non_deductible_amount' => 10.00,
1027 'contribution_status_id' => 1,
1030 $result = $this->callAPISuccess('contribution', 'create', $params);
1031 return $result['id'];
1035 * Delete contribution.
1037 * @param int $contributionId
1040 * @throws \CRM_Core_Exception
1042 public function contributionDelete($contributionId) {
1044 'contribution_id' => $contributionId,
1046 $result = $this->callAPISuccess('contribution', 'delete', $params);
1053 * @param array $params
1054 * Name-value pair for an event.
1057 * @throws \CRM_Core_Exception
1059 public function eventCreate($params = []) {
1060 // if no contact was passed, make up a dummy event creator
1061 if (!isset($params['contact_id'])) {
1062 $params['contact_id'] = $this->_contactCreate([
1063 'contact_type' => 'Individual',
1064 'first_name' => 'Event',
1065 'last_name' => 'Creator',
1069 // set defaults for missing params
1070 $params = array_merge([
1071 'title' => 'Annual CiviCRM meet',
1072 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1073 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1074 'event_type_id' => 1,
1076 'start_date' => 20081021,
1077 'end_date' => 20081023,
1078 'is_online_registration' => 1,
1079 'registration_start_date' => 20080601,
1080 'registration_end_date' => 20081015,
1081 'max_participants' => 100,
1082 'event_full_text' => 'Sorry! We are already full',
1085 'is_show_location' => 0,
1086 'is_email_confirm' => 1,
1089 return $this->callAPISuccess('Event', 'create', $params);
1093 * Create a paid event.
1095 * @param array $params
1097 * @param array $options
1099 * @param string $key
1100 * Index for storing event ID in ids array.
1104 * @throws \CRM_Core_Exception
1106 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1107 $params['is_monetary'] = TRUE;
1108 $event = $this->eventCreate($params);
1109 $this->ids
['Event'][$key] = (int) $event['id'];
1110 $this->ids
['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1111 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->ids
['PriceSet'][$key]);
1112 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->ids
['PriceSet'][$key], TRUE, FALSE);
1113 $priceSet = $priceSet[$this->ids
['PriceSet'][$key]] ??
NULL;
1114 $this->eventFeeBlock
= $priceSet['fields'] ??
NULL;
1126 public function eventDelete($id) {
1130 return $this->callAPISuccess('event', 'delete', $params);
1134 * Delete participant.
1136 * @param int $participantID
1140 public function participantDelete($participantID) {
1142 'id' => $participantID,
1144 $check = $this->callAPISuccess('Participant', 'get', $params);
1145 if ($check['count'] > 0) {
1146 return $this->callAPISuccess('Participant', 'delete', $params);
1151 * Create participant payment.
1153 * @param int $participantID
1154 * @param int $contributionID
1157 * $id of created payment
1159 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1160 //Create Participant Payment record With Values
1162 'participant_id' => $participantID,
1163 'contribution_id' => $contributionID,
1166 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1167 return $result['id'];
1171 * Delete participant payment.
1173 * @param int $paymentID
1175 public function participantPaymentDelete($paymentID) {
1179 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1185 * @param int $contactID
1188 * location id of created location
1190 public function locationAdd($contactID) {
1193 'location_type' => 'New Location Type',
1195 'name' => 'Saint Helier St',
1196 'county' => 'Marin',
1197 'country' => 'UNITED STATES',
1198 'state_province' => 'Michigan',
1199 'supplemental_address_1' => 'Hallmark Ct',
1200 'supplemental_address_2' => 'Jersey Village',
1201 'supplemental_address_3' => 'My Town',
1206 'contact_id' => $contactID,
1207 'address' => $address,
1208 'location_format' => '2.0',
1209 'location_type' => 'New Location Type',
1212 $result = $this->callAPISuccess('Location', 'create', $params);
1217 * Delete Locations of contact.
1219 * @param array $params
1222 public function locationDelete($params) {
1223 $this->callAPISuccess('Location', 'delete', $params);
1227 * Add a Location Type.
1229 * @param array $params
1231 * @return CRM_Core_DAO_LocationType
1232 * location id of created location
1234 public function locationTypeCreate($params = NULL) {
1235 if ($params === NULL) {
1237 'name' => 'New Location Type',
1238 'vcard_name' => 'New Location Type',
1239 'description' => 'Location Type for Delete',
1244 $locationType = new CRM_Core_DAO_LocationType();
1245 $locationType->copyValues($params);
1246 $locationType->save();
1247 // clear getfields cache
1248 CRM_Core_PseudoConstant
::flush();
1249 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1250 return $locationType->id
;
1254 * Delete a Location Type.
1256 * @param int $locationTypeId
1258 public function locationTypeDelete($locationTypeId) {
1259 $locationType = new CRM_Core_DAO_LocationType();
1260 $locationType->id
= $locationTypeId;
1261 $locationType->delete();
1267 * @param array $params
1269 * @return CRM_Core_DAO_Mapping
1270 * Mapping id of created mapping
1272 public function mappingCreate($params = NULL) {
1273 if ($params === NULL) {
1275 'name' => 'Mapping name',
1276 'description' => 'Mapping description',
1277 // 'Export Contact' mapping.
1278 'mapping_type_id' => 7,
1282 $mapping = new CRM_Core_DAO_Mapping();
1283 $mapping->copyValues($params);
1285 // clear getfields cache
1286 CRM_Core_PseudoConstant
::flush();
1287 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1294 * @param int $mappingId
1296 public function mappingDelete($mappingId) {
1297 $mapping = new CRM_Core_DAO_Mapping();
1298 $mapping->id
= $mappingId;
1303 * Prepare class for ACLs.
1305 protected function prepareForACLs() {
1306 $config = CRM_Core_Config
::singleton();
1307 $config->userPermissionClass
->permissions
= [];
1313 protected function cleanUpAfterACLs() {
1314 CRM_Utils_Hook
::singleton()->reset();
1315 $tablesToTruncate = [
1317 'civicrm_acl_cache',
1318 'civicrm_acl_entity_role',
1319 'civicrm_acl_contact_cache',
1321 $this->quickCleanup($tablesToTruncate);
1322 $config = CRM_Core_Config
::singleton();
1323 unset($config->userPermissionClass
->permissions
);
1327 * Create a smart group.
1329 * By default it will be a group of households.
1331 * @param array $smartGroupParams
1332 * @param array $groupParams
1333 * @param string $contactType
1337 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1338 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1339 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1341 $groupParams['saved_search_id'] = $savedSearch->id
;
1342 return $this->groupCreate($groupParams);
1348 * @param array $params
1350 public function uFFieldCreate($params = []) {
1351 $params = array_merge([
1353 'field_name' => 'first_name',
1356 'visibility' => 'Public Pages and Listings',
1357 'is_searchable' => '1',
1358 'label' => 'first_name',
1359 'field_type' => 'Individual',
1362 $this->callAPISuccess('uf_field', 'create', $params);
1366 * Add a UF Join Entry.
1368 * @param array $params
1371 * $id of created UF Join
1373 public function ufjoinCreate($params = NULL) {
1374 if ($params === NULL) {
1377 'module' => 'CiviEvent',
1378 'entity_table' => 'civicrm_event',
1384 $result = $this->callAPISuccess('uf_join', 'create', $params);
1389 * @param array $params
1390 * Optional parameters.
1391 * @param bool $reloadConfig
1392 * While enabling CiviCampaign component, we shouldn't always forcibly
1393 * reload config as this hinder hook call in test environment
1398 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1399 $this->enableCiviCampaign($reloadConfig);
1400 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1401 'name' => 'big_campaign',
1402 'title' => 'Campaign',
1404 return $campaign['id'];
1408 * Create Group for a contact.
1410 * @param int $contactId
1412 public function contactGroupCreate($contactId) {
1414 'contact_id.1' => $contactId,
1418 $this->callAPISuccess('GroupContact', 'Create', $params);
1422 * Delete Group for a contact.
1424 * @param int $contactId
1426 public function contactGroupDelete($contactId) {
1428 'contact_id.1' => $contactId,
1431 $this->civicrm_api('GroupContact', 'Delete', $params);
1437 * @param array $params
1441 * @throws \CRM_Core_Exception
1442 * @throws \CiviCRM_API3_Exception
1444 public function activityCreate($params = []) {
1445 $params = array_merge([
1446 'subject' => 'Discussion on warm beer',
1447 'activity_date_time' => date('Ymd'),
1449 'location' => 'Baker Street',
1450 'details' => 'Lets schedule a meeting',
1452 'activity_type_id' => 'Meeting',
1454 if (!isset($params['source_contact_id'])) {
1455 $params['source_contact_id'] = $this->individualCreate();
1457 if (!isset($params['target_contact_id'])) {
1458 $params['target_contact_id'] = $this->individualCreate([
1459 'first_name' => 'Julia',
1460 'last_name' => 'Anderson',
1462 'email' => 'julia_anderson@civicrm.org',
1463 'contact_type' => 'Individual',
1466 if (!isset($params['assignee_contact_id'])) {
1467 $params['assignee_contact_id'] = $params['target_contact_id'];
1470 $result = civicrm_api3('Activity', 'create', $params);
1472 $result['target_contact_id'] = $params['target_contact_id'];
1473 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1478 * Create an activity type.
1480 * @param array $params
1485 public function activityTypeCreate($params) {
1486 return $this->callAPISuccess('ActivityType', 'create', $params);
1490 * Delete activity type.
1492 * @param int $activityTypeId
1493 * Id of the activity type.
1497 public function activityTypeDelete($activityTypeId) {
1498 $params['activity_type_id'] = $activityTypeId;
1499 return $this->callAPISuccess('ActivityType', 'delete', $params);
1503 * Create custom group.
1505 * @param array $params
1509 public function customGroupCreate($params = []) {
1511 'title' => 'new custom group',
1512 'extends' => 'Contact',
1514 'style' => 'Inline',
1518 $params = array_merge($defaults, $params);
1520 return $this->callAPISuccess('custom_group', 'create', $params);
1524 * Existing function doesn't allow params to be over-ridden so need a new one
1525 * this one allows you to only pass in the params you want to change
1527 * @param array $params
1531 public function CustomGroupCreateByParams($params = []) {
1533 'title' => "API Custom Group",
1534 'extends' => 'Contact',
1536 'style' => 'Inline',
1539 $params = array_merge($defaults, $params);
1540 return $this->callAPISuccess('custom_group', 'create', $params);
1544 * Create custom group with multi fields.
1546 * @param array $params
1550 public function CustomGroupMultipleCreateByParams($params = []) {
1555 $params = array_merge($defaults, $params);
1556 return $this->CustomGroupCreateByParams($params);
1560 * Create custom group with multi fields.
1562 * @param array $params
1566 public function CustomGroupMultipleCreateWithFields($params = []) {
1567 // also need to pass on $params['custom_field'] if not set but not in place yet
1569 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1570 $ids['custom_group_id'] = $customGroup['id'];
1572 $customField = $this->customFieldCreate([
1573 'custom_group_id' => $ids['custom_group_id'],
1574 'label' => 'field_1' . $ids['custom_group_id'],
1578 $ids['custom_field_id'][] = $customField['id'];
1580 $customField = $this->customFieldCreate([
1581 'custom_group_id' => $ids['custom_group_id'],
1582 'default_value' => '',
1583 'label' => 'field_2' . $ids['custom_group_id'],
1586 $ids['custom_field_id'][] = $customField['id'];
1588 $customField = $this->customFieldCreate([
1589 'custom_group_id' => $ids['custom_group_id'],
1590 'default_value' => '',
1591 'label' => 'field_3' . $ids['custom_group_id'],
1594 $ids['custom_field_id'][] = $customField['id'];
1600 * Create a custom group with a single text custom field. See
1601 * participant:testCreateWithCustom for how to use this
1603 * @param string $function
1605 * @param string $filename
1609 * ids of created objects
1611 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1612 $params = ['title' => $function];
1613 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1614 $params['extends'] = $entity ?
$entity : 'Contact';
1615 $customGroup = $this->customGroupCreate($params);
1616 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1617 CRM_Core_PseudoConstant
::flush();
1619 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1623 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1624 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1626 * @param string $function
1628 * @param string $filename
1632 * ids of created objects
1634 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1635 $params = ['title' => $function];
1636 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1637 $params['extends'] = $entity ?
$entity : 'Contact';
1638 $customGroup = $this->customGroupCreate($params);
1639 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1640 CRM_Core_PseudoConstant
::flush();
1642 'defaultValue' => 'Default Value',
1643 'lowercasevalue' => 'Lowercase Value',
1644 1 => 'Integer Value',
1647 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1648 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1649 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1650 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1651 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1652 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1653 foreach ($options as $option_value => $option_label) {
1654 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1655 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1659 'custom_group_id' => $customGroup['id'],
1660 'custom_field_id' => $customField['id'],
1661 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1662 'custom_field_group_options' => $options,
1667 * Delete custom group.
1669 * @param int $customGroupID
1673 public function customGroupDelete($customGroupID) {
1674 $params['id'] = $customGroupID;
1675 return $this->callAPISuccess('custom_group', 'delete', $params);
1679 * Create custom field.
1681 * @param array $params
1682 * (custom_group_id) is required.
1686 public function customFieldCreate($params) {
1687 $params = array_merge([
1688 'label' => 'Custom Field',
1689 'data_type' => 'String',
1690 'html_type' => 'Text',
1691 'is_searchable' => 1,
1693 'default_value' => 'defaultValue',
1696 $result = $this->callAPISuccess('custom_field', 'create', $params);
1697 // these 2 functions are called with force to flush static caches
1698 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1699 CRM_Core_Component
::getEnabledComponents(1);
1704 * Delete custom field.
1706 * @param int $customFieldID
1710 public function customFieldDelete($customFieldID) {
1712 $params['id'] = $customFieldID;
1713 return $this->callAPISuccess('custom_field', 'delete', $params);
1723 public function noteCreate($cId) {
1725 'entity_table' => 'civicrm_contact',
1726 'entity_id' => $cId,
1727 'note' => 'hello I am testing Note',
1728 'contact_id' => $cId,
1729 'modified_date' => date('Ymd'),
1730 'subject' => 'Test Note',
1733 return $this->callAPISuccess('Note', 'create', $params);
1737 * Enable CiviCampaign Component.
1739 * @param bool $reloadConfig
1740 * Force relaod config or not
1742 public function enableCiviCampaign($reloadConfig = TRUE) {
1743 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1744 if ($reloadConfig) {
1745 // force reload of config object
1746 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1748 //flush cache by calling with reset
1749 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1753 * Create custom field with Option Values.
1755 * @param array $customGroup
1756 * @param string $name
1757 * Name of custom field.
1758 * @param array $extraParams
1759 * Additional parameters to pass through.
1763 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1765 'custom_group_id' => $customGroup['id'],
1766 'name' => 'test_custom_group',
1767 'label' => 'Country',
1768 'html_type' => 'Select',
1769 'data_type' => 'String',
1772 'is_searchable' => 0,
1778 'name' => 'option_group1',
1779 'label' => 'option_group_label1',
1783 'option_label' => ['Label1', 'Label2'],
1784 'option_value' => ['value1', 'value2'],
1785 'option_name' => [$name . '_1', $name . '_2'],
1786 'option_weight' => [1, 2],
1787 'option_status' => [1, 1],
1790 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1792 return $this->callAPISuccess('custom_field', 'create', $params);
1800 public function confirmEntitiesDeleted($entities) {
1801 foreach ($entities as $entity) {
1803 $result = $this->callAPISuccess($entity, 'Get', []);
1804 if ($result['error'] == 1 ||
$result['count'] > 0) {
1805 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1813 * Quick clean by emptying tables created for the test.
1815 * @param array $tablesToTruncate
1816 * @param bool $dropCustomValueTables
1818 * @throws \CRM_Core_Exception
1820 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1822 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1824 if ($dropCustomValueTables) {
1825 $optionGroupResult = CRM_Core_DAO
::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1826 while ($optionGroupResult->fetch()) {
1827 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1828 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1829 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1830 // generic cleanup routine.
1831 if (!empty($optionGroupResult->option_group_id
) && $optionGroupResult->option_group_id
> 50) {
1832 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id
);
1835 $tablesToTruncate[] = 'civicrm_custom_group';
1836 $tablesToTruncate[] = 'civicrm_custom_field';
1839 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1841 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1842 foreach ($tablesToTruncate as $table) {
1843 $sql = "TRUNCATE TABLE $table";
1844 CRM_Core_DAO
::executeQuery($sql);
1846 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1848 if ($dropCustomValueTables) {
1849 $dbName = self
::getDBName();
1851 SELECT TABLE_NAME as tableName
1852 FROM INFORMATION_SCHEMA.TABLES
1853 WHERE TABLE_SCHEMA = '{$dbName}'
1854 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1857 $tableDAO = CRM_Core_DAO
::executeQuery($query);
1858 while ($tableDAO->fetch()) {
1859 $sql = "DROP TABLE {$tableDAO->tableName}";
1860 CRM_Core_DAO
::executeQuery($sql);
1866 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1868 * @throws \CRM_Core_Exception
1870 public function quickCleanUpFinancialEntities() {
1871 $tablesToTruncate = [
1873 'civicrm_activity_contact',
1874 'civicrm_contribution',
1875 'civicrm_contribution_soft',
1876 'civicrm_contribution_product',
1877 'civicrm_financial_trxn',
1878 'civicrm_financial_item',
1879 'civicrm_contribution_recur',
1880 'civicrm_line_item',
1881 'civicrm_contribution_page',
1882 'civicrm_payment_processor',
1883 'civicrm_entity_financial_trxn',
1884 'civicrm_membership',
1885 'civicrm_membership_type',
1886 'civicrm_membership_payment',
1887 'civicrm_membership_log',
1888 'civicrm_membership_block',
1890 'civicrm_participant',
1891 'civicrm_participant_payment',
1893 'civicrm_pcp_block',
1895 'civicrm_pledge_block',
1896 'civicrm_pledge_payment',
1897 'civicrm_price_set_entity',
1898 'civicrm_price_field_value',
1899 'civicrm_price_field',
1901 $this->quickCleanup($tablesToTruncate);
1902 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1903 $this->restoreDefaultPriceSetConfig();
1904 $this->disableTaxAndInvoicing();
1905 $this->setCurrencySeparators(',');
1906 CRM_Core_PseudoConstant
::flush('taxRates');
1907 System
::singleton()->flushProcessors();
1908 // @fixme this parameter is leaking - it should not be defined as a class static
1909 // but for now we just handle in tear down.
1910 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1914 * Reset the price set config so results exist.
1916 public function restoreDefaultPriceSetConfig() {
1917 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1918 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1919 CRM_Core_DAO
::executeQuery("INSERT INTO `civicrm_price_field` (`id`, `price_set_id`, `name`, `label`, `html_type`, `is_enter_qty`, `help_pre`, `help_post`, `weight`, `is_display_amounts`, `options_per_line`, `is_active`, `is_required`, `active_on`, `expire_on`, `javascript`, `visibility_id`) VALUES (1, 1, 'contribution_amount', 'Contribution Amount', 'Text', 0, NULL, NULL, 1, 1, 1, 1, 1, NULL, NULL, NULL, 1)");
1920 CRM_Core_DAO
::executeQuery("INSERT INTO `civicrm_price_field_value` (`id`, `price_field_id`, `name`, `label`, `description`, `amount`, `count`, `max_value`, `weight`, `membership_type_id`, `membership_num_terms`, `is_default`, `is_active`, `financial_type_id`, `non_deductible_amount`) VALUES (1, 1, 'contribution_amount', 'Contribution Amount', NULL, '1', NULL, NULL, 1, NULL, NULL, 0, 1, 1, 0.00)");
1924 * Recreate default membership types.
1926 public function restoreMembershipTypes() {
1927 CRM_Core_DAO
::executeQuery(
1928 "REPLACE INTO civicrm_membership_type
1929 (id, domain_id, name, description, member_of_contact_id, financial_type_id, minimum_fee, duration_unit, duration_interval, period_type, fixed_period_start_day, fixed_period_rollover_day, relationship_type_id, relationship_direction, visibility, weight, is_active)
1931 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1932 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1933 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1938 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1939 * Default behaviour is to also delete the entity
1940 * @param array $params
1941 * Params array to check against.
1943 * Id of the entity concerned.
1944 * @param string $entity
1945 * Name of entity concerned (e.g. membership).
1946 * @param bool $delete
1947 * Should the entity be deleted as part of this check.
1948 * @param string $errorText
1949 * Text to print on error.
1953 * @param array $params
1956 * @param int $delete
1957 * @param string $errorText
1959 * @throws CRM_Core_Exception
1961 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1963 $result = $this->callAPISuccessGetSingle($entity, [
1968 $this->callAPISuccess($entity, 'Delete', [
1972 $dateFields = $keys = $dateTimeFields = [];
1973 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1974 foreach ($fields['values'] as $field => $settings) {
1975 if (array_key_exists($field, $result)) {
1976 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
1979 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
1981 $type = $settings['type'] ??
NULL;
1982 if ($type == CRM_Utils_Type
::T_DATE
) {
1983 $dateFields[] = $settings['name'];
1984 // we should identify both real names & unique names as dates
1985 if ($field != $settings['name']) {
1986 $dateFields[] = $field;
1989 if ($type == CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
1990 $dateTimeFields[] = $settings['name'];
1991 // we should identify both real names & unique names as dates
1992 if ($field != $settings['name']) {
1993 $dateTimeFields[] = $field;
1998 if (strtolower($entity) == 'contribution') {
1999 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
2000 // this is not returned in id format
2001 unset($params['payment_instrument_id']);
2002 $params['contribution_source'] = $params['source'];
2003 unset($params['source']);
2006 foreach ($params as $key => $value) {
2007 if ($key == 'version' ||
substr($key, 0, 3) == 'api' ||
!array_key_exists($keys[$key], $result)) {
2010 if (in_array($key, $dateFields)) {
2011 $value = date('Y-m-d', strtotime($value));
2012 $result[$key] = date('Y-m-d', strtotime($result[$key]));
2014 if (in_array($key, $dateTimeFields)) {
2015 $value = date('Y-m-d H:i:s', strtotime($value));
2016 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
2018 $this->assertEquals($value, $result[$keys[$key]], $key . " GetandCheck function determines that for key {$key} value: $value doesn't match " . print_r($result[$keys[$key]], TRUE) . $errorText);
2023 * Get formatted values in the actual and expected result.
2025 * @param array $actual
2026 * Actual calculated values.
2027 * @param array $expected
2030 public function checkArrayEquals(&$actual, &$expected) {
2031 self
::unsetId($actual);
2032 self
::unsetId($expected);
2033 $this->assertEquals($expected, $actual);
2037 * Unset the key 'id' from the array
2039 * @param array $unformattedArray
2040 * The array from which the 'id' has to be unset.
2042 public static function unsetId(&$unformattedArray) {
2043 $formattedArray = [];
2044 if (array_key_exists('id', $unformattedArray)) {
2045 unset($unformattedArray['id']);
2047 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
2048 foreach ($unformattedArray['values'] as $key => $value) {
2049 if (is_array($value)) {
2050 foreach ($value as $k => $v) {
2056 elseif ($key == 'id') {
2057 $unformattedArray[$key];
2059 $formattedArray = [$value];
2061 $unformattedArray['values'] = $formattedArray;
2066 * Helper to enable/disable custom directory support
2068 * @param array $customDirs
2070 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2071 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2073 public function customDirectories($customDirs) {
2074 $config = CRM_Core_Config
::singleton();
2076 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2077 unset($config->customPHPPathDir
);
2079 elseif ($customDirs['php_path'] === TRUE) {
2080 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2083 $config->customPHPPathDir
= $php_path;
2086 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2087 unset($config->customTemplateDir
);
2089 elseif ($customDirs['template_path'] === TRUE) {
2090 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2093 $config->customTemplateDir
= $template_path;
2098 * Generate a temporary folder.
2100 * @param string $prefix
2104 public function createTempDir($prefix = 'test-') {
2105 $tempDir = CRM_Utils_File
::tempdir($prefix);
2106 $this->tempDirs
[] = $tempDir;
2110 public function cleanTempDirs() {
2111 if (!is_array($this->tempDirs
)) {
2112 // fix test errors where this is not set
2115 foreach ($this->tempDirs
as $tempDir) {
2116 if (is_dir($tempDir)) {
2117 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2123 * Temporarily replace the singleton extension with a different one.
2125 * @param \CRM_Extension_System $system
2127 public function setExtensionSystem(CRM_Extension_System
$system) {
2128 if ($this->origExtensionSystem
== NULL) {
2129 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2131 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2134 public function unsetExtensionSystem() {
2135 if ($this->origExtensionSystem
!== NULL) {
2136 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2137 $this->origExtensionSystem
= NULL;
2142 * Temporarily alter the settings-metadata to add a mock setting.
2144 * WARNING: The setting metadata will disappear on the next cache-clear.
2150 public function setMockSettingsMetaData($extras) {
2151 CRM_Utils_Hook
::singleton()
2152 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2153 $metadata = array_merge($metadata, $extras);
2156 Civi
::service('settings_manager')->flush();
2158 $fields = $this->callAPISuccess('setting', 'getfields', []);
2159 foreach ($extras as $key => $spec) {
2160 $this->assertNotEmpty($spec['title']);
2161 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2166 * @param string $name
2168 public function financialAccountDelete($name) {
2169 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2170 $financialAccount->name
= $name;
2171 if ($financialAccount->find(TRUE)) {
2172 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2173 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2174 $entityFinancialType->delete();
2175 $financialAccount->delete();
2180 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2181 * (NB unclear if this is still required)
2183 public function _sethtmlGlobals() {
2184 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2186 'html_quickform_rule_required',
2187 'HTML/QuickForm/Rule/Required.php',
2190 'html_quickform_rule_range',
2191 'HTML/QuickForm/Rule/Range.php',
2194 'html_quickform_rule_range',
2195 'HTML/QuickForm/Rule/Range.php',
2198 'html_quickform_rule_range',
2199 'HTML/QuickForm/Rule/Range.php',
2202 'html_quickform_rule_email',
2203 'HTML/QuickForm/Rule/Email.php',
2206 'html_quickform_rule_regex',
2207 'HTML/QuickForm/Rule/Regex.php',
2210 'html_quickform_rule_regex',
2211 'HTML/QuickForm/Rule/Regex.php',
2214 'html_quickform_rule_regex',
2215 'HTML/QuickForm/Rule/Regex.php',
2218 'html_quickform_rule_regex',
2219 'HTML/QuickForm/Rule/Regex.php',
2221 'nopunctuation' => [
2222 'html_quickform_rule_regex',
2223 'HTML/QuickForm/Rule/Regex.php',
2226 'html_quickform_rule_regex',
2227 'HTML/QuickForm/Rule/Regex.php',
2230 'html_quickform_rule_callback',
2231 'HTML/QuickForm/Rule/Callback.php',
2234 'html_quickform_rule_compare',
2235 'HTML/QuickForm/Rule/Compare.php',
2238 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2239 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2241 'HTML/QuickForm/group.php',
2242 'HTML_QuickForm_group',
2245 'HTML/QuickForm/hidden.php',
2246 'HTML_QuickForm_hidden',
2249 'HTML/QuickForm/reset.php',
2250 'HTML_QuickForm_reset',
2253 'HTML/QuickForm/checkbox.php',
2254 'HTML_QuickForm_checkbox',
2257 'HTML/QuickForm/file.php',
2258 'HTML_QuickForm_file',
2261 'HTML/QuickForm/image.php',
2262 'HTML_QuickForm_image',
2265 'HTML/QuickForm/password.php',
2266 'HTML_QuickForm_password',
2269 'HTML/QuickForm/radio.php',
2270 'HTML_QuickForm_radio',
2273 'HTML/QuickForm/button.php',
2274 'HTML_QuickForm_button',
2277 'HTML/QuickForm/submit.php',
2278 'HTML_QuickForm_submit',
2281 'HTML/QuickForm/select.php',
2282 'HTML_QuickForm_select',
2285 'HTML/QuickForm/hiddenselect.php',
2286 'HTML_QuickForm_hiddenselect',
2289 'HTML/QuickForm/text.php',
2290 'HTML_QuickForm_text',
2293 'HTML/QuickForm/textarea.php',
2294 'HTML_QuickForm_textarea',
2297 'HTML/QuickForm/fckeditor.php',
2298 'HTML_QuickForm_FCKEditor',
2301 'HTML/QuickForm/tinymce.php',
2302 'HTML_QuickForm_TinyMCE',
2305 'HTML/QuickForm/dojoeditor.php',
2306 'HTML_QuickForm_dojoeditor',
2309 'HTML/QuickForm/link.php',
2310 'HTML_QuickForm_link',
2313 'HTML/QuickForm/advcheckbox.php',
2314 'HTML_QuickForm_advcheckbox',
2317 'HTML/QuickForm/date.php',
2318 'HTML_QuickForm_date',
2321 'HTML/QuickForm/static.php',
2322 'HTML_QuickForm_static',
2325 'HTML/QuickForm/header.php',
2326 'HTML_QuickForm_header',
2329 'HTML/QuickForm/html.php',
2330 'HTML_QuickForm_html',
2333 'HTML/QuickForm/hierselect.php',
2334 'HTML_QuickForm_hierselect',
2337 'HTML/QuickForm/autocomplete.php',
2338 'HTML_QuickForm_autocomplete',
2341 'HTML/QuickForm/xbutton.php',
2342 'HTML_QuickForm_xbutton',
2344 'advmultiselect' => [
2345 'HTML/QuickForm/advmultiselect.php',
2346 'HTML_QuickForm_advmultiselect',
2352 * Set up an acl allowing contact to see 2 specified groups
2353 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2355 * You need to have pre-created these groups & created the user e.g
2356 * $this->createLoggedInUser();
2357 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2358 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2360 * @param bool $isProfile
2362 public function setupACL($isProfile = FALSE) {
2364 $_REQUEST = $this->_params
;
2366 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2367 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2368 $ov = new CRM_Core_DAO_OptionValue();
2369 $ov->option_group_id
= $optionGroupID;
2371 if ($ov->find(TRUE)) {
2372 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2374 $optionValue = $this->callAPISuccess('option_value', 'create', [
2375 'option_group_id' => $optionGroupID,
2376 'label' => 'pick me',
2380 CRM_Core_DAO
::executeQuery("
2381 TRUNCATE civicrm_acl_cache
2384 CRM_Core_DAO
::executeQuery("
2385 TRUNCATE civicrm_acl_contact_cache
2388 CRM_Core_DAO
::executeQuery("
2389 INSERT INTO civicrm_acl_entity_role (
2390 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2391 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2395 CRM_Core_DAO
::executeQuery("
2396 INSERT INTO civicrm_acl (
2397 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2400 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2405 CRM_Core_DAO
::executeQuery("
2406 INSERT INTO civicrm_acl (
2407 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2410 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2414 CRM_Core_DAO
::executeQuery("
2415 INSERT INTO civicrm_acl (
2416 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2419 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2424 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2425 $this->callAPISuccess('group_contact', 'create', [
2426 'group_id' => $this->_permissionedGroup
,
2427 'contact_id' => $this->_loggedInUser
,
2432 CRM_ACL_BAO_Cache
::resetCache();
2433 CRM_ACL_API
::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2438 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2440 public function offsetDefaultPriceSet() {
2441 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2442 $firstID = $contributionPriceSet['id'];
2443 $this->callAPISuccess('price_set', 'create', [
2444 'id' => $contributionPriceSet['id'],
2448 unset($contributionPriceSet['id']);
2449 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2450 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2451 'price_set_id' => $firstID,
2452 'options' => ['limit' => 1],
2454 unset($priceField['id']);
2455 $priceField['price_set_id'] = $newPriceSet['id'];
2456 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2457 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2458 'price_set_id' => $firstID,
2460 'options' => ['limit' => 1],
2463 unset($priceFieldValue['id']);
2464 //create some padding to use up ids
2465 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2466 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2467 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2471 * Create an instance of the paypal processor.
2473 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2474 * this parent class & we don't have a structure for that yet
2475 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2476 * & the best protection against that is the functions this class affords
2478 * @param array $params
2480 * @return int $result['id'] payment processor id
2482 public function paymentProcessorCreate($params = []) {
2483 $params = array_merge([
2485 'domain_id' => CRM_Core_Config
::domainID(),
2486 'payment_processor_type_id' => 'PayPal',
2490 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2491 'password' => '1183377788',
2492 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2493 'url_site' => 'https://www.sandbox.paypal.com/',
2494 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2495 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2496 'class_name' => 'Payment_PayPalImpl',
2497 'billing_mode' => 3,
2498 'financial_type_id' => 1,
2499 'financial_account_id' => 12,
2500 // Credit card = 1 so can pass 'by accident'.
2501 'payment_instrument_id' => 'Debit Card',
2503 if (!is_numeric($params['payment_processor_type_id'])) {
2504 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2506 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2507 'name' => $params['payment_processor_type_id'],
2511 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2512 return $result['id'];
2516 * Set up initial recurring payment allowing subsequent IPN payments.
2518 * @param array $recurParams (Optional)
2519 * @param array $contributionParams (Optional)
2521 * @throws \CRM_Core_Exception
2523 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2524 $this->ids
['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
2525 $contributionParams = array_merge([
2526 'total_amount' => '200',
2527 'invoice_id' => $this->_invoiceID
,
2528 'financial_type_id' => 'Donation',
2529 'contribution_status_id' => 'Pending',
2530 'contact_id' => $this->_contactID
,
2531 'contribution_page_id' => $this->_contributionPageID
,
2532 'payment_processor_id' => $this->_paymentProcessorID
,
2534 'receive_date' => '2019-07-25 07:34:23',
2535 'skipCleanMoney' => TRUE,
2536 'amount_level' => 'expensive',
2537 'campaign_id' => $this->ids
['campaign'][0],
2538 'source' => 'Online Contribution: Page name',
2539 ], $contributionParams);
2540 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2541 'contact_id' => $this->_contactID
,
2544 'installments' => 5,
2545 'frequency_unit' => 'Month',
2546 'frequency_interval' => 1,
2547 'invoice_id' => $this->_invoiceID
,
2548 'contribution_status_id' => 2,
2549 'payment_processor_id' => $this->_paymentProcessorID
,
2550 // processor provided ID - use contact ID as proxy.
2551 'processor_id' => $this->_contactID
,
2552 'api.Order.create' => $contributionParams,
2553 ], $recurParams))['values'][0];
2554 $this->_contributionRecurID
= $contributionRecur['id'];
2555 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2556 $this->ids
['Contribution'][0] = $this->_contributionID
;
2560 * We don't have a good way to set up a recurring contribution with a membership so let's just do one then alter it
2562 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2564 * @throws \CRM_Core_Exception
2566 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2567 $membershipParams = $recurParams = [];
2568 if (!empty($params['duration_unit'])) {
2569 $membershipParams['duration_unit'] = $params['duration_unit'];
2571 if (!empty($params['frequency_unit'])) {
2572 $recurParams['frequency_unit'] = $params['frequency_unit'];
2575 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2576 //create a contribution so our membership & contribution don't both have id = 1
2577 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2578 $this->contributionCreate([
2579 'contact_id' => $this->_contactID
,
2581 'financial_type_id' => 1,
2582 'invoice_id' => 'abcd',
2584 'receive_date' => '2019-07-25 07:34:23',
2588 $this->ids
['membership'] = $this->callAPISuccess('Membership', 'create', [
2589 'contact_id' => $this->_contactID
,
2590 'membership_type_id' => $this->ids
['membership_type'],
2591 'format.only_id' => TRUE,
2592 'source' => 'Payment',
2593 'skipLineItem' => TRUE,
2595 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2600 'entity_table' => 'civicrm_membership',
2601 'entity_id' => $this->ids
['membership'],
2602 'label' => 'General',
2604 'unit_price' => 200,
2605 'line_total' => 200,
2606 'financial_type_id' => 1,
2607 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2609 'label' => 'Membership Amount',
2610 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2612 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2614 'label' => 'General',
2615 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2622 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids
['membership'], 'contribution_recur_id' => $this->_contributionRecurID
]);
2630 public function CiviUnitTestCase_fatalErrorHandler($message) {
2631 throw new Exception("{$message['message']}: {$message['code']}");
2635 * Wrap the entire test case in a transaction.
2637 * Only subsequent DB statements will be wrapped in TX -- this cannot
2638 * retroactively wrap old DB statements. Therefore, it makes sense to
2639 * call this at the beginning of setUp().
2641 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2642 * this option does not work with, e.g., custom-data.
2644 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2645 * if TRUNCATE or ALTER is called while using a transaction.
2648 * Whether to use nesting or reference-counting.
2650 public function useTransaction($nest = TRUE) {
2652 $this->tx
= new CRM_Core_Transaction($nest);
2653 $this->tx
->rollback();
2658 * Assert the attachment exists.
2660 * @param bool $exists
2661 * @param array $apiResult
2663 protected function assertAttachmentExistence($exists, $apiResult) {
2664 $fileId = $apiResult['id'];
2665 $this->assertTrue(is_numeric($fileId));
2666 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2667 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2668 1 => [$fileId, 'Int'],
2670 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2671 1 => [$fileId, 'Int'],
2676 * Assert 2 sql strings are the same, ignoring double spaces.
2678 * @param string $expectedSQL
2679 * @param string $actualSQL
2680 * @param string $message
2682 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2683 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2684 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2685 $this->assertEquals($expected, $actual, $message);
2689 * Create a price set for an event.
2691 * @param int $feeTotal
2692 * @param int $minAmt
2693 * @param string $type
2695 * @param array $options
2699 * @throws \CRM_Core_Exception
2701 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2702 // creating price set, price field
2703 $paramsSet['title'] = 'Price Set';
2704 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2705 $paramsSet['is_active'] = FALSE;
2706 $paramsSet['extends'] = 1;
2707 $paramsSet['min_amount'] = $minAmt;
2709 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2710 $this->_ids
['price_set'] = $priceSet->id
;
2713 'label' => 'Price Field',
2714 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2715 'html_type' => $type,
2716 'price' => $feeTotal,
2717 'option_label' => ['1' => 'Price Field'],
2718 'option_value' => ['1' => $feeTotal],
2719 'option_name' => ['1' => $feeTotal],
2720 'option_weight' => ['1' => 1],
2721 'option_amount' => ['1' => 1],
2722 'is_display_amounts' => 1,
2724 'options_per_line' => 1,
2725 'is_active' => ['1' => 1],
2726 'price_set_id' => $this->_ids
['price_set'],
2727 'is_enter_qty' => 1,
2728 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2730 if ($type === 'Radio') {
2731 foreach ($options as $index => $option) {
2732 $paramsField['is_enter_qty'] = 0;
2733 $optionID = $index +
2;
2734 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2735 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2739 $this->callAPISuccess('PriceField', 'create', $paramsField);
2740 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2741 $this->_ids
['price_field'] = array_keys($fields['values']);
2742 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2743 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2745 return $this->_ids
['price_set'];
2749 * Add a profile to a contribution page.
2751 * @param string $name
2752 * @param int $contributionPageID
2753 * @param string $module
2755 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2757 'uf_group_id' => $name,
2758 'module' => $module,
2759 'entity_table' => 'civicrm_contribution_page',
2760 'entity_id' => $contributionPageID,
2763 if ($module !== 'CiviContribute') {
2764 $params['module_data'] = [$module => []];
2766 $this->callAPISuccess('UFJoin', 'create', $params);
2770 * Add participant with contribution
2774 * @throws \CRM_Core_Exception
2776 protected function createPartiallyPaidParticipantOrder() {
2777 $orderParams = $this->getParticipantOrderParams();
2778 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2779 return $this->callAPISuccess('Order', 'create', $orderParams);
2785 * @param string $component
2786 * @param int $componentId
2787 * @param array $priceFieldOptions
2791 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2792 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2793 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2794 $paramsSet['is_active'] = TRUE;
2795 $paramsSet['financial_type_id'] = 'Event Fee';
2796 $paramsSet['extends'] = 1;
2797 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2798 $priceSetId = $priceSet['id'];
2799 //Checking for priceset added in the table.
2800 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2801 'id', $paramsSet['title'], 'Check DB for created priceset'
2803 $paramsField = array_merge([
2804 'label' => 'Price Field',
2805 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2806 'html_type' => 'CheckBox',
2807 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2808 'option_value' => ['1' => 100, '2' => 200],
2809 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2810 'option_weight' => ['1' => 1, '2' => 2],
2811 'option_amount' => ['1' => 100, '2' => 200],
2812 'is_display_amounts' => 1,
2814 'options_per_line' => 1,
2815 'is_active' => ['1' => 1, '2' => 1],
2816 'price_set_id' => $priceSet['id'],
2817 'is_enter_qty' => 1,
2818 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2819 ], $priceFieldOptions);
2821 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2823 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSetId);
2825 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2829 * Replace the template with a test-oriented template designed to show all the variables.
2831 * @param string $templateName
2832 * @param string $type
2834 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2835 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2836 CRM_Core_DAO
::executeQuery(
2837 "UPDATE civicrm_msg_template
2838 SET msg_{$type} = %1
2839 WHERE workflow_name = '{$templateName}'
2840 AND is_default = 1", [1 => [$testTemplate, 'String']]
2845 * Reinstate the default template.
2847 * @param string $templateName
2848 * @param string $type
2850 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2851 CRM_Core_DAO
::executeQuery(
2852 "UPDATE civicrm_option_group og
2853 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2854 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2855 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2856 SET m.msg_{$type} = m2.msg_{$type}
2857 WHERE og.name = 'msg_tpl_workflow_contribution'
2858 AND ov.name = '{$templateName}'
2859 AND m.is_default = 1"
2864 * Flush statics relating to financial type.
2866 protected function flushFinancialTypeStatics() {
2867 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2868 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2870 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2871 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2873 CRM_Contribute_PseudoConstant
::flush('financialType');
2874 CRM_Contribute_PseudoConstant
::flush('membershipType');
2875 // Pseudoconstants may be saved to the cache table.
2876 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2877 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2878 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2882 * Set the permissions to the supplied array.
2884 * @param array $permissions
2886 protected function setPermissions($permissions) {
2887 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2888 $this->flushFinancialTypeStatics();
2892 * @param array $params
2895 public function _checkFinancialRecords($params, $context) {
2897 'entity_id' => $params['id'],
2898 'entity_table' => 'civicrm_contribution',
2900 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2901 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2902 if ($context == 'pending') {
2903 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2904 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2907 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2909 'id' => $trxn['financial_trxn_id'],
2911 if ($context != 'online' && $context != 'payLater') {
2913 'to_financial_account_id' => 6,
2914 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2918 if ($context == 'feeAmount') {
2919 $compareParams['fee_amount'] = 50;
2921 elseif ($context == 'online') {
2923 'to_financial_account_id' => 12,
2924 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2926 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2929 elseif ($context == 'payLater') {
2931 'to_financial_account_id' => 7,
2932 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2936 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2938 'financial_trxn_id' => $trxn['financial_trxn_id'],
2939 'entity_table' => 'civicrm_financial_item',
2941 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2943 'id' => $entityTrxn['entity_id'],
2946 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2948 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2950 if ($context == 'payLater') {
2952 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2954 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2957 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2958 if ($context == 'feeAmount') {
2960 'entity_id' => $params['id'],
2961 'entity_table' => 'civicrm_contribution',
2963 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2965 'id' => $maxTrxn['financial_trxn_id'],
2968 'to_financial_account_id' => 5,
2969 'from_financial_account_id' => 6,
2970 'total_amount' => 50,
2973 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2974 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2976 'entity_id' => $trxnId['financialTrxnId'],
2977 'entity_table' => 'civicrm_financial_trxn',
2982 'financial_account_id' => 5,
2984 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2986 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2987 // line should be copied into all the functions that call this function & evaluated there
2988 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2989 // when calling completeTransaction or repeatTransaction.
2990 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2994 * Return financial type id on basis of name
2996 * @param string $name Financial type m/c name
3000 public function getFinancialTypeId($name) {
3001 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
3005 * Cleanup function for contents of $this->ids.
3007 * This is a best effort cleanup to use in tear downs etc.
3009 * It will not fail if the data has already been removed (some tests may do
3010 * their own cleanup).
3012 protected function cleanUpSetUpIDs() {
3013 foreach ($this->setupIDs
as $entity => $id) {
3015 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
3017 catch (CiviCRM_API3_Exception
$e) {
3018 // This is a best-effort cleanup function, ignore.
3024 * Create Financial Type.
3026 * @param array $params
3030 protected function createFinancialType($params = []) {
3031 $params = array_merge($params,
3033 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
3037 return $this->callAPISuccess('FinancialType', 'create', $params);
3041 * Create Payment Instrument.
3043 * @param array $params
3044 * @param string $financialAccountName
3048 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3049 $params = array_merge([
3050 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3051 'option_group_id' => 'payment_instrument',
3054 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3056 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3058 $financialAccountParams = [
3059 'entity_table' => 'civicrm_option_value',
3060 'entity_id' => $newPaymentInstrument,
3061 'account_relationship' => $relationTypeID,
3062 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3064 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3066 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3070 * Enable Tax and Invoicing
3072 * @param array $params
3074 * @return \Civi\Core\SettingsBag
3076 protected function enableTaxAndInvoicing($params = []) {
3077 // Enable component contribute setting
3078 $contributeSetting = array_merge($params,
3081 'invoice_prefix' => 'INV_',
3083 'due_date_period' => 'days',
3085 'is_email_pdf' => 1,
3086 'tax_term' => 'Sales Tax',
3087 'tax_display_settings' => 'Inclusive',
3090 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3094 * Enable Tax and Invoicing
3096 * @throws \CRM_Core_Exception
3098 protected function disableTaxAndInvoicing() {
3099 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3100 foreach ($accounts as $account) {
3101 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3102 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3105 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3106 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3108 return Civi
::settings()->set('invoicing', FALSE);
3112 * Add Sales Tax Account for the financial type.
3114 * @param int $financialTypeId
3116 * @param array $accountParams
3118 * @return CRM_Financial_DAO_EntityFinancialAccount
3119 * @throws \CRM_Core_Exception
3121 protected function addTaxAccountToFinancialType(int $financialTypeId, $accountParams = []) {
3122 $params = array_merge([
3123 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3124 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3125 'is_deductible' => 1,
3130 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3132 'entity_table' => 'civicrm_financial_type',
3133 'entity_id' => $financialTypeId,
3134 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3137 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3138 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = $params['tax_rate'];
3140 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3141 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3142 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3143 $dao->copyValues($entityParams);
3145 if ($dao->fetch()) {
3146 $entityParams['id'] = $dao->id
;
3148 $entityParams['financial_account_id'] = $account->id
;
3150 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3154 * Create price set with contribution test for test setup.
3156 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3157 * on parent class at some point (fn is not in 4.4).
3160 * @param array $params
3162 public function createPriceSetWithPage($entity = NULL, $params = []) {
3163 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3164 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3165 'title' => "Test Contribution Page",
3166 'financial_type_id' => 1,
3167 'currency' => 'NZD',
3168 'goal_amount' => 50,
3169 'is_pay_later' => 1,
3170 'is_monetary' => TRUE,
3171 'is_email_receipt' => FALSE,
3173 $priceSet = $this->callAPISuccess('price_set', 'create', [
3174 'is_quick_config' => 0,
3175 'extends' => 'CiviMember',
3176 'financial_type_id' => 1,
3177 'title' => 'my Page',
3179 $priceSetID = $priceSet['id'];
3181 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3182 $priceField = $this->callAPISuccess('price_field', 'create', [
3183 'price_set_id' => $priceSetID,
3184 'label' => 'Goat Breed',
3185 'html_type' => 'Radio',
3187 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3188 'price_set_id' => $priceSetID,
3189 'price_field_id' => $priceField['id'],
3190 'label' => 'Long Haired Goat',
3192 'financial_type_id' => 'Donation',
3193 'membership_type_id' => $membershipTypeID,
3194 'membership_num_terms' => 1,
3196 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3197 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3198 'price_set_id' => $priceSetID,
3199 'price_field_id' => $priceField['id'],
3200 'label' => 'Shoe-eating Goat',
3202 'financial_type_id' => 'Donation',
3203 'membership_type_id' => $membershipTypeID,
3204 'membership_num_terms' => 2,
3206 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3208 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3209 'price_set_id' => $priceSetID,
3210 'price_field_id' => $priceField['id'],
3211 'label' => 'Shoe-eating Goat',
3213 'financial_type_id' => 'Donation',
3215 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3217 $this->_ids
['price_set'] = $priceSetID;
3218 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3219 $this->_ids
['price_field'] = [$priceField['id']];
3221 $this->_ids
['membership_type'] = $membershipTypeID;
3225 * Only specified contact returned.
3227 * @implements CRM_Utils_Hook::aclWhereClause
3231 * @param $whereTables
3235 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3236 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3240 * @implements CRM_Utils_Hook::selectWhereClause
3242 * @param string $entity
3243 * @param array $clauses
3245 public function selectWhereClauseHook($entity, &$clauses) {
3246 if ($entity == 'Event') {
3247 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3252 * An implementation of hook_civicrm_post used with all our test cases.
3255 * @param string $objectName
3256 * @param int $objectId
3259 public function onPost($op, $objectName, $objectId, &$objectRef) {
3260 if ($op == 'create' && $objectName == 'Individual') {
3261 CRM_Core_DAO
::executeQuery(
3262 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3264 1 => [$objectId, 'Integer'],
3269 if ($op == 'edit' && $objectName == 'Participant') {
3271 1 => [$objectId, 'Integer'],
3273 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3274 CRM_Core_DAO
::executeQuery($query, $params);
3279 * Instantiate form object.
3281 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3283 * @param string $class
3284 * Name of form class.
3286 * @param array $formValues
3288 * @param string $pageName
3290 * @return \CRM_Core_Form
3291 * @throws \CRM_Core_Exception
3293 public function getFormObject($class, $formValues = [], $pageName = '') {
3294 $_POST = $formValues;
3295 /* @var CRM_Core_Form $form */
3296 $form = new $class();
3297 $_SERVER['REQUEST_METHOD'] = 'GET';
3299 case 'CRM_Event_Cart_Form_Checkout_Payment':
3300 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3301 $form->controller
= new CRM_Event_Cart_Controller_Checkout();
3305 $form->controller
= new CRM_Core_Controller();
3308 $pageName = $form->getName();
3310 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3311 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3316 * Get possible thousand separators.
3320 public function getThousandSeparators() {
3321 return [['.'], [',']];
3325 * Get the boolean options as a provider.
3329 public function getBooleanDataProvider() {
3330 return [[TRUE], [FALSE]];
3334 * Set the separators for thousands and decimal points.
3336 * Note that this only covers some common scenarios.
3338 * It does not cater for a situation where the thousand separator is a [space]
3339 * Latter is the Norwegian localization. At least some tests need to
3340 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3341 * to provide broader coverage.
3343 * @param string $thousandSeparator
3345 protected function setCurrencySeparators($thousandSeparator) {
3346 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3347 Civi
::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3351 * Sets the thousand separator.
3353 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3355 * @param $thousandSeparator
3357 protected function setMonetaryThousandSeparator($thousandSeparator) {
3358 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3362 * Sets the decimal separator.
3364 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3366 * @param $decimalPoint
3368 protected function setMonetaryDecimalPoint($decimalPoint) {
3369 Civi
::settings()->set('monetaryDecimalPoint', $decimalPoint);
3373 * Sets the default currency.
3377 protected function setDefaultCurrency($currency) {
3378 Civi
::settings()->set('defaultCurrency', $currency);
3382 * Format money as it would be input.
3384 * @param string $amount
3388 protected function formatMoneyInput($amount) {
3389 return CRM_Utils_Money
::format($amount, NULL, '%a');
3393 * Get the contribution object.
3395 * @param int $contributionID
3397 * @return \CRM_Contribute_BAO_Contribution
3399 protected function getContributionObject($contributionID) {
3400 $contributionObj = new CRM_Contribute_BAO_Contribution();
3401 $contributionObj->id
= $contributionID;
3402 $contributionObj->find(TRUE);
3403 return $contributionObj;
3407 * Enable multilingual.
3409 public function enableMultilingual() {
3410 $this->callAPISuccess('Setting', 'create', [
3411 'lcMessages' => 'en_US',
3412 'languageLimit' => [
3417 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3420 $dbLocale = '_en_US';
3424 * Setup or clean up SMS tests
3426 * @param bool $teardown
3428 * @throws \CiviCRM_API3_Exception
3430 public function setupForSmsTests($teardown = FALSE) {
3431 require_once 'CiviTest/CiviTestSMSProvider.php';
3433 // Option value params for CiviTestSMSProvider
3434 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3436 'option_group_id' => $groupID,
3437 'label' => 'unittestSMS',
3438 'value' => 'unit.test.sms',
3439 'name' => 'CiviTestSMSProvider',
3446 // Test completed, delete provider
3447 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3448 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3452 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3453 // in CRM/Sms/Provider.php even though it is not an extension.
3454 return civicrm_api3('option_value', 'create', $params);
3458 * Start capturing browser output.
3460 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3462 protected function startCapturingOutput() {
3464 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3468 * Stop capturing browser output and return as a csv.
3470 * @param bool $isFirstRowHeaders
3472 * @return \League\Csv\Reader
3474 * @throws \League\Csv\Exception
3476 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3477 $output = ob_get_flush();
3478 $stream = fopen('php://memory', 'r+');
3479 fwrite($stream, $output);
3481 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3482 $csv = Reader
::createFromString($output);
3483 if ($isFirstRowHeaders) {
3484 $csv->setHeaderOffset(0);
3491 * Rename various labels to not match the names.
3493 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3494 * code that expects it to.
3496 protected function renameLabels() {
3497 $replacements = ['Pending', 'Refunded'];
3498 foreach ($replacements as $name) {
3499 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3504 * Undo any label renaming.
3506 protected function resetLabels() {
3507 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3511 * Get parameters to set up a multi-line participant order.
3514 * @throws \CRM_Core_Exception
3516 protected function getParticipantOrderParams(): array {
3517 $this->_contactId
= $this->individualCreate();
3518 $event = $this->eventCreate();
3519 $this->_eventId
= $event['id'];
3521 'id' => $this->_eventId
,
3522 'financial_type_id' => 4,
3525 $this->callAPISuccess('event', 'create', $eventParams);
3526 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3527 $participantParams = [
3528 'financial_type_id' => 4,
3529 'event_id' => $this->_eventId
,
3532 'fee_currency' => 'USD',
3533 'contact_id' => $this->_contactId
,
3535 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3537 'total_amount' => 300,
3538 'currency' => 'USD',
3539 'contact_id' => $this->_contactId
,
3540 'financial_type_id' => 4,
3541 'contribution_status_id' => 'Pending',
3542 'contribution_mode' => 'participant',
3543 'participant_id' => $participant['id'],
3545 foreach ($priceFields['values'] as $key => $priceField) {
3546 $orderParams['line_items'][] = [
3549 'price_field_id' => $priceField['price_field_id'],
3550 'price_field_value_id' => $priceField['id'],
3551 'label' => $priceField['label'],
3552 'field_title' => $priceField['label'],
3554 'unit_price' => $priceField['amount'],
3555 'line_total' => $priceField['amount'],
3556 'financial_type_id' => $priceField['financial_type_id'],
3557 'entity_table' => 'civicrm_participant',
3560 'params' => $participant,
3563 return $orderParams;
3569 * @throws \CRM_Core_Exception
3571 protected function validatePayments($payments) {
3572 foreach ($payments as $payment) {
3573 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3574 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3575 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3576 // This is a pretty rough way at guessing which payment is the overpayment - but
3577 // for the test suite it should be enough.
3580 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3581 'financial_trxn_id' => $payment['id'],
3582 'entity_table' => 'civicrm_financial_item',
3583 'return' => ['amount'],
3586 foreach ($items as $item) {
3587 $itemTotal +
= $item['amount'];
3589 $this->assertEquals($payment['total_amount'], $itemTotal);
3594 * Validate all created payments.
3596 * @throws \CRM_Core_Exception
3598 protected function validateAllPayments() {
3599 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3600 $this->validatePayments($payments);
3604 * Validate all created contributions.
3606 * @throws \CRM_Core_Exception
3608 protected function validateAllContributions() {
3609 $contributions = $this->callAPISuccess('Contribution', 'get', ['return' => ['tax_amount', 'total_amount']])['values'];
3610 foreach ($contributions as $contribution) {
3611 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3614 foreach ($lineItems as $lineItem) {
3615 $total +
= $lineItem['line_total'];
3616 $taxTotal +
= (float) ($lineItem['tax_amount'] ??
0);
3618 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ??
0));
3619 $this->assertEquals($total, $contribution['total_amount']);
3625 * @throws \CRM_Core_Exception
3627 protected function createRuleGroup() {
3628 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3629 'contact_type' => 'Individual',
3631 'used' => 'General',
3632 'name' => 'TestRule',
3633 'title' => 'TestRule',
3640 * Generic create test.
3642 * @param int $version
3644 * @throws \CRM_Core_Exception
3646 protected function basicCreateTest(int $version) {
3647 $this->_apiversion
= $version;
3648 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
3649 $this->assertEquals(1, $result['count']);
3650 $this->assertNotNull($result['values'][$result['id']]['id']);
3651 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
3655 * Generic delete test.
3657 * @param int $version
3659 * @throws \CRM_Core_Exception
3661 protected function basicDeleteTest($version) {
3662 $this->_apiversion
= $version;
3663 $result = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
3664 $deleteParams = ['id' => $result['id']];
3665 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
3666 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
3667 $this->assertEquals(0, $checkDeleted['count']);
3671 * Create and return a case object for the given Client ID.
3673 * @param int $clientId
3674 * @param int $loggedInUser
3675 * Omit or pass NULL to use the same as clientId
3676 * @param array $extra
3677 * Optional specific parameters such as start_date
3679 * @return CRM_Case_BAO_Case
3681 public function createCase($clientId, $loggedInUser = NULL, $extra = []) {
3682 if (empty($loggedInUser)) {
3683 // backwards compatibility - but it's more typical that the creator is a different person than the client
3684 $loggedInUser = $clientId;
3686 $caseParams = array_merge([
3687 'activity_subject' => 'Case Subject',
3688 'client_id' => $clientId,
3689 'case_type_id' => 1,
3691 'case_type' => 'housing_support',
3692 'subject' => 'Case Subject',
3693 'start_date' => date("Y-m-d"),
3694 'start_date_time' => date("YmdHis"),
3696 'activity_details' => '',
3698 $form = new CRM_Case_Form_Case();
3699 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3703 * Validate that all location entities have exactly one primary.
3705 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3707 public function assertLocationValidity() {
3708 $this->assertEquals(0, CRM_Core_DAO
::singleValueQuery('SELECT COUNT(*) FROM
3710 (SELECT a1.contact_id
3711 FROM civicrm_address a1
3712 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3713 AND a1.contact_id = a2.contact_id
3716 AND a2.id IS NOT NULL
3717 AND a1.contact_id IS NOT NULL
3719 SELECT a1.contact_id
3720 FROM civicrm_address a1
3721 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3722 AND a1.contact_id = a2.contact_id
3723 WHERE a1.is_primary = 0
3725 AND a1.contact_id IS NOT NULL
3729 SELECT a1.contact_id
3730 FROM civicrm_email a1
3731 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3732 AND a1.contact_id = a2.contact_id
3735 AND a2.id IS NOT NULL
3736 AND a1.contact_id IS NOT NULL
3738 SELECT a1.contact_id
3739 FROM civicrm_email a1
3740 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3741 AND a1.contact_id = a2.contact_id
3742 WHERE a1.is_primary = 0
3744 AND a1.contact_id IS NOT NULL
3748 SELECT a1.contact_id
3749 FROM civicrm_phone a1
3750 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3751 AND a1.contact_id = a2.contact_id
3754 AND a2.id IS NOT NULL
3755 AND a1.contact_id IS NOT NULL
3757 SELECT a1.contact_id
3758 FROM civicrm_phone a1
3759 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3760 AND a1.contact_id = a2.contact_id
3761 WHERE a1.is_primary = 0
3763 AND a1.contact_id IS NOT NULL
3767 SELECT a1.contact_id
3769 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3770 AND a1.contact_id = a2.contact_id
3773 AND a2.id IS NOT NULL
3774 AND a1.contact_id IS NOT NULL
3776 SELECT a1.contact_id
3778 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3779 AND a1.contact_id = a2.contact_id
3780 WHERE a1.is_primary = 0
3782 AND a1.contact_id IS NOT NULL
3786 SELECT a1.contact_id
3787 FROM civicrm_openid a1
3788 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3789 AND a1.contact_id = a2.contact_id
3790 WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3793 SELECT a1.contact_id
3794 FROM civicrm_openid a1
3795 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3796 AND a1.contact_id = a2.contact_id
3799 AND a2.id IS NOT NULL
3800 AND a1.contact_id IS NOT NULL
3802 SELECT a1.contact_id
3803 FROM civicrm_openid a1
3804 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3805 AND a1.contact_id = a2.contact_id
3806 WHERE a1.is_primary = 0
3808 AND a1.contact_id IS NOT NULL) as primary_descrepancies
3813 * Ensure the specified mysql mode/s are activated.
3815 * @param array $modes
3817 protected function ensureMySQLMode(array $modes): void
{
3818 $currentModes = array_fill_keys(CRM_Utils_SQL
::getSqlModes(), 1);
3819 $currentModes = array_merge($currentModes, array_fill_keys($modes, 1));
3820 CRM_Core_DAO
::executeQuery("SET GLOBAL sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
3821 CRM_Core_DAO
::executeQuery("SET sql_mode = '" . implode(',', array_keys($currentModes)) . "'");