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 = [];
93 * @var array of temporary directory names
98 * @var bool populateOnce allows to skip db resets in setUp
100 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
101 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
104 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
106 * see also: http://forum.civicrm.org/index.php/topic,18065.0.html
108 public static $populateOnce = FALSE;
111 * @var bool DBResetRequired allows skipping DB reset
112 * in specific test case. If you still need
113 * to reset single test (method) of such case, call
114 * $this->cleanDB() in the first line of this
117 public $DBResetRequired = TRUE;
120 * @var CRM_Core_Transaction|NULL
125 * Array of IDs created to support the test.
128 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
135 * Class used for hooks during tests.
137 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
139 * $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
141 * @var CRM_Utils_Hook_UnitTests hookClass
143 public $hookClass = NULL;
146 * @var array common values to be re-used multiple times within a class - usually to create the relevant entity
148 protected $_params = [];
151 * @var CRM_Extension_System
153 protected $origExtensionSystem;
156 * Array of IDs created during test setup routine.
158 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
162 public $setupIDs = [];
165 * PHPUnit Mock Method to use.
169 public $mockMethod = 'getMock';
174 * Because we are overriding the parent class constructor, we
175 * need to show the same arguments as exist in the constructor of
176 * PHPUnit_Framework_TestCase, since
177 * PHPUnit_Framework_TestSuite::createTest() creates a
178 * ReflectionClass of the Test class and checks the constructor
179 * of that class to decide how to set up the test.
181 * @param string $name
183 * @param string $dataName
185 public function __construct($name = NULL, array $data = [], $dataName = '') {
186 parent
::__construct($name, $data, $dataName);
188 // we need full error reporting
189 error_reporting(E_ALL
& ~E_NOTICE
);
191 self
::$_dbName = self
::getDBName();
193 // also load the class loader
194 require_once 'CRM/Core/ClassLoader.php';
195 CRM_Core_ClassLoader
::singleton()->register();
196 if (function_exists('_civix_phpunit_setUp')) {
197 // FIXME: loosen coupling
198 _civix_phpunit_setUp();
200 if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version
::id(), '5', '>=')) {
201 $this->mockMethod
= 'createMock';
203 elseif (class_exists('PHPUnit\Runner\Version') && version_compare(PHPUnit\Runner\Version
::id(), '6', '>=')) {
204 $this->mockMethod
= 'createMock';
209 * Override to run the test and assert its state.
213 * @throws \PHPUnit_Framework_IncompleteTest
214 * @throws \PHPUnit_Framework_SkippedTest
216 protected function runTest() {
218 return parent
::runTest();
220 catch (PEAR_Exception
$e) {
221 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
222 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
229 public function requireDBReset() {
230 return $this->DBResetRequired
;
236 public static function getDBName() {
237 static $dbName = NULL;
238 if ($dbName === NULL) {
239 require_once "DB.php";
240 $dsninfo = DB
::parseDSN(CIVICRM_DSN
);
241 $dbName = $dsninfo['database'];
247 * Create database connection for this instance.
249 * Initialize the test database if it hasn't been initialized
252 protected function getConnection() {
253 if (!self
::$dbInit) {
254 $dbName = self
::getDBName();
256 // install test database
257 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
259 static::_populateDB(FALSE, $this);
261 self
::$dbInit = TRUE;
267 * Required implementation of abstract method.
269 protected function getDataSet() {
273 * @param bool $perClass
274 * @param null $object
277 * TRUE if the populate logic runs; FALSE if it is skipped
279 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
280 if (CIVICRM_UF
!== 'UnitTests') {
281 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
284 if ($perClass ||
$object == NULL) {
288 $dbreset = $object->requireDBReset();
291 if (self
::$populateOnce ||
!$dbreset) {
294 self
::$populateOnce = NULL;
296 Civi\Test
::data()->populate();
301 public static function setUpBeforeClass() {
302 static::_populateDB(TRUE);
304 // also set this global hack
305 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
309 * Common setup functions for all unit tests.
311 protected function setUp() {
312 $session = CRM_Core_Session
::singleton();
313 $session->set('userID', NULL);
315 $this->_apiversion
= 3;
318 $this->errorScope
= CRM_Core_TemporaryErrorScope
::useException();
319 // Use a temporary file for STDIN
320 $GLOBALS['stdin'] = tmpfile();
321 if ($GLOBALS['stdin'] === FALSE) {
322 echo "Couldn't open temporary file\n";
326 // Get and save a connection to the database
327 $this->_dbconn
= $this->getConnection();
329 // reload database before each test
330 // $this->_populateDB();
332 // "initialize" CiviCRM to avoid problems when running single tests
333 // FIXME: look at it closer in second stage
335 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
336 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
338 // disable any left-over test extensions
339 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
341 // reset all the caches
342 CRM_Utils_System
::flushCache();
344 // initialize the object once db is loaded
345 \Civi
::$statics = [];
347 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
349 // when running unit tests, use mockup user framework
350 $this->hookClass
= CRM_Utils_Hook
::singleton();
352 // Make sure the DB connection is setup properly
353 $config->userSystem
->setMySQLTimeZone();
354 $env = new CRM_Utils_Check_Component_Env();
355 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
357 // clear permissions stub to not check permissions
358 $config->userPermissionClass
->permissions
= NULL;
360 //flush component settings
361 CRM_Core_Component
::getEnabledComponents(TRUE);
363 $_REQUEST = $_GET = $_POST = [];
364 error_reporting(E_ALL
);
366 $this->renameLabels();
367 $this->_sethtmlGlobals();
371 * Read everything from the datasets directory and insert into the db.
373 public function loadAllFixtures() {
374 $fixturesDir = __DIR__
. '/../../fixtures';
376 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
378 $jsonFiles = glob($fixturesDir . '/*.json');
379 foreach ($jsonFiles as $jsonFixture) {
380 $json = json_decode(file_get_contents($jsonFixture));
381 foreach ($json as $tableName => $vars) {
382 if ($tableName === 'civicrm_contact') {
383 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');
386 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
388 foreach ($vars as $entity) {
389 $keys = $values = [];
390 foreach ($entity as $key => $value) {
392 $values[] = is_numeric($value) ?
$value : "'{$value}'";
394 CRM_Core_DAO
::executeQuery("
395 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
402 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
406 * Load the data that used to be handled by the discontinued dbunit class.
408 * This could do with further tidy up - the initial priority is simply to get rid of
409 * the dbunity package which is no longer supported.
411 * @param string $fileName
413 protected function loadXMLDataSet($fileName) {
414 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
415 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
416 foreach ($xml as $tableName => $element) {
417 if (!empty($element)) {
418 foreach ($element as $row) {
419 $keys = $values = [];
420 if (isset($row['@attributes'])) {
421 foreach ($row['@attributes'] as $key => $value) {
423 $values[] = is_numeric($value) ?
$value : "'{$value}'";
426 elseif (!empty($row)) {
427 // cos we copied it & it is inconsistent....
428 foreach ($row as $key => $value) {
430 $values[] = is_numeric($value) ?
$value : "'{$value}'";
434 if (!empty($values)) {
435 CRM_Core_DAO
::executeQuery("
436 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
442 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
446 * Create default domain contacts for the two domains added during test class.
447 * database population.
449 public function createDomainContacts() {
450 $this->organizationCreate();
451 $this->organizationCreate(['organization_name' => 'Second Domain']);
455 * Common teardown functions for all unit tests.
457 protected function tearDown() {
458 $this->_apiversion
= 3;
459 $this->resetLabels();
461 error_reporting(E_ALL
& ~E_NOTICE
);
462 CRM_Utils_Hook
::singleton()->reset();
463 if ($this->hookClass
) {
464 $this->hookClass
->reset();
466 CRM_Core_Session
::singleton()->reset(1);
469 $this->tx
->rollback()->commit();
472 CRM_Core_Transaction
::forceRollbackIfEnabled();
473 \Civi\Core\Transaction\Manager
::singleton(TRUE);
476 CRM_Core_Transaction
::forceRollbackIfEnabled();
477 \Civi\Core\Transaction\Manager
::singleton(TRUE);
479 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match'];
480 $this->quickCleanup($tablesToTruncate);
481 $this->createDomainContacts();
484 $this->cleanTempDirs();
485 $this->unsetExtensionSystem();
486 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
487 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
491 * Create a batch of external API calls which can
492 * be executed concurrently.
495 * $calls = $this->createExternalAPI()
496 * ->addCall('Contact', 'get', ...)
497 * ->addCall('Contact', 'get', ...)
503 * @return \Civi\API\ExternalBatch
504 * @throws PHPUnit_Framework_SkippedTestError
506 public function createExternalAPI() {
507 global $civicrm_root;
509 'version' => $this->_apiversion
,
513 $calls = new \Civi\API\
ExternalBatch($defaultParams);
515 if (!$calls->isSupported()) {
516 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
523 * Create required data based on $this->entity & $this->params
524 * This is just a way to set up the test data for delete & get functions
525 * so the distinction between set
526 * up & tested functions is clearer
531 public function createTestEntity() {
532 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
536 * @param int $contactTypeId
540 public function contactTypeDelete($contactTypeId) {
541 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
543 throw new Exception('Could not delete contact type');
548 * @param array $params
552 public function membershipTypeCreate($params = []) {
553 CRM_Member_PseudoConstant
::flush('membershipType');
554 CRM_Core_Config
::clearDBCache();
555 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
556 $params = array_merge([
558 'duration_unit' => 'year',
559 'duration_interval' => 1,
560 'period_type' => 'rolling',
561 'member_of_contact_id' => $memberOfOrganization,
563 'financial_type_id' => 2,
566 'visibility' => 'Public',
569 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
571 CRM_Member_PseudoConstant
::flush('membershipType');
572 CRM_Utils_Cache
::singleton()->flush();
574 return $result['id'];
580 * @param array $params
583 * @throws \CRM_Core_Exception
585 public function contactMembershipCreate($params) {
586 $params = array_merge([
587 'join_date' => '2007-01-21',
588 'start_date' => '2007-01-21',
589 'end_date' => '2007-12-21',
590 'source' => 'Payment',
591 'membership_type_id' => 'General',
593 if (!is_numeric($params['membership_type_id'])) {
594 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
595 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
596 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
600 $result = $this->callAPISuccess('Membership', 'create', $params);
601 return $result['id'];
605 * Delete Membership Type.
607 * @param array $params
609 public function membershipTypeDelete($params) {
610 $this->callAPISuccess('MembershipType', 'Delete', $params);
614 * @param int $membershipID
616 public function membershipDelete($membershipID) {
617 $deleteParams = ['id' => $membershipID];
618 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
622 * @param string $name
626 public function membershipStatusCreate($name = 'test member status') {
627 $params['name'] = $name;
628 $params['start_event'] = 'start_date';
629 $params['end_event'] = 'end_date';
630 $params['is_current_member'] = 1;
631 $params['is_active'] = 1;
633 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
634 CRM_Member_PseudoConstant
::flush('membershipStatus');
635 return $result['id'];
639 * @param int $membershipStatusID
641 public function membershipStatusDelete($membershipStatusID) {
642 if (!$membershipStatusID) {
645 $result = $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
648 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
649 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
650 $renewedMembershipEndDate = new DateTime($membershipEndDate);
651 switch ($durationUnit) {
653 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
657 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
658 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
659 $renewedMembershipEndDate->add(new DateInterval('P1D'));
660 $renewedMembershipEndDate->add(new DateInterval('P1M'));
661 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
664 return $renewedMembershipEndDate->format('Y-m-d');
668 * Create a relationship type.
670 * @param array $params
674 * @throws \CRM_Core_Exception
676 public function relationshipTypeCreate($params = []) {
677 $params = array_merge([
678 'name_a_b' => 'Relation 1 for relationship type create',
679 'name_b_a' => 'Relation 2 for relationship type create',
680 'contact_type_a' => 'Individual',
681 'contact_type_b' => 'Organization',
686 $result = $this->callAPISuccess('relationship_type', 'create', $params);
687 CRM_Core_PseudoConstant
::flush('relationshipType');
689 return $result['id'];
693 * Delete Relatinship Type.
695 * @param int $relationshipTypeID
697 public function relationshipTypeDelete($relationshipTypeID) {
698 $params['id'] = $relationshipTypeID;
699 $check = $this->callAPISuccess('relationship_type', 'get', $params);
700 if (!empty($check['count'])) {
701 $this->callAPISuccess('relationship_type', 'delete', $params);
706 * @param array $params
709 * @throws \CRM_Core_Exception
711 public function paymentProcessorTypeCreate($params = NULL) {
712 if (is_null($params)) {
714 'name' => 'API_Test_PP',
715 'title' => 'API Test Payment Processor',
716 'class_name' => 'CRM_Core_Payment_APITest',
717 'billing_mode' => 'form',
723 $result = $this->callAPISuccess('payment_processor_type', 'create', $params);
725 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
727 return $result['id'];
731 * Create test Authorize.net instance.
733 * @param array $params
737 public function paymentProcessorAuthorizeNetCreate($params = []) {
738 $params = array_merge([
739 'name' => 'Authorize',
740 'domain_id' => CRM_Core_Config
::domainID(),
741 'payment_processor_type_id' => 'AuthNet',
742 'title' => 'AuthNet',
747 'user_name' => '4y5BfuW7jm',
748 'password' => '4cAmW927n8uLf5J8',
749 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
750 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
751 'class_name' => 'Payment_AuthorizeNet',
755 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
756 return $result['id'];
760 * Create Participant.
762 * @param array $params
763 * Array of contact id and event id values.
766 * $id of participant created
768 public function participantCreate($params = []) {
769 if (empty($params['contact_id'])) {
770 $params['contact_id'] = $this->individualCreate();
772 if (empty($params['event_id'])) {
773 $event = $this->eventCreate();
774 $params['event_id'] = $event['id'];
779 'register_date' => 20070219,
780 'source' => 'Wimbeldon',
781 'event_level' => 'Payment',
785 $params = array_merge($defaults, $params);
786 $result = $this->callAPISuccess('Participant', 'create', $params);
787 return $result['id'];
791 * Create Payment Processor.
794 * Id Payment Processor
796 public function processorCreate($params = []) {
800 'payment_processor_type_id' => 'Dummy',
801 'financial_account_id' => 12,
805 'url_site' => 'http://dummy.com',
806 'url_recur' => 'http://dummy.com',
809 'payment_instrument_id' => 'Debit Card',
811 $processorParams = array_merge($processorParams, $params);
812 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
813 return $processor['id'];
817 * Create Payment Processor.
819 * @param array $processorParams
821 * @return \CRM_Core_Payment_Dummy
822 * Instance of Dummy Payment Processor
824 * @throws \CiviCRM_API3_Exception
826 public function dummyProcessorCreate($processorParams = []) {
827 $paymentProcessorID = $this->processorCreate($processorParams);
828 // 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
829 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
830 $processorParams['is_test'] = FALSE;
831 $this->processorCreate($processorParams);
832 return System
::singleton()->getById($paymentProcessorID);
836 * Create contribution page.
838 * @param array $params
841 * Array of contribution page
843 public function contributionPageCreate($params = []) {
844 $this->_pageParams
= array_merge([
845 'title' => 'Test Contribution Page',
846 'financial_type_id' => 1,
848 'financial_account_id' => 1,
850 'is_allow_other_amount' => 1,
852 'max_amount' => 1000,
854 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
858 * Create a sample batch.
860 public function batchCreate() {
861 $params = $this->_params
;
862 $params['name'] = $params['title'] = 'Batch_433397';
863 $params['status_id'] = 1;
864 $result = $this->callAPISuccess('batch', 'create', $params);
865 return $result['id'];
871 * @param array $params
874 * result of created tag
876 public function tagCreate($params = []) {
878 'name' => 'New Tag3',
879 'description' => 'This is description for Our New Tag ',
882 $params = array_merge($defaults, $params);
883 $result = $this->callAPISuccess('Tag', 'create', $params);
884 return $result['values'][$result['id']];
891 * Id of the tag to be deleted.
895 public function tagDelete($tagId) {
896 require_once 'api/api.php';
900 $result = $this->callAPISuccess('Tag', 'delete', $params);
901 return $result['id'];
905 * Add entity(s) to the tag
907 * @param array $params
911 public function entityTagAdd($params) {
912 $result = $this->callAPISuccess('entity_tag', 'create', $params);
919 * @param array $params
923 * id of created pledge
925 public function pledgeCreate($params) {
926 $params = array_merge([
927 'pledge_create_date' => date('Ymd'),
928 'start_date' => date('Ymd'),
929 'scheduled_date' => date('Ymd'),
931 'pledge_status_id' => '2',
932 'financial_type_id' => '1',
933 'pledge_original_installment_amount' => 20,
934 'frequency_interval' => 5,
935 'frequency_unit' => 'year',
936 'frequency_day' => 15,
941 $result = $this->callAPISuccess('Pledge', 'create', $params);
942 return $result['id'];
946 * Delete contribution.
948 * @param int $pledgeId
950 public function pledgeDelete($pledgeId) {
952 'pledge_id' => $pledgeId,
954 $this->callAPISuccess('Pledge', 'delete', $params);
958 * Create contribution.
960 * @param array $params
961 * Array of parameters.
964 * id of created contribution
965 * @throws \CRM_Core_Exception
967 public function contributionCreate($params) {
969 $params = array_merge([
971 'receive_date' => date('Ymd'),
972 'total_amount' => 100.00,
973 'fee_amount' => 5.00,
974 'financial_type_id' => 1,
975 'payment_instrument_id' => 1,
976 'non_deductible_amount' => 10.00,
978 'contribution_status_id' => 1,
981 $result = $this->callAPISuccess('contribution', 'create', $params);
982 return $result['id'];
986 * Delete contribution.
988 * @param int $contributionId
991 * @throws \CRM_Core_Exception
993 public function contributionDelete($contributionId) {
995 'contribution_id' => $contributionId,
997 $result = $this->callAPISuccess('contribution', 'delete', $params);
1004 * @param array $params
1005 * Name-value pair for an event.
1009 public function eventCreate($params = []) {
1010 // if no contact was passed, make up a dummy event creator
1011 if (!isset($params['contact_id'])) {
1012 $params['contact_id'] = $this->_contactCreate([
1013 'contact_type' => 'Individual',
1014 'first_name' => 'Event',
1015 'last_name' => 'Creator',
1019 // set defaults for missing params
1020 $params = array_merge([
1021 'title' => 'Annual CiviCRM meet',
1022 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1023 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1024 'event_type_id' => 1,
1026 'start_date' => 20081021,
1027 'end_date' => 20081023,
1028 'is_online_registration' => 1,
1029 'registration_start_date' => 20080601,
1030 'registration_end_date' => 20081015,
1031 'max_participants' => 100,
1032 'event_full_text' => 'Sorry! We are already full',
1035 'is_show_location' => 0,
1036 'is_email_confirm' => 1,
1039 return $this->callAPISuccess('Event', 'create', $params);
1043 * Create a paid event.
1045 * @param array $params
1049 * @throws \CRM_Core_Exception
1051 protected function eventCreatePaid($params) {
1052 $event = $this->eventCreate($params);
1053 $this->priceSetID
= $this->eventPriceSetCreate(55, 0, 'Radio');
1054 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->priceSetID
);
1055 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->priceSetID
, TRUE, FALSE);
1056 $priceSet = CRM_Utils_Array
::value($this->priceSetID
, $priceSet);
1057 $this->eventFeeBlock
= CRM_Utils_Array
::value('fields', $priceSet);
1069 public function eventDelete($id) {
1073 return $this->callAPISuccess('event', 'delete', $params);
1077 * Delete participant.
1079 * @param int $participantID
1083 public function participantDelete($participantID) {
1085 'id' => $participantID,
1087 $check = $this->callAPISuccess('Participant', 'get', $params);
1088 if ($check['count'] > 0) {
1089 return $this->callAPISuccess('Participant', 'delete', $params);
1094 * Create participant payment.
1096 * @param int $participantID
1097 * @param int $contributionID
1100 * $id of created payment
1102 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1103 //Create Participant Payment record With Values
1105 'participant_id' => $participantID,
1106 'contribution_id' => $contributionID,
1109 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1110 return $result['id'];
1114 * Delete participant payment.
1116 * @param int $paymentID
1118 public function participantPaymentDelete($paymentID) {
1122 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1128 * @param int $contactID
1131 * location id of created location
1133 public function locationAdd($contactID) {
1136 'location_type' => 'New Location Type',
1138 'name' => 'Saint Helier St',
1139 'county' => 'Marin',
1140 'country' => 'UNITED STATES',
1141 'state_province' => 'Michigan',
1142 'supplemental_address_1' => 'Hallmark Ct',
1143 'supplemental_address_2' => 'Jersey Village',
1144 'supplemental_address_3' => 'My Town',
1149 'contact_id' => $contactID,
1150 'address' => $address,
1151 'location_format' => '2.0',
1152 'location_type' => 'New Location Type',
1155 $result = $this->callAPISuccess('Location', 'create', $params);
1160 * Delete Locations of contact.
1162 * @param array $params
1165 public function locationDelete($params) {
1166 $this->callAPISuccess('Location', 'delete', $params);
1170 * Add a Location Type.
1172 * @param array $params
1174 * @return CRM_Core_DAO_LocationType
1175 * location id of created location
1177 public function locationTypeCreate($params = NULL) {
1178 if ($params === NULL) {
1180 'name' => 'New Location Type',
1181 'vcard_name' => 'New Location Type',
1182 'description' => 'Location Type for Delete',
1187 $locationType = new CRM_Core_DAO_LocationType();
1188 $locationType->copyValues($params);
1189 $locationType->save();
1190 // clear getfields cache
1191 CRM_Core_PseudoConstant
::flush();
1192 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1193 return $locationType;
1197 * Delete a Location Type.
1199 * @param int $locationTypeId
1201 public function locationTypeDelete($locationTypeId) {
1202 $locationType = new CRM_Core_DAO_LocationType();
1203 $locationType->id
= $locationTypeId;
1204 $locationType->delete();
1210 * @param array $params
1212 * @return CRM_Core_DAO_Mapping
1213 * Mapping id of created mapping
1215 public function mappingCreate($params = NULL) {
1216 if ($params === NULL) {
1218 'name' => 'Mapping name',
1219 'description' => 'Mapping description',
1220 // 'Export Contact' mapping.
1221 'mapping_type_id' => 7,
1225 $mapping = new CRM_Core_DAO_Mapping();
1226 $mapping->copyValues($params);
1228 // clear getfields cache
1229 CRM_Core_PseudoConstant
::flush();
1230 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1237 * @param int $mappingId
1239 public function mappingDelete($mappingId) {
1240 $mapping = new CRM_Core_DAO_Mapping();
1241 $mapping->id
= $mappingId;
1246 * Prepare class for ACLs.
1248 protected function prepareForACLs() {
1249 $config = CRM_Core_Config
::singleton();
1250 $config->userPermissionClass
->permissions
= [];
1256 protected function cleanUpAfterACLs() {
1257 CRM_Utils_Hook
::singleton()->reset();
1258 $tablesToTruncate = [
1260 'civicrm_acl_cache',
1261 'civicrm_acl_entity_role',
1262 'civicrm_acl_contact_cache',
1264 $this->quickCleanup($tablesToTruncate);
1265 $config = CRM_Core_Config
::singleton();
1266 unset($config->userPermissionClass
->permissions
);
1270 * Create a smart group.
1272 * By default it will be a group of households.
1274 * @param array $smartGroupParams
1275 * @param array $groupParams
1276 * @param string $contactType
1280 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1281 $smartGroupParams = array_merge([
1282 'formValues' => ['contact_type' => ['IN' => [$contactType]]],
1285 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1287 $groupParams['saved_search_id'] = $savedSearch->id
;
1288 return $this->groupCreate($groupParams);
1294 * @param array $params
1296 public function uFFieldCreate($params = []) {
1297 $params = array_merge([
1299 'field_name' => 'first_name',
1302 'visibility' => 'Public Pages and Listings',
1303 'is_searchable' => '1',
1304 'label' => 'first_name',
1305 'field_type' => 'Individual',
1308 $this->callAPISuccess('uf_field', 'create', $params);
1312 * Add a UF Join Entry.
1314 * @param array $params
1317 * $id of created UF Join
1319 public function ufjoinCreate($params = NULL) {
1320 if ($params === NULL) {
1323 'module' => 'CiviEvent',
1324 'entity_table' => 'civicrm_event',
1330 $result = $this->callAPISuccess('uf_join', 'create', $params);
1335 * @param array $params
1336 * Optional parameters.
1337 * @param bool $reloadConfig
1338 * While enabling CiviCampaign component, we shouldn't always forcibly
1339 * reload config as this hinder hook call in test environment
1344 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1345 $this->enableCiviCampaign($reloadConfig);
1346 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1347 'name' => 'big_campaign',
1348 'title' => 'Campaign',
1350 return $campaign['id'];
1354 * Create Group for a contact.
1356 * @param int $contactId
1358 public function contactGroupCreate($contactId) {
1360 'contact_id.1' => $contactId,
1364 $this->callAPISuccess('GroupContact', 'Create', $params);
1368 * Delete Group for a contact.
1370 * @param int $contactId
1372 public function contactGroupDelete($contactId) {
1374 'contact_id.1' => $contactId,
1377 $this->civicrm_api('GroupContact', 'Delete', $params);
1383 * @param array $params
1387 public function activityCreate($params = []) {
1388 $params = array_merge([
1389 'subject' => 'Discussion on warm beer',
1390 'activity_date_time' => date('Ymd'),
1392 'location' => 'Baker Street',
1393 'details' => 'Lets schedule a meeting',
1395 'activity_type_id' => 'Meeting',
1397 if (!isset($params['source_contact_id'])) {
1398 $params['source_contact_id'] = $this->individualCreate();
1400 if (!isset($params['target_contact_id'])) {
1401 $params['target_contact_id'] = $this->individualCreate([
1402 'first_name' => 'Julia',
1403 'last_name' => 'Anderson',
1405 'email' => 'julia_anderson@civicrm.org',
1406 'contact_type' => 'Individual',
1409 if (!isset($params['assignee_contact_id'])) {
1410 $params['assignee_contact_id'] = $params['target_contact_id'];
1413 $result = civicrm_api3('Activity', 'create', $params);
1415 $result['target_contact_id'] = $params['target_contact_id'];
1416 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1421 * Create an activity type.
1423 * @param array $params
1428 public function activityTypeCreate($params) {
1429 return $this->callAPISuccess('ActivityType', 'create', $params);
1433 * Delete activity type.
1435 * @param int $activityTypeId
1436 * Id of the activity type.
1440 public function activityTypeDelete($activityTypeId) {
1441 $params['activity_type_id'] = $activityTypeId;
1442 return $this->callAPISuccess('ActivityType', 'delete', $params);
1446 * Create custom group.
1448 * @param array $params
1452 public function customGroupCreate($params = []) {
1454 'title' => 'new custom group',
1455 'extends' => 'Contact',
1457 'style' => 'Inline',
1461 $params = array_merge($defaults, $params);
1463 return $this->callAPISuccess('custom_group', 'create', $params);
1467 * Existing function doesn't allow params to be over-ridden so need a new one
1468 * this one allows you to only pass in the params you want to change
1470 * @param array $params
1474 public function CustomGroupCreateByParams($params = []) {
1476 'title' => "API Custom Group",
1477 'extends' => 'Contact',
1479 'style' => 'Inline',
1482 $params = array_merge($defaults, $params);
1483 return $this->callAPISuccess('custom_group', 'create', $params);
1487 * Create custom group with multi fields.
1489 * @param array $params
1493 public function CustomGroupMultipleCreateByParams($params = []) {
1498 $params = array_merge($defaults, $params);
1499 return $this->CustomGroupCreateByParams($params);
1503 * Create custom group with multi fields.
1505 * @param array $params
1509 public function CustomGroupMultipleCreateWithFields($params = []) {
1510 // also need to pass on $params['custom_field'] if not set but not in place yet
1512 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1513 $ids['custom_group_id'] = $customGroup['id'];
1515 $customField = $this->customFieldCreate([
1516 'custom_group_id' => $ids['custom_group_id'],
1517 'label' => 'field_1' . $ids['custom_group_id'],
1521 $ids['custom_field_id'][] = $customField['id'];
1523 $customField = $this->customFieldCreate([
1524 'custom_group_id' => $ids['custom_group_id'],
1525 'default_value' => '',
1526 'label' => 'field_2' . $ids['custom_group_id'],
1529 $ids['custom_field_id'][] = $customField['id'];
1531 $customField = $this->customFieldCreate([
1532 'custom_group_id' => $ids['custom_group_id'],
1533 'default_value' => '',
1534 'label' => 'field_3' . $ids['custom_group_id'],
1537 $ids['custom_field_id'][] = $customField['id'];
1543 * Create a custom group with a single text custom field. See
1544 * participant:testCreateWithCustom for how to use this
1546 * @param string $function
1548 * @param string $filename
1552 * ids of created objects
1554 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1555 $params = ['title' => $function];
1556 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1557 $params['extends'] = $entity ?
$entity : 'Contact';
1558 $customGroup = $this->customGroupCreate($params);
1559 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1560 CRM_Core_PseudoConstant
::flush();
1562 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1566 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1567 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1569 * @param string $function
1571 * @param string $filename
1575 * ids of created objects
1577 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1578 $params = ['title' => $function];
1579 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1580 $params['extends'] = $entity ?
$entity : 'Contact';
1581 $customGroup = $this->customGroupCreate($params);
1582 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1583 CRM_Core_PseudoConstant
::flush();
1585 'defaultValue' => 'Default Value',
1586 'lowercasevalue' => 'Lowercase Value',
1587 1 => 'Integer Value',
1590 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1591 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1592 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1593 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1594 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1595 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1596 foreach ($options as $option_value => $option_label) {
1597 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1598 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1602 'custom_group_id' => $customGroup['id'],
1603 'custom_field_id' => $customField['id'],
1604 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1605 'custom_field_group_options' => $options,
1610 * Delete custom group.
1612 * @param int $customGroupID
1616 public function customGroupDelete($customGroupID) {
1617 $params['id'] = $customGroupID;
1618 return $this->callAPISuccess('custom_group', 'delete', $params);
1622 * Create custom field.
1624 * @param array $params
1625 * (custom_group_id) is required.
1629 public function customFieldCreate($params) {
1630 $params = array_merge([
1631 'label' => 'Custom Field',
1632 'data_type' => 'String',
1633 'html_type' => 'Text',
1634 'is_searchable' => 1,
1636 'default_value' => 'defaultValue',
1639 $result = $this->callAPISuccess('custom_field', 'create', $params);
1640 // these 2 functions are called with force to flush static caches
1641 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1642 CRM_Core_Component
::getEnabledComponents(1);
1647 * Delete custom field.
1649 * @param int $customFieldID
1653 public function customFieldDelete($customFieldID) {
1655 $params['id'] = $customFieldID;
1656 return $this->callAPISuccess('custom_field', 'delete', $params);
1666 public function noteCreate($cId) {
1668 'entity_table' => 'civicrm_contact',
1669 'entity_id' => $cId,
1670 'note' => 'hello I am testing Note',
1671 'contact_id' => $cId,
1672 'modified_date' => date('Ymd'),
1673 'subject' => 'Test Note',
1676 return $this->callAPISuccess('Note', 'create', $params);
1680 * Enable CiviCampaign Component.
1682 * @param bool $reloadConfig
1683 * Force relaod config or not
1685 public function enableCiviCampaign($reloadConfig = TRUE) {
1686 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1687 if ($reloadConfig) {
1688 // force reload of config object
1689 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1691 //flush cache by calling with reset
1692 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1696 * Create custom field with Option Values.
1698 * @param array $customGroup
1699 * @param string $name
1700 * Name of custom field.
1701 * @param array $extraParams
1702 * Additional parameters to pass through.
1706 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1708 'custom_group_id' => $customGroup['id'],
1709 'name' => 'test_custom_group',
1710 'label' => 'Country',
1711 'html_type' => 'Select',
1712 'data_type' => 'String',
1715 'is_searchable' => 0,
1721 'name' => 'option_group1',
1722 'label' => 'option_group_label1',
1726 'option_label' => ['Label1', 'Label2'],
1727 'option_value' => ['value1', 'value2'],
1728 'option_name' => [$name . '_1', $name . '_2'],
1729 'option_weight' => [1, 2],
1730 'option_status' => [1, 1],
1733 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1735 return $this->callAPISuccess('custom_field', 'create', $params);
1743 public function confirmEntitiesDeleted($entities) {
1744 foreach ($entities as $entity) {
1746 $result = $this->callAPISuccess($entity, 'Get', []);
1747 if ($result['error'] == 1 ||
$result['count'] > 0) {
1748 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1756 * Quick clean by emptying tables created for the test.
1758 * @param array $tablesToTruncate
1759 * @param bool $dropCustomValueTables
1761 * @throws \CRM_Core_Exception
1763 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1765 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1767 if ($dropCustomValueTables) {
1768 $optionGroupResult = CRM_Core_DAO
::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1769 while ($optionGroupResult->fetch()) {
1770 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1771 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1772 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1773 // generic cleanup routine.
1774 if (!empty($optionGroupResult->option_group_id
) && $optionGroupResult->option_group_id
> 50) {
1775 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id
);
1778 $tablesToTruncate[] = 'civicrm_custom_group';
1779 $tablesToTruncate[] = 'civicrm_custom_field';
1782 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1784 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1785 foreach ($tablesToTruncate as $table) {
1786 $sql = "TRUNCATE TABLE $table";
1787 CRM_Core_DAO
::executeQuery($sql);
1789 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1791 if ($dropCustomValueTables) {
1792 $dbName = self
::getDBName();
1794 SELECT TABLE_NAME as tableName
1795 FROM INFORMATION_SCHEMA.TABLES
1796 WHERE TABLE_SCHEMA = '{$dbName}'
1797 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1800 $tableDAO = CRM_Core_DAO
::executeQuery($query);
1801 while ($tableDAO->fetch()) {
1802 $sql = "DROP TABLE {$tableDAO->tableName}";
1803 CRM_Core_DAO
::executeQuery($sql);
1809 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1811 * @throws \CRM_Core_Exception
1813 public function quickCleanUpFinancialEntities() {
1814 $tablesToTruncate = [
1816 'civicrm_activity_contact',
1817 'civicrm_contribution',
1818 'civicrm_contribution_soft',
1819 'civicrm_contribution_product',
1820 'civicrm_financial_trxn',
1821 'civicrm_financial_item',
1822 'civicrm_contribution_recur',
1823 'civicrm_line_item',
1824 'civicrm_contribution_page',
1825 'civicrm_payment_processor',
1826 'civicrm_entity_financial_trxn',
1827 'civicrm_membership',
1828 'civicrm_membership_type',
1829 'civicrm_membership_payment',
1830 'civicrm_membership_log',
1831 'civicrm_membership_block',
1833 'civicrm_participant',
1834 'civicrm_participant_payment',
1836 'civicrm_pledge_block',
1837 'civicrm_pledge_payment',
1838 'civicrm_price_set_entity',
1839 'civicrm_price_field_value',
1840 'civicrm_price_field',
1842 $this->quickCleanup($tablesToTruncate);
1843 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1844 $this->restoreDefaultPriceSetConfig();
1845 $this->disableTaxAndInvoicing();
1846 $this->setCurrencySeparators(',');
1847 CRM_Core_PseudoConstant
::flush('taxRates');
1848 System
::singleton()->flushProcessors();
1849 // @fixme this parameter is leaking - it should not be defined as a class static
1850 // but for now we just handle in tear down.
1851 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1855 * Reset the price set config so results exist.
1857 public function restoreDefaultPriceSetConfig() {
1858 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1859 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1860 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)");
1861 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)");
1865 * Recreate default membership types.
1867 public function restoreMembershipTypes() {
1868 CRM_Core_DAO
::executeQuery(
1869 "REPLACE INTO civicrm_membership_type
1870 (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)
1872 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1873 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1874 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1879 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1880 * Default behaviour is to also delete the entity
1881 * @param array $params
1882 * Params array to check against.
1884 * Id of the entity concerned.
1885 * @param string $entity
1886 * Name of entity concerned (e.g. membership).
1887 * @param bool $delete
1888 * Should the entity be deleted as part of this check.
1889 * @param string $errorText
1890 * Text to print on error.
1894 * @param array $params
1897 * @param int $delete
1898 * @param string $errorText
1900 * @throws CRM_Core_Exception
1902 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1904 $result = $this->callAPISuccessGetSingle($entity, [
1909 $this->callAPISuccess($entity, 'Delete', [
1913 $dateFields = $keys = $dateTimeFields = [];
1914 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1915 foreach ($fields['values'] as $field => $settings) {
1916 if (array_key_exists($field, $result)) {
1917 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
1920 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
1922 $type = CRM_Utils_Array
::value('type', $settings);
1923 if ($type == CRM_Utils_Type
::T_DATE
) {
1924 $dateFields[] = $settings['name'];
1925 // we should identify both real names & unique names as dates
1926 if ($field != $settings['name']) {
1927 $dateFields[] = $field;
1930 if ($type == CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
1931 $dateTimeFields[] = $settings['name'];
1932 // we should identify both real names & unique names as dates
1933 if ($field != $settings['name']) {
1934 $dateTimeFields[] = $field;
1939 if (strtolower($entity) == 'contribution') {
1940 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1941 // this is not returned in id format
1942 unset($params['payment_instrument_id']);
1943 $params['contribution_source'] = $params['source'];
1944 unset($params['source']);
1947 foreach ($params as $key => $value) {
1948 if ($key == 'version' ||
substr($key, 0, 3) == 'api' ||
!array_key_exists($keys[$key], $result)) {
1951 if (in_array($key, $dateFields)) {
1952 $value = date('Y-m-d', strtotime($value));
1953 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1955 if (in_array($key, $dateTimeFields)) {
1956 $value = date('Y-m-d H:i:s', strtotime($value));
1957 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
1959 $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);
1964 * Get formatted values in the actual and expected result.
1966 * @param array $actual
1967 * Actual calculated values.
1968 * @param array $expected
1971 public function checkArrayEquals(&$actual, &$expected) {
1972 self
::unsetId($actual);
1973 self
::unsetId($expected);
1974 $this->assertEquals($expected, $actual);
1978 * Unset the key 'id' from the array
1980 * @param array $unformattedArray
1981 * The array from which the 'id' has to be unset.
1983 public static function unsetId(&$unformattedArray) {
1984 $formattedArray = [];
1985 if (array_key_exists('id', $unformattedArray)) {
1986 unset($unformattedArray['id']);
1988 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
1989 foreach ($unformattedArray['values'] as $key => $value) {
1990 if (is_array($value)) {
1991 foreach ($value as $k => $v) {
1997 elseif ($key == 'id') {
1998 $unformattedArray[$key];
2000 $formattedArray = [$value];
2002 $unformattedArray['values'] = $formattedArray;
2007 * Helper to enable/disable custom directory support
2009 * @param array $customDirs
2011 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2012 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2014 public function customDirectories($customDirs) {
2015 $config = CRM_Core_Config
::singleton();
2017 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2018 unset($config->customPHPPathDir
);
2020 elseif ($customDirs['php_path'] === TRUE) {
2021 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2024 $config->customPHPPathDir
= $php_path;
2027 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2028 unset($config->customTemplateDir
);
2030 elseif ($customDirs['template_path'] === TRUE) {
2031 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2034 $config->customTemplateDir
= $template_path;
2039 * Generate a temporary folder.
2041 * @param string $prefix
2045 public function createTempDir($prefix = 'test-') {
2046 $tempDir = CRM_Utils_File
::tempdir($prefix);
2047 $this->tempDirs
[] = $tempDir;
2051 public function cleanTempDirs() {
2052 if (!is_array($this->tempDirs
)) {
2053 // fix test errors where this is not set
2056 foreach ($this->tempDirs
as $tempDir) {
2057 if (is_dir($tempDir)) {
2058 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2064 * Temporarily replace the singleton extension with a different one.
2066 * @param \CRM_Extension_System $system
2068 public function setExtensionSystem(CRM_Extension_System
$system) {
2069 if ($this->origExtensionSystem
== NULL) {
2070 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2072 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2075 public function unsetExtensionSystem() {
2076 if ($this->origExtensionSystem
!== NULL) {
2077 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2078 $this->origExtensionSystem
= NULL;
2083 * Temporarily alter the settings-metadata to add a mock setting.
2085 * WARNING: The setting metadata will disappear on the next cache-clear.
2091 public function setMockSettingsMetaData($extras) {
2092 CRM_Utils_Hook
::singleton()
2093 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2094 $metadata = array_merge($metadata, $extras);
2097 Civi
::service('settings_manager')->flush();
2099 $fields = $this->callAPISuccess('setting', 'getfields', []);
2100 foreach ($extras as $key => $spec) {
2101 $this->assertNotEmpty($spec['title']);
2102 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2107 * @param string $name
2109 public function financialAccountDelete($name) {
2110 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2111 $financialAccount->name
= $name;
2112 if ($financialAccount->find(TRUE)) {
2113 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2114 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2115 $entityFinancialType->delete();
2116 $financialAccount->delete();
2121 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2122 * (NB unclear if this is still required)
2124 public function _sethtmlGlobals() {
2125 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2127 'html_quickform_rule_required',
2128 'HTML/QuickForm/Rule/Required.php',
2131 'html_quickform_rule_range',
2132 'HTML/QuickForm/Rule/Range.php',
2135 'html_quickform_rule_range',
2136 'HTML/QuickForm/Rule/Range.php',
2139 'html_quickform_rule_range',
2140 'HTML/QuickForm/Rule/Range.php',
2143 'html_quickform_rule_email',
2144 'HTML/QuickForm/Rule/Email.php',
2147 'html_quickform_rule_regex',
2148 'HTML/QuickForm/Rule/Regex.php',
2151 'html_quickform_rule_regex',
2152 'HTML/QuickForm/Rule/Regex.php',
2155 'html_quickform_rule_regex',
2156 'HTML/QuickForm/Rule/Regex.php',
2159 'html_quickform_rule_regex',
2160 'HTML/QuickForm/Rule/Regex.php',
2162 'nopunctuation' => [
2163 'html_quickform_rule_regex',
2164 'HTML/QuickForm/Rule/Regex.php',
2167 'html_quickform_rule_regex',
2168 'HTML/QuickForm/Rule/Regex.php',
2171 'html_quickform_rule_callback',
2172 'HTML/QuickForm/Rule/Callback.php',
2175 'html_quickform_rule_compare',
2176 'HTML/QuickForm/Rule/Compare.php',
2179 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2180 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2182 'HTML/QuickForm/group.php',
2183 'HTML_QuickForm_group',
2186 'HTML/QuickForm/hidden.php',
2187 'HTML_QuickForm_hidden',
2190 'HTML/QuickForm/reset.php',
2191 'HTML_QuickForm_reset',
2194 'HTML/QuickForm/checkbox.php',
2195 'HTML_QuickForm_checkbox',
2198 'HTML/QuickForm/file.php',
2199 'HTML_QuickForm_file',
2202 'HTML/QuickForm/image.php',
2203 'HTML_QuickForm_image',
2206 'HTML/QuickForm/password.php',
2207 'HTML_QuickForm_password',
2210 'HTML/QuickForm/radio.php',
2211 'HTML_QuickForm_radio',
2214 'HTML/QuickForm/button.php',
2215 'HTML_QuickForm_button',
2218 'HTML/QuickForm/submit.php',
2219 'HTML_QuickForm_submit',
2222 'HTML/QuickForm/select.php',
2223 'HTML_QuickForm_select',
2226 'HTML/QuickForm/hiddenselect.php',
2227 'HTML_QuickForm_hiddenselect',
2230 'HTML/QuickForm/text.php',
2231 'HTML_QuickForm_text',
2234 'HTML/QuickForm/textarea.php',
2235 'HTML_QuickForm_textarea',
2238 'HTML/QuickForm/fckeditor.php',
2239 'HTML_QuickForm_FCKEditor',
2242 'HTML/QuickForm/tinymce.php',
2243 'HTML_QuickForm_TinyMCE',
2246 'HTML/QuickForm/dojoeditor.php',
2247 'HTML_QuickForm_dojoeditor',
2250 'HTML/QuickForm/link.php',
2251 'HTML_QuickForm_link',
2254 'HTML/QuickForm/advcheckbox.php',
2255 'HTML_QuickForm_advcheckbox',
2258 'HTML/QuickForm/date.php',
2259 'HTML_QuickForm_date',
2262 'HTML/QuickForm/static.php',
2263 'HTML_QuickForm_static',
2266 'HTML/QuickForm/header.php',
2267 'HTML_QuickForm_header',
2270 'HTML/QuickForm/html.php',
2271 'HTML_QuickForm_html',
2274 'HTML/QuickForm/hierselect.php',
2275 'HTML_QuickForm_hierselect',
2278 'HTML/QuickForm/autocomplete.php',
2279 'HTML_QuickForm_autocomplete',
2282 'HTML/QuickForm/xbutton.php',
2283 'HTML_QuickForm_xbutton',
2285 'advmultiselect' => [
2286 'HTML/QuickForm/advmultiselect.php',
2287 'HTML_QuickForm_advmultiselect',
2293 * Set up an acl allowing contact to see 2 specified groups
2294 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2296 * You need to have pre-created these groups & created the user e.g
2297 * $this->createLoggedInUser();
2298 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2299 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2301 * @param bool $isProfile
2303 public function setupACL($isProfile = FALSE) {
2305 $_REQUEST = $this->_params
;
2307 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2308 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2309 $ov = new CRM_Core_DAO_OptionValue();
2310 $ov->option_group_id
= $optionGroupID;
2312 if ($ov->find(TRUE)) {
2313 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2315 $optionValue = $this->callAPISuccess('option_value', 'create', [
2316 'option_group_id' => $optionGroupID,
2317 'label' => 'pick me',
2321 CRM_Core_DAO
::executeQuery("
2322 TRUNCATE civicrm_acl_cache
2325 CRM_Core_DAO
::executeQuery("
2326 TRUNCATE civicrm_acl_contact_cache
2329 CRM_Core_DAO
::executeQuery("
2330 INSERT INTO civicrm_acl_entity_role (
2331 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2332 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2336 CRM_Core_DAO
::executeQuery("
2337 INSERT INTO civicrm_acl (
2338 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2341 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2346 CRM_Core_DAO
::executeQuery("
2347 INSERT INTO civicrm_acl (
2348 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2351 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2355 CRM_Core_DAO
::executeQuery("
2356 INSERT INTO civicrm_acl (
2357 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2360 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2365 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2366 $this->callAPISuccess('group_contact', 'create', [
2367 'group_id' => $this->_permissionedGroup
,
2368 'contact_id' => $this->_loggedInUser
,
2373 CRM_ACL_BAO_Cache
::resetCache();
2374 CRM_ACL_API
::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2379 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2381 public function offsetDefaultPriceSet() {
2382 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2383 $firstID = $contributionPriceSet['id'];
2384 $this->callAPISuccess('price_set', 'create', [
2385 'id' => $contributionPriceSet['id'],
2389 unset($contributionPriceSet['id']);
2390 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2391 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2392 'price_set_id' => $firstID,
2393 'options' => ['limit' => 1],
2395 unset($priceField['id']);
2396 $priceField['price_set_id'] = $newPriceSet['id'];
2397 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2398 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2399 'price_set_id' => $firstID,
2401 'options' => ['limit' => 1],
2404 unset($priceFieldValue['id']);
2405 //create some padding to use up ids
2406 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2407 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2408 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2412 * Create an instance of the paypal processor.
2414 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2415 * this parent class & we don't have a structure for that yet
2416 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2417 * & the best protection against that is the functions this class affords
2419 * @param array $params
2421 * @return int $result['id'] payment processor id
2423 public function paymentProcessorCreate($params = []) {
2424 $params = array_merge([
2426 'domain_id' => CRM_Core_Config
::domainID(),
2427 'payment_processor_type_id' => 'PayPal',
2431 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2432 'password' => '1183377788',
2433 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2434 'url_site' => 'https://www.sandbox.paypal.com/',
2435 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2436 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2437 'class_name' => 'Payment_PayPalImpl',
2438 'billing_mode' => 3,
2439 'financial_type_id' => 1,
2440 'financial_account_id' => 12,
2441 // Credit card = 1 so can pass 'by accident'.
2442 'payment_instrument_id' => 'Debit Card',
2444 if (!is_numeric($params['payment_processor_type_id'])) {
2445 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2447 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2448 'name' => $params['payment_processor_type_id'],
2452 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2453 return $result['id'];
2457 * Set up initial recurring payment allowing subsequent IPN payments.
2459 * @param array $recurParams (Optional)
2460 * @param array $contributionParams (Optional)
2462 * @throws \CRM_Core_Exception
2464 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2465 $contributionParams = array_merge([
2466 'total_amount' => '200',
2467 'invoice_id' => $this->_invoiceID
,
2468 'financial_type_id' => 'Donation',
2469 'contribution_status_id' => 'Pending',
2470 'contact_id' => $this->_contactID
,
2471 'contribution_page_id' => $this->_contributionPageID
,
2472 'payment_processor_id' => $this->_paymentProcessorID
,
2474 'receive_date' => '2019-07-25 07:34:23',
2475 'skipCleanMoney' => TRUE,
2476 ], $contributionParams);
2477 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2478 'contact_id' => $this->_contactID
,
2481 'installments' => 5,
2482 'frequency_unit' => 'Month',
2483 'frequency_interval' => 1,
2484 'invoice_id' => $this->_invoiceID
,
2485 'contribution_status_id' => 2,
2486 'payment_processor_id' => $this->_paymentProcessorID
,
2487 // processor provided ID - use contact ID as proxy.
2488 'processor_id' => $this->_contactID
,
2489 'api.Order.create' => $contributionParams,
2490 ], $recurParams))['values'][0];
2491 $this->_contributionRecurID
= $contributionRecur['id'];
2492 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2493 $this->ids
['Contribution'][0] = $this->_contributionID
;
2497 * 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
2499 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2501 * @throws \CRM_Core_Exception
2503 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2504 $membershipParams = $recurParams = [];
2505 if (!empty($params['duration_unit'])) {
2506 $membershipParams['duration_unit'] = $params['duration_unit'];
2508 if (!empty($params['frequency_unit'])) {
2509 $recurParams['frequency_unit'] = $params['frequency_unit'];
2512 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2513 //create a contribution so our membership & contribution don't both have id = 1
2514 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2515 $this->contributionCreate([
2516 'contact_id' => $this->_contactID
,
2518 'financial_type_id' => 1,
2519 'invoice_id' => 'abcd',
2521 'receive_date' => '2019-07-25 07:34:23',
2525 $this->ids
['membership'] = $this->callAPISuccess('Membership', 'create', [
2526 'contact_id' => $this->_contactID
,
2527 'membership_type_id' => $this->ids
['membership_type'],
2528 'format.only_id' => TRUE,
2529 'source' => 'Payment',
2530 'skipLineItem' => TRUE,
2532 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2537 'entity_table' => 'civicrm_membership',
2538 'entity_id' => $this->ids
['membership'],
2539 'label' => 'General',
2541 'unit_price' => 200,
2542 'line_total' => 200,
2543 'financial_type_id' => 1,
2544 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2546 'label' => 'Membership Amount',
2547 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2549 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2551 'label' => 'General',
2552 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2559 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids
['membership'], 'contribution_recur_id' => $this->_contributionRecurID
]);
2567 public function CiviUnitTestCase_fatalErrorHandler($message) {
2568 throw new Exception("{$message['message']}: {$message['code']}");
2572 * Wrap the entire test case in a transaction.
2574 * Only subsequent DB statements will be wrapped in TX -- this cannot
2575 * retroactively wrap old DB statements. Therefore, it makes sense to
2576 * call this at the beginning of setUp().
2578 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2579 * this option does not work with, e.g., custom-data.
2581 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2582 * if TRUNCATE or ALTER is called while using a transaction.
2585 * Whether to use nesting or reference-counting.
2587 public function useTransaction($nest = TRUE) {
2589 $this->tx
= new CRM_Core_Transaction($nest);
2590 $this->tx
->rollback();
2595 * Assert the attachment exists.
2597 * @param bool $exists
2598 * @param array $apiResult
2600 protected function assertAttachmentExistence($exists, $apiResult) {
2601 $fileId = $apiResult['id'];
2602 $this->assertTrue(is_numeric($fileId));
2603 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2604 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2605 1 => [$fileId, 'Int'],
2607 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2608 1 => [$fileId, 'Int'],
2613 * Assert 2 sql strings are the same, ignoring double spaces.
2615 * @param string $expectedSQL
2616 * @param string $actualSQL
2617 * @param string $message
2619 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2620 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2621 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2622 $this->assertEquals($expected, $actual, $message);
2626 * Create a price set for an event.
2628 * @param int $feeTotal
2629 * @param int $minAmt
2630 * @param string $type
2634 * @throws \CRM_Core_Exception
2636 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text') {
2637 // creating price set, price field
2638 $paramsSet['title'] = 'Price Set';
2639 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2640 $paramsSet['is_active'] = FALSE;
2641 $paramsSet['extends'] = 1;
2642 $paramsSet['min_amount'] = $minAmt;
2644 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2645 $this->_ids
['price_set'] = $priceSet->id
;
2648 'label' => 'Price Field',
2649 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2650 'html_type' => $type,
2651 'price' => $feeTotal,
2652 'option_label' => ['1' => 'Price Field'],
2653 'option_value' => ['1' => $feeTotal],
2654 'option_name' => ['1' => $feeTotal],
2655 'option_weight' => ['1' => 1],
2656 'option_amount' => ['1' => 1],
2657 'is_display_amounts' => 1,
2659 'options_per_line' => 1,
2660 'is_active' => ['1' => 1],
2661 'price_set_id' => $this->_ids
['price_set'],
2662 'is_enter_qty' => 1,
2663 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2665 if ($type === 'Radio') {
2666 $paramsField['is_enter_qty'] = 0;
2667 $paramsField['option_value'][2] = $paramsField['option_weight'][2] = $paramsField['option_amount'][2] = 100;
2668 $paramsField['option_label'][2] = $paramsField['option_name'][2] = 'hundy';
2670 $this->callAPISuccess('PriceField', 'create', $paramsField);
2671 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2672 $this->_ids
['price_field'] = array_keys($fields['values']);
2673 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2674 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2676 return $this->_ids
['price_set'];
2680 * Add a profile to a contribution page.
2682 * @param string $name
2683 * @param int $contributionPageID
2684 * @param string $module
2686 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2688 'uf_group_id' => $name,
2689 'module' => $module,
2690 'entity_table' => 'civicrm_contribution_page',
2691 'entity_id' => $contributionPageID,
2694 if ($module !== 'CiviContribute') {
2695 $params['module_data'] = [$module => []];
2697 $this->callAPISuccess('UFJoin', 'create', $params);
2701 * Add participant with contribution
2705 * @throws \CRM_Core_Exception
2707 protected function createPartiallyPaidParticipantOrder() {
2708 $orderParams = $this->getParticipantOrderParams();
2709 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2710 return $this->callAPISuccess('Order', 'create', $orderParams);
2716 * @param string $component
2717 * @param int $componentId
2718 * @param array $priceFieldOptions
2722 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2723 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2724 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2725 $paramsSet['is_active'] = TRUE;
2726 $paramsSet['financial_type_id'] = 'Event Fee';
2727 $paramsSet['extends'] = 1;
2728 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2729 $priceSetId = $priceSet['id'];
2730 //Checking for priceset added in the table.
2731 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2732 'id', $paramsSet['title'], 'Check DB for created priceset'
2734 $paramsField = array_merge([
2735 'label' => 'Price Field',
2736 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2737 'html_type' => 'CheckBox',
2738 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2739 'option_value' => ['1' => 100, '2' => 200],
2740 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2741 'option_weight' => ['1' => 1, '2' => 2],
2742 'option_amount' => ['1' => 100, '2' => 200],
2743 'is_display_amounts' => 1,
2745 'options_per_line' => 1,
2746 'is_active' => ['1' => 1, '2' => 1],
2747 'price_set_id' => $priceSet['id'],
2748 'is_enter_qty' => 1,
2749 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2750 ], $priceFieldOptions);
2752 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2754 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSetId);
2756 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2760 * Replace the template with a test-oriented template designed to show all the variables.
2762 * @param string $templateName
2763 * @param string $type
2765 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2766 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2767 CRM_Core_DAO
::executeQuery(
2768 "UPDATE civicrm_option_group og
2769 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2770 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2771 SET m.msg_{$type} = %1
2772 WHERE og.name LIKE 'msg_tpl_workflow_%'
2773 AND ov.name = '{$templateName}'
2774 AND m.is_default = 1", [1 => [$testTemplate, 'String']]
2779 * Reinstate the default template.
2781 * @param string $templateName
2782 * @param string $type
2784 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2785 CRM_Core_DAO
::executeQuery(
2786 "UPDATE civicrm_option_group og
2787 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2788 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2789 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2790 SET m.msg_{$type} = m2.msg_{$type}
2791 WHERE og.name = 'msg_tpl_workflow_contribution'
2792 AND ov.name = '{$templateName}'
2793 AND m.is_default = 1"
2798 * Flush statics relating to financial type.
2800 protected function flushFinancialTypeStatics() {
2801 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2802 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2804 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2805 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2807 CRM_Contribute_PseudoConstant
::flush('financialType');
2808 CRM_Contribute_PseudoConstant
::flush('membershipType');
2809 // Pseudoconstants may be saved to the cache table.
2810 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2811 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2812 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2816 * Set the permissions to the supplied array.
2818 * @param array $permissions
2820 protected function setPermissions($permissions) {
2821 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2822 $this->flushFinancialTypeStatics();
2826 * @param array $params
2829 public function _checkFinancialRecords($params, $context) {
2831 'entity_id' => $params['id'],
2832 'entity_table' => 'civicrm_contribution',
2834 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2835 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2836 if ($context == 'pending') {
2837 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2838 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2841 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2843 'id' => $trxn['financial_trxn_id'],
2845 if ($context != 'online' && $context != 'payLater') {
2847 'to_financial_account_id' => 6,
2848 'total_amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2852 if ($context == 'feeAmount') {
2853 $compareParams['fee_amount'] = 50;
2855 elseif ($context == 'online') {
2857 'to_financial_account_id' => 12,
2858 'total_amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2860 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2863 elseif ($context == 'payLater') {
2865 'to_financial_account_id' => 7,
2866 'total_amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2870 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2872 'financial_trxn_id' => $trxn['financial_trxn_id'],
2873 'entity_table' => 'civicrm_financial_item',
2875 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2877 'id' => $entityTrxn['entity_id'],
2880 'amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2882 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2884 if ($context == 'payLater') {
2886 'amount' => CRM_Utils_Array
::value('total_amount', $params, 100),
2888 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2891 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2892 if ($context == 'feeAmount') {
2894 'entity_id' => $params['id'],
2895 'entity_table' => 'civicrm_contribution',
2897 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2899 'id' => $maxTrxn['financial_trxn_id'],
2902 'to_financial_account_id' => 5,
2903 'from_financial_account_id' => 6,
2904 'total_amount' => 50,
2907 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2908 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2910 'entity_id' => $trxnId['financialTrxnId'],
2911 'entity_table' => 'civicrm_financial_trxn',
2916 'financial_account_id' => 5,
2918 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2920 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2921 // line should be copied into all the functions that call this function & evaluated there
2922 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2923 // when calling completeTransaction or repeatTransaction.
2924 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2928 * Return financial type id on basis of name
2930 * @param string $name Financial type m/c name
2934 public function getFinancialTypeId($name) {
2935 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2939 * Cleanup function for contents of $this->ids.
2941 * This is a best effort cleanup to use in tear downs etc.
2943 * It will not fail if the data has already been removed (some tests may do
2944 * their own cleanup).
2946 protected function cleanUpSetUpIDs() {
2947 foreach ($this->setupIDs
as $entity => $id) {
2949 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
2951 catch (CiviCRM_API3_Exception
$e) {
2952 // This is a best-effort cleanup function, ignore.
2958 * Create Financial Type.
2960 * @param array $params
2964 protected function createFinancialType($params = []) {
2965 $params = array_merge($params,
2967 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
2971 return $this->callAPISuccess('FinancialType', 'create', $params);
2975 * Create Payment Instrument.
2977 * @param array $params
2978 * @param string $financialAccountName
2982 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
2983 $params = array_merge([
2984 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
2985 'option_group_id' => 'payment_instrument',
2988 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
2990 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
2992 $financialAccountParams = [
2993 'entity_table' => 'civicrm_option_value',
2994 'entity_id' => $newPaymentInstrument,
2995 'account_relationship' => $relationTypeID,
2996 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
2998 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3000 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3004 * Enable Tax and Invoicing
3006 * @param array $params
3008 * @return \Civi\Core\SettingsBag
3010 protected function enableTaxAndInvoicing($params = []) {
3011 // Enable component contribute setting
3012 $contributeSetting = array_merge($params,
3015 'invoice_prefix' => 'INV_',
3016 'credit_notes_prefix' => 'CN_',
3018 'due_date_period' => 'days',
3020 'is_email_pdf' => 1,
3021 'tax_term' => 'Sales Tax',
3022 'tax_display_settings' => 'Inclusive',
3025 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3029 * Enable Tax and Invoicing
3031 protected function disableTaxAndInvoicing($params = []) {
3032 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3033 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3035 // Enable component contribute setting
3036 $contributeSetting = array_merge($params,
3041 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3045 * Add Sales Tax relation for financial type with financial account.
3047 * @param int $financialTypeId
3051 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
3053 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3054 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3055 'is_deductible' => 1,
3060 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3062 'entity_table' => 'civicrm_financial_type',
3063 'entity_id' => $financialTypeId,
3064 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3067 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3068 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3070 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3071 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3072 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3073 $dao->copyValues($entityParams);
3075 if ($dao->fetch()) {
3076 $entityParams['id'] = $dao->id
;
3078 $entityParams['financial_account_id'] = $account->id
;
3080 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3084 * Create price set with contribution test for test setup.
3086 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3087 * on parent class at some point (fn is not in 4.4).
3090 * @param array $params
3092 public function createPriceSetWithPage($entity = NULL, $params = []) {
3093 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3094 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3095 'title' => "Test Contribution Page",
3096 'financial_type_id' => 1,
3097 'currency' => 'NZD',
3098 'goal_amount' => 50,
3099 'is_pay_later' => 1,
3100 'is_monetary' => TRUE,
3101 'is_email_receipt' => FALSE,
3103 $priceSet = $this->callAPISuccess('price_set', 'create', [
3104 'is_quick_config' => 0,
3105 'extends' => 'CiviMember',
3106 'financial_type_id' => 1,
3107 'title' => 'my Page',
3109 $priceSetID = $priceSet['id'];
3111 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3112 $priceField = $this->callAPISuccess('price_field', 'create', [
3113 'price_set_id' => $priceSetID,
3114 'label' => 'Goat Breed',
3115 'html_type' => 'Radio',
3117 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3118 'price_set_id' => $priceSetID,
3119 'price_field_id' => $priceField['id'],
3120 'label' => 'Long Haired Goat',
3122 'financial_type_id' => 'Donation',
3123 'membership_type_id' => $membershipTypeID,
3124 'membership_num_terms' => 1,
3126 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3127 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3128 'price_set_id' => $priceSetID,
3129 'price_field_id' => $priceField['id'],
3130 'label' => 'Shoe-eating Goat',
3132 'financial_type_id' => 'Donation',
3133 'membership_type_id' => $membershipTypeID,
3134 'membership_num_terms' => 2,
3136 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3138 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3139 'price_set_id' => $priceSetID,
3140 'price_field_id' => $priceField['id'],
3141 'label' => 'Shoe-eating Goat',
3143 'financial_type_id' => 'Donation',
3145 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3147 $this->_ids
['price_set'] = $priceSetID;
3148 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3149 $this->_ids
['price_field'] = [$priceField['id']];
3151 $this->_ids
['membership_type'] = $membershipTypeID;
3155 * Only specified contact returned.
3157 * @implements CRM_Utils_Hook::aclWhereClause
3161 * @param $whereTables
3165 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3166 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3170 * @implements CRM_Utils_Hook::selectWhereClause
3172 * @param string $entity
3173 * @param array $clauses
3175 public function selectWhereClauseHook($entity, &$clauses) {
3176 if ($entity == 'Event') {
3177 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3182 * An implementation of hook_civicrm_post used with all our test cases.
3185 * @param string $objectName
3186 * @param int $objectId
3189 public function onPost($op, $objectName, $objectId, &$objectRef) {
3190 if ($op == 'create' && $objectName == 'Individual') {
3191 CRM_Core_DAO
::executeQuery(
3192 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3194 1 => [$objectId, 'Integer'],
3199 if ($op == 'edit' && $objectName == 'Participant') {
3201 1 => [$objectId, 'Integer'],
3203 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3204 CRM_Core_DAO
::executeQuery($query, $params);
3209 * Instantiate form object.
3211 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3213 * @param string $class
3214 * Name of form class.
3216 * @param array $formValues
3218 * @param string $pageName
3220 * @return \CRM_Core_Form
3221 * @throws \CRM_Core_Exception
3223 public function getFormObject($class, $formValues = [], $pageName = '') {
3224 $form = new $class();
3225 $_SERVER['REQUEST_METHOD'] = 'GET';
3226 $form->controller
= new CRM_Core_Controller();
3227 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3228 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3233 * Get possible thousand separators.
3237 public function getThousandSeparators() {
3238 return [['.'], [',']];
3242 * Get the boolean options as a provider.
3246 public function getBooleanDataProvider() {
3247 return [[TRUE], [FALSE]];
3251 * Set the separators for thousands and decimal points.
3253 * @param string $thousandSeparator
3255 protected function setCurrencySeparators($thousandSeparator) {
3256 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3258 ->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3262 * Format money as it would be input.
3264 * @param string $amount
3268 protected function formatMoneyInput($amount) {
3269 return CRM_Utils_Money
::format($amount, NULL, '%a');
3273 * Get the contribution object.
3275 * @param int $contributionID
3277 * @return \CRM_Contribute_BAO_Contribution
3279 protected function getContributionObject($contributionID) {
3280 $contributionObj = new CRM_Contribute_BAO_Contribution();
3281 $contributionObj->id
= $contributionID;
3282 $contributionObj->find(TRUE);
3283 return $contributionObj;
3287 * Enable multilingual.
3289 public function enableMultilingual() {
3290 $this->callAPISuccess('Setting', 'create', [
3291 'lcMessages' => 'en_US',
3292 'languageLimit' => [
3297 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3300 $dbLocale = '_en_US';
3304 * Setup or clean up SMS tests
3306 * @param bool $teardown
3308 * @throws \CiviCRM_API3_Exception
3310 public function setupForSmsTests($teardown = FALSE) {
3311 require_once 'CiviTest/CiviTestSMSProvider.php';
3313 // Option value params for CiviTestSMSProvider
3314 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3316 'option_group_id' => $groupID,
3317 'label' => 'unittestSMS',
3318 'value' => 'unit.test.sms',
3319 'name' => 'CiviTestSMSProvider',
3326 // Test completed, delete provider
3327 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3328 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3332 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3333 // in CRM/Sms/Provider.php even though it is not an extension.
3334 return civicrm_api3('option_value', 'create', $params);
3338 * Start capturing browser output.
3340 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3342 protected function startCapturingOutput() {
3344 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3348 * Stop capturing browser output and return as a csv.
3350 * @param bool $isFirstRowHeaders
3352 * @return \League\Csv\Reader
3354 * @throws \League\Csv\Exception
3356 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3357 $output = ob_get_flush();
3358 $stream = fopen('php://memory', 'r+');
3359 fwrite($stream, $output);
3361 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3362 $csv = Reader
::createFromString($output);
3363 if ($isFirstRowHeaders) {
3364 $csv->setHeaderOffset(0);
3371 * Rename various labels to not match the names.
3373 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3374 * code that expects it to.
3376 protected function renameLabels() {
3377 $replacements = ['Pending', 'Refunded'];
3378 foreach ($replacements as $name) {
3379 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3384 * Undo any label renaming.
3386 protected function resetLabels() {
3387 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3391 * Get parameters to set up a multi-line participant order.
3394 * @throws \CRM_Core_Exception
3396 protected function getParticipantOrderParams(): array {
3397 $this->_contactId
= $this->individualCreate();
3398 $event = $this->eventCreate();
3399 $this->_eventId
= $event['id'];
3401 'id' => $this->_eventId
,
3402 'financial_type_id' => 4,
3405 $this->callAPISuccess('event', 'create', $eventParams);
3406 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3407 $participantParams = [
3408 'financial_type_id' => 4,
3409 'event_id' => $this->_eventId
,
3412 'fee_currency' => 'USD',
3413 'contact_id' => $this->_contactId
,
3415 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3417 'total_amount' => 300,
3418 'currency' => 'USD',
3419 'contact_id' => $this->_contactId
,
3420 'financial_type_id' => 4,
3421 'contribution_status_id' => 'Pending',
3422 'contribution_mode' => 'participant',
3423 'participant_id' => $participant['id'],
3425 foreach ($priceFields['values'] as $key => $priceField) {
3426 $orderParams['line_items'][] = [
3429 'price_field_id' => $priceField['price_field_id'],
3430 'price_field_value_id' => $priceField['id'],
3431 'label' => $priceField['label'],
3432 'field_title' => $priceField['label'],
3434 'unit_price' => $priceField['amount'],
3435 'line_total' => $priceField['amount'],
3436 'financial_type_id' => $priceField['financial_type_id'],
3437 'entity_table' => 'civicrm_participant',
3440 'params' => $participant,
3443 return $orderParams;
3449 * @throws \CRM_Core_Exception
3451 protected function validatePayments($payments) {
3452 foreach ($payments as $payment) {
3453 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3454 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3455 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3456 // This is a pretty rough way at guessing which payment is the overpayment - but
3457 // for the test suite it should be enough.
3460 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3461 'financial_trxn_id' => $payment['id'],
3462 'entity_table' => 'civicrm_financial_item',
3463 'return' => ['amount'],
3466 foreach ($items as $item) {
3467 $itemTotal +
= $item['amount'];
3469 $this->assertEquals($payment['total_amount'], $itemTotal);
3474 * Validate all created payments.
3476 * @throws \CRM_Core_Exception
3478 protected function validateAllPayments() {
3479 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3480 $this->validatePayments($payments);
3484 * Validate all created contributions.
3486 * @throws \CRM_Core_Exception
3488 protected function validateAllContributions() {
3489 $contributions = $this->callAPISuccess('Contribution', 'get', [])['values'];
3490 foreach ($contributions as $contribution) {
3491 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3493 foreach ($lineItems as $lineItem) {
3494 $total +
= $lineItem['line_total'];
3496 $this->assertEquals($total, $contribution['total_amount']);