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\Api4\OptionGroup
;
30 use Civi\Payment\System
;
31 use Civi\Api4\OptionValue
;
32 use League\Csv\Reader
;
35 * Include class definitions
37 require_once 'api/api.php';
38 define('API_LATEST_VERSION', 3);
41 * Base class for CiviCRM unit tests
43 * This class supports two (mutually-exclusive) techniques for cleaning up test data. Subclasses
44 * may opt for one or neither:
46 * 1. quickCleanup() is a helper which truncates a series of tables. Call quickCleanup()
47 * as part of setUp() and/or tearDown(). quickCleanup() is thorough - but it can
48 * be cumbersome to use (b/c you must identify the tables to cleanup) and slow to execute.
49 * 2. useTransaction() executes the test inside a transaction. It's easier to use
50 * (because you don't need to identify specific tables), but it doesn't work for tests
51 * which manipulate schema or truncate data -- and could behave inconsistently
52 * for tests which specifically examine DB transactions.
54 * Common functions for unit tests
58 class CiviUnitTestCase
extends PHPUnit\Framework\TestCase
{
60 use \Civi\Test\Api3DocTrait
;
61 use \Civi\Test\GenericAssertionsTrait
;
62 use \Civi\Test\DbTestTrait
;
63 use \Civi\Test\ContactTestTrait
;
64 use \Civi\Test\MailingTestTrait
;
67 * Database has been initialized.
71 private static $dbInit = FALSE;
74 * Database connection.
76 * @var PHPUnit_Extensions_Database_DB_IDatabaseConnection
85 static protected $_dbName;
88 * Track tables we have modified during a test.
92 protected $_tablesToTruncate = [];
96 * Array of temporary directory names
102 * populateOnce allows to skip db resets in setUp
104 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
105 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
108 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
110 * @see http://forum.civicrm.org/index.php/topic,18065.0.html
112 public static $populateOnce = FALSE;
115 * DBResetRequired allows skipping DB reset
116 * in specific test case. If you still need
117 * to reset single test (method) of such case, call
118 * $this->cleanDB() in the first line of this
122 public $DBResetRequired = TRUE;
125 * @var CRM_Core_Transaction|null
130 * Array of IDs created to support the test.
133 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
140 * Should financials be checked after the test but before tear down.
142 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
143 * are some test data issues and some real bugs currently blockinng.
147 protected $isValidateFinancialsOnPostAssert = FALSE;
150 * Should location types be checked to ensure primary addresses are correctly assigned after each test.
154 protected $isLocationTypesOnPostAssert = TRUE;
157 * Class used for hooks during tests.
159 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
161 * $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
163 * @var \CRM_Utils_Hook_UnitTests
165 public $hookClass = NULL;
169 * Common values to be re-used multiple times within a class - usually to create the relevant entity
171 protected $_params = [];
174 * @var CRM_Extension_System
176 protected $origExtensionSystem;
179 * Array of IDs created during test setup routine.
181 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
185 public $setupIDs = [];
190 * Because we are overriding the parent class constructor, we
191 * need to show the same arguments as exist in the constructor of
192 * PHPUnit_Framework_TestCase, since
193 * PHPUnit_Framework_TestSuite::createTest() creates a
194 * ReflectionClass of the Test class and checks the constructor
195 * of that class to decide how to set up the test.
197 * @param string $name
199 * @param string $dataName
201 public function __construct($name = NULL, array $data = [], $dataName = '') {
202 parent
::__construct($name, $data, $dataName);
204 // we need full error reporting
205 error_reporting(E_ALL
& ~E_NOTICE
);
207 self
::$_dbName = self
::getDBName();
209 // also load the class loader
210 require_once 'CRM/Core/ClassLoader.php';
211 CRM_Core_ClassLoader
::singleton()->register();
212 if (function_exists('_civix_phpunit_setUp')) {
213 // FIXME: loosen coupling
214 _civix_phpunit_setUp();
219 * Override to run the test and assert its state.
223 * @throws \PHPUnit_Framework_IncompleteTest
224 * @throws \PHPUnit_Framework_SkippedTest
226 protected function runTest() {
228 return parent
::runTest();
230 catch (PEAR_Exception
$e) {
231 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
232 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
239 public function requireDBReset() {
240 return $this->DBResetRequired
;
246 public static function getDBName() {
247 static $dbName = NULL;
248 if ($dbName === NULL) {
249 require_once "DB.php";
250 $dsn = CRM_Utils_SQL
::autoSwitchDSN(CIVICRM_DSN
);
251 $dsninfo = DB
::parseDSN($dsn);
252 $dbName = $dsninfo['database'];
258 * Create database connection for this instance.
260 * Initialize the test database if it hasn't been initialized
263 protected function getConnection() {
264 if (!self
::$dbInit) {
265 $dbName = self
::getDBName();
267 // install test database
268 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
270 static::_populateDB(FALSE, $this);
272 self
::$dbInit = TRUE;
278 * Required implementation of abstract method.
280 protected function getDataSet() {
284 * @param bool $perClass
285 * @param null $object
288 * TRUE if the populate logic runs; FALSE if it is skipped
290 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
291 if (CIVICRM_UF
!== 'UnitTests') {
292 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
295 if ($perClass ||
$object == NULL) {
299 $dbreset = $object->requireDBReset();
302 if (self
::$populateOnce ||
!$dbreset) {
305 self
::$populateOnce = NULL;
307 Civi\Test
::data()->populate();
312 public static function setUpBeforeClass() {
313 static::_populateDB(TRUE);
315 // also set this global hack
316 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
320 * Common setup functions for all unit tests.
322 protected function setUp(): void
{
323 $session = CRM_Core_Session
::singleton();
324 $session->set('userID', NULL);
326 $this->_apiversion
= 3;
328 // Use a temporary file for STDIN
329 $GLOBALS['stdin'] = tmpfile();
330 if ($GLOBALS['stdin'] === FALSE) {
331 echo "Couldn't open temporary file\n";
335 // Get and save a connection to the database
336 $this->_dbconn
= $this->getConnection();
338 // reload database before each test
339 // $this->_populateDB();
341 // "initialize" CiviCRM to avoid problems when running single tests
342 // FIXME: look at it closer in second stage
344 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
345 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
347 // disable any left-over test extensions
348 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
350 // reset all the caches
351 CRM_Utils_System
::flushCache();
353 // initialize the object once db is loaded
354 \Civi
::$statics = [];
356 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
358 // when running unit tests, use mockup user framework
359 $this->hookClass
= CRM_Utils_Hook
::singleton();
361 // Make sure the DB connection is setup properly
362 $config->userSystem
->setMySQLTimeZone();
363 $env = new CRM_Utils_Check_Component_Env();
364 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
366 // clear permissions stub to not check permissions
367 $config->userPermissionClass
->permissions
= NULL;
369 //flush component settings
370 CRM_Core_Component
::getEnabledComponents(TRUE);
372 $_REQUEST = $_GET = $_POST = [];
373 error_reporting(E_ALL
);
375 $this->renameLabels();
376 $this->_sethtmlGlobals();
377 $this->ensureMySQLMode(['IGNORE_SPACE', 'ERROR_FOR_DIVISION_BY_ZERO', 'STRICT_TRANS_TABLES']);
381 * Read everything from the datasets directory and insert into the db.
383 public function loadAllFixtures(): void
{
384 $fixturesDir = __DIR__
. '/../../fixtures';
386 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
388 $jsonFiles = glob($fixturesDir . '/*.json');
389 foreach ($jsonFiles as $jsonFixture) {
390 $json = json_decode(file_get_contents($jsonFixture));
391 foreach ($json as $tableName => $vars) {
392 if ($tableName === 'civicrm_contact') {
393 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');
396 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
398 foreach ($vars as $entity) {
399 $keys = $values = [];
400 foreach ($entity as $key => $value) {
402 $values[] = is_numeric($value) ?
$value : "'{$value}'";
404 CRM_Core_DAO
::executeQuery("
405 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
412 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
416 * Load the data that used to be handled by the discontinued dbunit class.
418 * This could do with further tidy up - the initial priority is simply to get rid of
419 * the dbunity package which is no longer supported.
421 * @param string $fileName
423 protected function loadXMLDataSet($fileName) {
424 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
425 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
426 foreach ($xml as $tableName => $element) {
427 if (!empty($element)) {
428 foreach ($element as $row) {
429 $keys = $values = [];
430 if (isset($row['@attributes'])) {
431 foreach ($row['@attributes'] as $key => $value) {
433 $values[] = is_numeric($value) ?
$value : "'{$value}'";
436 elseif (!empty($row)) {
437 // cos we copied it & it is inconsistent....
438 foreach ($row as $key => $value) {
440 $values[] = is_numeric($value) ?
$value : "'{$value}'";
444 if (!empty($values)) {
445 CRM_Core_DAO
::executeQuery("
446 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
452 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
456 * Create default domain contacts for the two domains added during test class.
457 * database population.
459 * @throws \CiviCRM_API3_Exception
460 * @throws \API_Exception
462 public function createDomainContacts(): void
{
463 $this->organizationCreate(['api.Email.create' => ['email' => 'fixme.domainemail@example.org']]);
464 $this->organizationCreate([
465 'organization_name' => 'Second Domain',
466 'api.Email.create' => ['email' => 'domainemail2@example.org'],
467 'api.Address.create' => [
468 'street_address' => '15 Main St',
469 'location_type_id' => 1,
470 'city' => 'Collinsville',
471 'country_id' => 1228,
472 'state_province_id' => 1003,
473 'postal_code' => 6022,
476 OptionValue
::replace(FALSE)->addWhere(
477 'option_group_id:name', '=', 'from_email_address'
480 'name' => '"FIXME" <info@EXAMPLE.ORG>',
481 'label' => '"FIXME" <info@EXAMPLE.ORG>',
482 ])->setRecords([['domain_id' => 1], ['domain_id' => 2]])->execute();
486 * Common teardown functions for all unit tests.
488 * @throws \CiviCRM_API3_Exception
489 * @throws \CRM_Core_Exception
491 protected function tearDown(): void
{
492 $this->_apiversion
= 3;
493 $this->resetLabels();
495 error_reporting(E_ALL
& ~E_NOTICE
);
496 CRM_Utils_Hook
::singleton()->reset();
497 if ($this->hookClass
) {
498 $this->hookClass
->reset();
500 CRM_Core_Session
::singleton()->reset(1);
503 $this->tx
->rollback()->commit();
506 CRM_Core_Transaction
::forceRollbackIfEnabled();
507 \Civi\Core\Transaction\Manager
::singleton(TRUE);
510 CRM_Core_Transaction
::forceRollbackIfEnabled();
511 \Civi\Core\Transaction\Manager
::singleton(TRUE);
513 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match', 'civicrm_email', 'civicrm_address'];
514 $this->quickCleanup($tablesToTruncate);
515 $this->createDomainContacts();
518 $this->cleanTempDirs();
519 $this->unsetExtensionSystem();
520 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
521 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
525 * CHeck that all tests that have created payments have created them with the right financial entities.
527 * @throws \API_Exception
528 * @throws \CRM_Core_Exception
530 protected function assertPostConditions() {
531 // Reset to version 3 as not all (e.g payments) work on v4
532 $this->_apiversion
= 3;
533 if ($this->isLocationTypesOnPostAssert
) {
534 $this->assertLocationValidity();
536 $this->assertCount(1, OptionGroup
::get(FALSE)
537 ->addWhere('name', '=', 'from_email_address')
539 if (!$this->isValidateFinancialsOnPostAssert
) {
542 $this->validateAllPayments();
543 $this->validateAllContributions();
547 * Create a batch of external API calls which can
548 * be executed concurrently.
551 * $calls = $this->createExternalAPI()
552 * ->addCall('Contact', 'get', ...)
553 * ->addCall('Contact', 'get', ...)
559 * @return \Civi\API\ExternalBatch
560 * @throws PHPUnit_Framework_SkippedTestError
562 public function createExternalAPI() {
563 global $civicrm_root;
565 'version' => $this->_apiversion
,
569 $calls = new \Civi\API\
ExternalBatch($defaultParams);
571 if (!$calls->isSupported()) {
572 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
579 * Create required data based on $this->entity & $this->params
580 * This is just a way to set up the test data for delete & get functions
581 * so the distinction between set
582 * up & tested functions is clearer
587 public function createTestEntity() {
588 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
592 * @param int $contactTypeId
596 public function contactTypeDelete($contactTypeId) {
597 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
599 throw new Exception('Could not delete contact type');
604 * @param array $params
608 public function membershipTypeCreate($params = []) {
609 CRM_Member_PseudoConstant
::flush('membershipType');
610 CRM_Core_Config
::clearDBCache();
611 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
612 $params = array_merge([
614 'duration_unit' => 'year',
615 'duration_interval' => 1,
616 'period_type' => 'rolling',
617 'member_of_contact_id' => $memberOfOrganization,
619 'financial_type_id' => 2,
622 'visibility' => 'Public',
625 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
627 CRM_Member_PseudoConstant
::flush('membershipType');
628 CRM_Utils_Cache
::singleton()->flush();
630 return (int) $result['id'];
636 * @param array $params
639 * @throws \CRM_Core_Exception
641 public function contactMembershipCreate($params) {
642 $params = array_merge([
643 'join_date' => '2007-01-21',
644 'start_date' => '2007-01-21',
645 'end_date' => '2007-12-21',
646 'source' => 'Payment',
647 'membership_type_id' => 'General',
649 if (!is_numeric($params['membership_type_id'])) {
650 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
651 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
652 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
656 $result = $this->callAPISuccess('Membership', 'create', $params);
657 return $result['id'];
661 * Delete Membership Type.
663 * @param array $params
665 public function membershipTypeDelete($params) {
666 $this->callAPISuccess('MembershipType', 'Delete', $params);
670 * @param int $membershipID
672 public function membershipDelete($membershipID) {
673 $deleteParams = ['id' => $membershipID];
674 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
678 * @param string $name
682 * @throws \CRM_Core_Exception
684 public function membershipStatusCreate($name = 'test member status') {
685 $params['name'] = $name;
686 $params['start_event'] = 'start_date';
687 $params['end_event'] = 'end_date';
688 $params['is_current_member'] = 1;
689 $params['is_active'] = 1;
691 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
692 CRM_Member_PseudoConstant
::flush('membershipStatus');
693 return (int) $result['id'];
697 * Delete the given membership status, deleting any memberships of the status first.
699 * @param int $membershipStatusID
701 * @throws \CRM_Core_Exception
703 public function membershipStatusDelete(int $membershipStatusID) {
704 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
705 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
708 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
709 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
710 $renewedMembershipEndDate = new DateTime($membershipEndDate);
711 switch ($durationUnit) {
713 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
717 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
718 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
719 $renewedMembershipEndDate->add(new DateInterval('P1D'));
720 $renewedMembershipEndDate->add(new DateInterval('P1M'));
721 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
724 return $renewedMembershipEndDate->format('Y-m-d');
728 * Create a relationship type.
730 * @param array $params
734 * @throws \CRM_Core_Exception
736 public function relationshipTypeCreate($params = []) {
737 $params = array_merge([
738 'name_a_b' => 'Relation 1 for relationship type create',
739 'name_b_a' => 'Relation 2 for relationship type create',
740 'contact_type_a' => 'Individual',
741 'contact_type_b' => 'Organization',
746 $result = $this->callAPISuccess('relationship_type', 'create', $params);
747 CRM_Core_PseudoConstant
::flush('relationshipType');
749 return $result['id'];
753 * Delete Relatinship Type.
755 * @param int $relationshipTypeID
757 public function relationshipTypeDelete($relationshipTypeID) {
758 $params['id'] = $relationshipTypeID;
759 $check = $this->callAPISuccess('relationship_type', 'get', $params);
760 if (!empty($check['count'])) {
761 $this->callAPISuccess('relationship_type', 'delete', $params);
766 * @param array $params
769 * @throws \CRM_Core_Exception
771 public function paymentProcessorTypeCreate($params = []) {
772 $params = array_merge([
773 'name' => 'API_Test_PP',
774 'title' => 'API Test Payment Processor',
775 'class_name' => 'CRM_Core_Payment_APITest',
776 'billing_mode' => 'form',
781 $result = $this->callAPISuccess('PaymentProcessorType', 'create', $params);
783 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
785 return $result['id'];
789 * Create test Authorize.net instance.
791 * @param array $params
794 * @throws \CRM_Core_Exception
796 public function paymentProcessorAuthorizeNetCreate($params = []) {
797 $params = array_merge([
798 'name' => 'Authorize',
799 'domain_id' => CRM_Core_Config
::domainID(),
800 'payment_processor_type_id' => 'AuthNet',
801 'title' => 'AuthNet',
806 'user_name' => '4y5BfuW7jm',
807 'password' => '4cAmW927n8uLf5J8',
808 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
809 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
810 'class_name' => 'Payment_AuthorizeNet',
814 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
815 return (int) $result['id'];
819 * Create Participant.
821 * @param array $params
822 * Array of contact id and event id values.
825 * $id of participant created
827 public function participantCreate($params = []) {
828 if (empty($params['contact_id'])) {
829 $params['contact_id'] = $this->individualCreate();
831 if (empty($params['event_id'])) {
832 $event = $this->eventCreate();
833 $params['event_id'] = $event['id'];
838 'register_date' => 20070219,
839 'source' => 'Wimbeldon',
840 'event_level' => 'Payment',
844 $params = array_merge($defaults, $params);
845 $result = $this->callAPISuccess('Participant', 'create', $params);
846 return $result['id'];
850 * Create Payment Processor.
853 * Id Payment Processor
855 public function processorCreate($params = []) {
859 'payment_processor_type_id' => 'Dummy',
860 'financial_account_id' => 12,
864 'url_site' => 'http://dummy.com',
865 'url_recur' => 'http://dummy.com',
868 'payment_instrument_id' => 'Debit Card',
870 $processorParams = array_merge($processorParams, $params);
871 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
872 return $processor['id'];
876 * Create Payment Processor.
878 * @param array $processorParams
880 * @return \CRM_Core_Payment_Dummy
881 * Instance of Dummy Payment Processor
883 * @throws \CiviCRM_API3_Exception
885 public function dummyProcessorCreate($processorParams = []) {
886 $paymentProcessorID = $this->processorCreate($processorParams);
887 // 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
888 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
889 $processorParams['is_test'] = FALSE;
890 $this->processorCreate($processorParams);
891 return System
::singleton()->getById($paymentProcessorID);
895 * Create contribution page.
897 * @param array $params
900 * Array of contribution page
902 public function contributionPageCreate($params = []) {
903 $this->_pageParams
= array_merge([
904 'title' => 'Test Contribution Page',
905 'financial_type_id' => 1,
907 'financial_account_id' => 1,
909 'is_allow_other_amount' => 1,
911 'max_amount' => 1000,
913 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
917 * Create a sample batch.
919 public function batchCreate() {
920 $params = $this->_params
;
921 $params['name'] = $params['title'] = 'Batch_433397';
922 $params['status_id'] = 1;
923 $result = $this->callAPISuccess('batch', 'create', $params);
924 return $result['id'];
930 * @param array $params
933 * result of created tag
935 public function tagCreate($params = []) {
937 'name' => 'New Tag3',
938 'description' => 'This is description for Our New Tag ',
941 $params = array_merge($defaults, $params);
942 $result = $this->callAPISuccess('Tag', 'create', $params);
943 return $result['values'][$result['id']];
950 * Id of the tag to be deleted.
954 public function tagDelete($tagId) {
955 require_once 'api/api.php';
959 $result = $this->callAPISuccess('Tag', 'delete', $params);
960 return $result['id'];
964 * Add entity(s) to the tag
966 * @param array $params
970 public function entityTagAdd($params) {
971 $result = $this->callAPISuccess('entity_tag', 'create', $params);
978 * @param array $params
982 * id of created pledge
984 * @throws \CRM_Core_Exception
986 public function pledgeCreate($params) {
987 $params = array_merge([
988 'pledge_create_date' => date('Ymd'),
989 'start_date' => date('Ymd'),
990 'scheduled_date' => date('Ymd'),
992 'pledge_status_id' => '2',
993 'financial_type_id' => '1',
994 'pledge_original_installment_amount' => 20,
995 'frequency_interval' => 5,
996 'frequency_unit' => 'year',
997 'frequency_day' => 15,
1002 $result = $this->callAPISuccess('Pledge', 'create', $params);
1003 return $result['id'];
1007 * Delete contribution.
1009 * @param int $pledgeId
1011 * @throws \CRM_Core_Exception
1013 public function pledgeDelete($pledgeId) {
1015 'pledge_id' => $pledgeId,
1017 $this->callAPISuccess('Pledge', 'delete', $params);
1021 * Create contribution.
1023 * @param array $params
1024 * Array of parameters.
1027 * id of created contribution
1028 * @throws \CRM_Core_Exception
1030 public function contributionCreate($params) {
1032 $params = array_merge([
1034 'receive_date' => date('Ymd'),
1035 'total_amount' => 100.00,
1036 'fee_amount' => 5.00,
1037 'financial_type_id' => 1,
1038 'payment_instrument_id' => 1,
1039 'non_deductible_amount' => 10.00,
1041 'contribution_status_id' => 1,
1044 $result = $this->callAPISuccess('contribution', 'create', $params);
1045 return $result['id'];
1049 * Delete contribution.
1051 * @param int $contributionId
1054 * @throws \CRM_Core_Exception
1056 public function contributionDelete($contributionId) {
1058 'contribution_id' => $contributionId,
1060 $result = $this->callAPISuccess('contribution', 'delete', $params);
1067 * @param array $params
1068 * Name-value pair for an event.
1071 * @throws \CRM_Core_Exception
1073 public function eventCreate($params = []) {
1074 // if no contact was passed, make up a dummy event creator
1075 if (!isset($params['contact_id'])) {
1076 $params['contact_id'] = $this->_contactCreate([
1077 'contact_type' => 'Individual',
1078 'first_name' => 'Event',
1079 'last_name' => 'Creator',
1083 // set defaults for missing params
1084 $params = array_merge([
1085 'title' => 'Annual CiviCRM meet',
1086 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1087 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1088 'event_type_id' => 1,
1090 'start_date' => 20081021,
1091 'end_date' => 20081023,
1092 'is_online_registration' => 1,
1093 'registration_start_date' => 20080601,
1094 'registration_end_date' => 20081015,
1095 'max_participants' => 100,
1096 'event_full_text' => 'Sorry! We are already full',
1099 'is_show_location' => 0,
1100 'is_email_confirm' => 1,
1103 return $this->callAPISuccess('Event', 'create', $params);
1107 * Create a paid event.
1109 * @param array $params
1111 * @param array $options
1113 * @param string $key
1114 * Index for storing event ID in ids array.
1118 * @throws \CRM_Core_Exception
1120 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1121 $params['is_monetary'] = TRUE;
1122 $event = $this->eventCreate($params);
1123 $this->ids
['Event'][$key] = (int) $event['id'];
1124 $this->ids
['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1125 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->ids
['PriceSet'][$key]);
1126 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->ids
['PriceSet'][$key], TRUE, FALSE);
1127 $priceSet = $priceSet[$this->ids
['PriceSet'][$key]] ??
NULL;
1128 $this->eventFeeBlock
= $priceSet['fields'] ??
NULL;
1140 public function eventDelete($id) {
1144 return $this->callAPISuccess('event', 'delete', $params);
1148 * Delete participant.
1150 * @param int $participantID
1154 public function participantDelete($participantID) {
1156 'id' => $participantID,
1158 $check = $this->callAPISuccess('Participant', 'get', $params);
1159 if ($check['count'] > 0) {
1160 return $this->callAPISuccess('Participant', 'delete', $params);
1165 * Create participant payment.
1167 * @param int $participantID
1168 * @param int $contributionID
1171 * $id of created payment
1173 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1174 //Create Participant Payment record With Values
1176 'participant_id' => $participantID,
1177 'contribution_id' => $contributionID,
1180 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1181 return $result['id'];
1185 * Delete participant payment.
1187 * @param int $paymentID
1189 public function participantPaymentDelete($paymentID) {
1193 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1199 * @param int $contactID
1202 * location id of created location
1204 public function locationAdd($contactID) {
1207 'location_type' => 'New Location Type',
1209 'name' => 'Saint Helier St',
1210 'county' => 'Marin',
1211 'country' => 'UNITED STATES',
1212 'state_province' => 'Michigan',
1213 'supplemental_address_1' => 'Hallmark Ct',
1214 'supplemental_address_2' => 'Jersey Village',
1215 'supplemental_address_3' => 'My Town',
1220 'contact_id' => $contactID,
1221 'address' => $address,
1222 'location_format' => '2.0',
1223 'location_type' => 'New Location Type',
1226 $result = $this->callAPISuccess('Location', 'create', $params);
1231 * Delete Locations of contact.
1233 * @param array $params
1236 public function locationDelete($params) {
1237 $this->callAPISuccess('Location', 'delete', $params);
1241 * Add a Location Type.
1243 * @param array $params
1245 * @return CRM_Core_DAO_LocationType
1246 * location id of created location
1248 public function locationTypeCreate($params = NULL) {
1249 if ($params === NULL) {
1251 'name' => 'New Location Type',
1252 'vcard_name' => 'New Location Type',
1253 'description' => 'Location Type for Delete',
1258 $locationType = new CRM_Core_DAO_LocationType();
1259 $locationType->copyValues($params);
1260 $locationType->save();
1261 // clear getfields cache
1262 CRM_Core_PseudoConstant
::flush();
1263 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1264 return $locationType->id
;
1268 * Delete a Location Type.
1270 * @param int $locationTypeId
1272 public function locationTypeDelete($locationTypeId) {
1273 $locationType = new CRM_Core_DAO_LocationType();
1274 $locationType->id
= $locationTypeId;
1275 $locationType->delete();
1281 * @param array $params
1283 * @return CRM_Core_DAO_Mapping
1284 * Mapping id of created mapping
1286 public function mappingCreate($params = NULL) {
1287 if ($params === NULL) {
1289 'name' => 'Mapping name',
1290 'description' => 'Mapping description',
1291 // 'Export Contact' mapping.
1292 'mapping_type_id' => 7,
1296 $mapping = new CRM_Core_DAO_Mapping();
1297 $mapping->copyValues($params);
1299 // clear getfields cache
1300 CRM_Core_PseudoConstant
::flush();
1301 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1308 * @param int $mappingId
1310 public function mappingDelete($mappingId) {
1311 $mapping = new CRM_Core_DAO_Mapping();
1312 $mapping->id
= $mappingId;
1317 * Prepare class for ACLs.
1319 protected function prepareForACLs() {
1320 $config = CRM_Core_Config
::singleton();
1321 $config->userPermissionClass
->permissions
= [];
1327 protected function cleanUpAfterACLs() {
1328 CRM_Utils_Hook
::singleton()->reset();
1329 $tablesToTruncate = [
1331 'civicrm_acl_cache',
1332 'civicrm_acl_entity_role',
1333 'civicrm_acl_contact_cache',
1335 $this->quickCleanup($tablesToTruncate);
1336 $config = CRM_Core_Config
::singleton();
1337 unset($config->userPermissionClass
->permissions
);
1341 * Create a smart group.
1343 * By default it will be a group of households.
1345 * @param array $smartGroupParams
1346 * @param array $groupParams
1347 * @param string $contactType
1351 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1352 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1353 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1355 $groupParams['saved_search_id'] = $savedSearch->id
;
1356 return $this->groupCreate($groupParams);
1362 * @param array $params
1364 public function uFFieldCreate($params = []) {
1365 $params = array_merge([
1367 'field_name' => 'first_name',
1370 'visibility' => 'Public Pages and Listings',
1371 'is_searchable' => '1',
1372 'label' => 'first_name',
1373 'field_type' => 'Individual',
1376 $this->callAPISuccess('uf_field', 'create', $params);
1380 * Add a UF Join Entry.
1382 * @param array $params
1385 * $id of created UF Join
1387 public function ufjoinCreate($params = NULL) {
1388 if ($params === NULL) {
1391 'module' => 'CiviEvent',
1392 'entity_table' => 'civicrm_event',
1398 $result = $this->callAPISuccess('uf_join', 'create', $params);
1403 * @param array $params
1404 * Optional parameters.
1405 * @param bool $reloadConfig
1406 * While enabling CiviCampaign component, we shouldn't always forcibly
1407 * reload config as this hinder hook call in test environment
1412 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1413 $this->enableCiviCampaign($reloadConfig);
1414 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1415 'name' => 'big_campaign',
1416 'title' => 'Campaign',
1418 return $campaign['id'];
1422 * Create Group for a contact.
1424 * @param int $contactId
1426 public function contactGroupCreate($contactId) {
1428 'contact_id.1' => $contactId,
1432 $this->callAPISuccess('GroupContact', 'Create', $params);
1436 * Delete Group for a contact.
1438 * @param int $contactId
1440 public function contactGroupDelete($contactId) {
1442 'contact_id.1' => $contactId,
1445 $this->civicrm_api('GroupContact', 'Delete', $params);
1451 * @param array $params
1455 * @throws \CRM_Core_Exception
1456 * @throws \CiviCRM_API3_Exception
1458 public function activityCreate($params = []) {
1459 $params = array_merge([
1460 'subject' => 'Discussion on warm beer',
1461 'activity_date_time' => date('Ymd'),
1463 'location' => 'Baker Street',
1464 'details' => 'Lets schedule a meeting',
1466 'activity_type_id' => 'Meeting',
1468 if (!isset($params['source_contact_id'])) {
1469 $params['source_contact_id'] = $this->individualCreate();
1471 if (!isset($params['target_contact_id'])) {
1472 $params['target_contact_id'] = $this->individualCreate([
1473 'first_name' => 'Julia',
1474 'last_name' => 'Anderson',
1476 'email' => 'julia_anderson@civicrm.org',
1477 'contact_type' => 'Individual',
1480 if (!isset($params['assignee_contact_id'])) {
1481 $params['assignee_contact_id'] = $params['target_contact_id'];
1484 $result = civicrm_api3('Activity', 'create', $params);
1486 $result['target_contact_id'] = $params['target_contact_id'];
1487 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1492 * Create an activity type.
1494 * @param array $params
1499 public function activityTypeCreate($params) {
1500 return $this->callAPISuccess('ActivityType', 'create', $params);
1504 * Delete activity type.
1506 * @param int $activityTypeId
1507 * Id of the activity type.
1511 public function activityTypeDelete($activityTypeId) {
1512 $params['activity_type_id'] = $activityTypeId;
1513 return $this->callAPISuccess('ActivityType', 'delete', $params);
1517 * Create custom group.
1519 * @param array $params
1523 public function customGroupCreate($params = []) {
1525 'title' => 'new custom group',
1526 'extends' => 'Contact',
1528 'style' => 'Inline',
1532 $params = array_merge($defaults, $params);
1534 return $this->callAPISuccess('custom_group', 'create', $params);
1538 * Existing function doesn't allow params to be over-ridden so need a new one
1539 * this one allows you to only pass in the params you want to change
1541 * @param array $params
1545 public function CustomGroupCreateByParams($params = []) {
1547 'title' => "API Custom Group",
1548 'extends' => 'Contact',
1550 'style' => 'Inline',
1553 $params = array_merge($defaults, $params);
1554 return $this->callAPISuccess('custom_group', 'create', $params);
1558 * Create custom group with multi fields.
1560 * @param array $params
1564 public function CustomGroupMultipleCreateByParams($params = []) {
1569 $params = array_merge($defaults, $params);
1570 return $this->CustomGroupCreateByParams($params);
1574 * Create custom group with multi fields.
1576 * @param array $params
1580 public function CustomGroupMultipleCreateWithFields($params = []) {
1581 // also need to pass on $params['custom_field'] if not set but not in place yet
1583 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1584 $ids['custom_group_id'] = $customGroup['id'];
1586 $customField = $this->customFieldCreate([
1587 'custom_group_id' => $ids['custom_group_id'],
1588 'label' => 'field_1' . $ids['custom_group_id'],
1592 $ids['custom_field_id'][] = $customField['id'];
1594 $customField = $this->customFieldCreate([
1595 'custom_group_id' => $ids['custom_group_id'],
1596 'default_value' => '',
1597 'label' => 'field_2' . $ids['custom_group_id'],
1600 $ids['custom_field_id'][] = $customField['id'];
1602 $customField = $this->customFieldCreate([
1603 'custom_group_id' => $ids['custom_group_id'],
1604 'default_value' => '',
1605 'label' => 'field_3' . $ids['custom_group_id'],
1608 $ids['custom_field_id'][] = $customField['id'];
1614 * Create a custom group with a single text custom field. See
1615 * participant:testCreateWithCustom for how to use this
1617 * @param string $function
1619 * @param string $filename
1623 * ids of created objects
1625 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1626 $params = ['title' => $function];
1627 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1628 $params['extends'] = $entity ?
$entity : 'Contact';
1629 $customGroup = $this->customGroupCreate($params);
1630 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1631 CRM_Core_PseudoConstant
::flush();
1633 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1637 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1638 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1640 * @param string $function
1642 * @param string $filename
1646 * ids of created objects
1648 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1649 $params = ['title' => $function];
1650 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1651 $params['extends'] = $entity ?
$entity : 'Contact';
1652 $customGroup = $this->customGroupCreate($params);
1653 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1654 CRM_Core_PseudoConstant
::flush();
1656 'defaultValue' => 'Default Value',
1657 'lowercasevalue' => 'Lowercase Value',
1658 1 => 'Integer Value',
1661 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1662 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1663 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1664 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1665 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1666 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1667 foreach ($options as $option_value => $option_label) {
1668 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1669 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1673 'custom_group_id' => $customGroup['id'],
1674 'custom_field_id' => $customField['id'],
1675 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1676 'custom_field_group_options' => $options,
1681 * Delete custom group.
1683 * @param int $customGroupID
1687 public function customGroupDelete($customGroupID) {
1688 $params['id'] = $customGroupID;
1689 return $this->callAPISuccess('custom_group', 'delete', $params);
1693 * Create custom field.
1695 * @param array $params
1696 * (custom_group_id) is required.
1700 public function customFieldCreate($params) {
1701 $params = array_merge([
1702 'label' => 'Custom Field',
1703 'data_type' => 'String',
1704 'html_type' => 'Text',
1705 'is_searchable' => 1,
1707 'default_value' => 'defaultValue',
1710 $result = $this->callAPISuccess('custom_field', 'create', $params);
1711 // these 2 functions are called with force to flush static caches
1712 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1713 CRM_Core_Component
::getEnabledComponents(1);
1718 * Delete custom field.
1720 * @param int $customFieldID
1724 public function customFieldDelete($customFieldID) {
1726 $params['id'] = $customFieldID;
1727 return $this->callAPISuccess('custom_field', 'delete', $params);
1737 public function noteCreate($cId) {
1739 'entity_table' => 'civicrm_contact',
1740 'entity_id' => $cId,
1741 'note' => 'hello I am testing Note',
1742 'contact_id' => $cId,
1743 'modified_date' => date('Ymd'),
1744 'subject' => 'Test Note',
1747 return $this->callAPISuccess('Note', 'create', $params);
1751 * Enable CiviCampaign Component.
1753 * @param bool $reloadConfig
1754 * Force relaod config or not
1756 public function enableCiviCampaign($reloadConfig = TRUE) {
1757 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1758 if ($reloadConfig) {
1759 // force reload of config object
1760 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1762 //flush cache by calling with reset
1763 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1767 * Create custom field with Option Values.
1769 * @param array $customGroup
1770 * @param string $name
1771 * Name of custom field.
1772 * @param array $extraParams
1773 * Additional parameters to pass through.
1777 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1779 'custom_group_id' => $customGroup['id'],
1780 'name' => 'test_custom_group',
1781 'label' => 'Country',
1782 'html_type' => 'Select',
1783 'data_type' => 'String',
1786 'is_searchable' => 0,
1792 'name' => 'option_group1',
1793 'label' => 'option_group_label1',
1797 'option_label' => ['Label1', 'Label2'],
1798 'option_value' => ['value1', 'value2'],
1799 'option_name' => [$name . '_1', $name . '_2'],
1800 'option_weight' => [1, 2],
1801 'option_status' => [1, 1],
1804 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1806 return $this->callAPISuccess('custom_field', 'create', $params);
1814 public function confirmEntitiesDeleted($entities) {
1815 foreach ($entities as $entity) {
1817 $result = $this->callAPISuccess($entity, 'Get', []);
1818 if ($result['error'] == 1 ||
$result['count'] > 0) {
1819 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1827 * Quick clean by emptying tables created for the test.
1829 * @param array $tablesToTruncate
1830 * @param bool $dropCustomValueTables
1832 * @throws \CRM_Core_Exception
1834 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1836 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1838 if ($dropCustomValueTables) {
1839 $optionGroupResult = CRM_Core_DAO
::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1840 while ($optionGroupResult->fetch()) {
1841 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1842 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1843 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1844 // generic cleanup routine.
1845 if (!empty($optionGroupResult->option_group_id
) && $optionGroupResult->option_group_id
> 50) {
1846 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id
);
1849 $tablesToTruncate[] = 'civicrm_custom_group';
1850 $tablesToTruncate[] = 'civicrm_custom_field';
1853 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1855 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1856 foreach ($tablesToTruncate as $table) {
1857 $sql = "TRUNCATE TABLE $table";
1858 CRM_Core_DAO
::executeQuery($sql);
1860 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1862 if ($dropCustomValueTables) {
1863 $dbName = self
::getDBName();
1865 SELECT TABLE_NAME as tableName
1866 FROM INFORMATION_SCHEMA.TABLES
1867 WHERE TABLE_SCHEMA = '{$dbName}'
1868 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1871 $tableDAO = CRM_Core_DAO
::executeQuery($query);
1872 while ($tableDAO->fetch()) {
1873 $sql = "DROP TABLE {$tableDAO->tableName}";
1874 CRM_Core_DAO
::executeQuery($sql);
1880 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1882 * @throws \CRM_Core_Exception
1884 public function quickCleanUpFinancialEntities() {
1885 $tablesToTruncate = [
1887 'civicrm_activity_contact',
1888 'civicrm_contribution',
1889 'civicrm_contribution_soft',
1890 'civicrm_contribution_product',
1891 'civicrm_financial_trxn',
1892 'civicrm_financial_item',
1893 'civicrm_contribution_recur',
1894 'civicrm_line_item',
1895 'civicrm_contribution_page',
1896 'civicrm_payment_processor',
1897 'civicrm_entity_financial_trxn',
1898 'civicrm_membership',
1899 'civicrm_membership_type',
1900 'civicrm_membership_payment',
1901 'civicrm_membership_log',
1902 'civicrm_membership_block',
1904 'civicrm_participant',
1905 'civicrm_participant_payment',
1907 'civicrm_pcp_block',
1909 'civicrm_pledge_block',
1910 'civicrm_pledge_payment',
1911 'civicrm_price_set_entity',
1912 'civicrm_price_field_value',
1913 'civicrm_price_field',
1915 $this->quickCleanup($tablesToTruncate);
1916 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1917 $this->restoreDefaultPriceSetConfig();
1918 $this->disableTaxAndInvoicing();
1919 $this->setCurrencySeparators(',');
1920 CRM_Core_PseudoConstant
::flush('taxRates');
1921 System
::singleton()->flushProcessors();
1922 // @fixme this parameter is leaking - it should not be defined as a class static
1923 // but for now we just handle in tear down.
1924 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1928 * Reset the price set config so results exist.
1930 public function restoreDefaultPriceSetConfig() {
1931 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1932 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1933 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)");
1934 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)");
1938 * Recreate default membership types.
1940 public function restoreMembershipTypes() {
1941 CRM_Core_DAO
::executeQuery(
1942 "REPLACE INTO civicrm_membership_type
1943 (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)
1945 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1946 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1947 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1952 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1953 * Default behaviour is to also delete the entity
1954 * @param array $params
1955 * Params array to check against.
1957 * Id of the entity concerned.
1958 * @param string $entity
1959 * Name of entity concerned (e.g. membership).
1960 * @param bool $delete
1961 * Should the entity be deleted as part of this check.
1962 * @param string $errorText
1963 * Text to print on error.
1967 * @param array $params
1970 * @param int $delete
1971 * @param string $errorText
1973 * @throws CRM_Core_Exception
1975 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1977 $result = $this->callAPISuccessGetSingle($entity, [
1982 $this->callAPISuccess($entity, 'Delete', [
1986 $dateFields = $keys = $dateTimeFields = [];
1987 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1988 foreach ($fields['values'] as $field => $settings) {
1989 if (array_key_exists($field, $result)) {
1990 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
1993 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
1995 $type = $settings['type'] ??
NULL;
1996 if ($type == CRM_Utils_Type
::T_DATE
) {
1997 $dateFields[] = $settings['name'];
1998 // we should identify both real names & unique names as dates
1999 if ($field != $settings['name']) {
2000 $dateFields[] = $field;
2003 if ($type == CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
2004 $dateTimeFields[] = $settings['name'];
2005 // we should identify both real names & unique names as dates
2006 if ($field != $settings['name']) {
2007 $dateTimeFields[] = $field;
2012 if (strtolower($entity) == 'contribution') {
2013 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
2014 // this is not returned in id format
2015 unset($params['payment_instrument_id']);
2016 $params['contribution_source'] = $params['source'];
2017 unset($params['source']);
2020 foreach ($params as $key => $value) {
2021 if ($key == 'version' ||
substr($key, 0, 3) == 'api' ||
!array_key_exists($keys[$key], $result)) {
2024 if (in_array($key, $dateFields)) {
2025 $value = date('Y-m-d', strtotime($value));
2026 $result[$key] = date('Y-m-d', strtotime($result[$key]));
2028 if (in_array($key, $dateTimeFields)) {
2029 $value = date('Y-m-d H:i:s', strtotime($value));
2030 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
2032 $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);
2037 * Get formatted values in the actual and expected result.
2039 * @param array $actual
2040 * Actual calculated values.
2041 * @param array $expected
2044 public function checkArrayEquals(&$actual, &$expected) {
2045 self
::unsetId($actual);
2046 self
::unsetId($expected);
2047 $this->assertEquals($expected, $actual);
2051 * Unset the key 'id' from the array
2053 * @param array $unformattedArray
2054 * The array from which the 'id' has to be unset.
2056 public static function unsetId(&$unformattedArray) {
2057 $formattedArray = [];
2058 if (array_key_exists('id', $unformattedArray)) {
2059 unset($unformattedArray['id']);
2061 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
2062 foreach ($unformattedArray['values'] as $key => $value) {
2063 if (is_array($value)) {
2064 foreach ($value as $k => $v) {
2070 elseif ($key == 'id') {
2071 $unformattedArray[$key];
2073 $formattedArray = [$value];
2075 $unformattedArray['values'] = $formattedArray;
2080 * Helper to enable/disable custom directory support
2082 * @param array $customDirs
2084 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2085 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2087 public function customDirectories($customDirs) {
2088 $config = CRM_Core_Config
::singleton();
2090 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2091 unset($config->customPHPPathDir
);
2093 elseif ($customDirs['php_path'] === TRUE) {
2094 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2097 $config->customPHPPathDir
= $php_path;
2100 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2101 unset($config->customTemplateDir
);
2103 elseif ($customDirs['template_path'] === TRUE) {
2104 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2107 $config->customTemplateDir
= $template_path;
2112 * Generate a temporary folder.
2114 * @param string $prefix
2118 public function createTempDir($prefix = 'test-') {
2119 $tempDir = CRM_Utils_File
::tempdir($prefix);
2120 $this->tempDirs
[] = $tempDir;
2124 public function cleanTempDirs() {
2125 if (!is_array($this->tempDirs
)) {
2126 // fix test errors where this is not set
2129 foreach ($this->tempDirs
as $tempDir) {
2130 if (is_dir($tempDir)) {
2131 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2137 * Temporarily replace the singleton extension with a different one.
2139 * @param \CRM_Extension_System $system
2141 public function setExtensionSystem(CRM_Extension_System
$system) {
2142 if ($this->origExtensionSystem
== NULL) {
2143 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2145 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2148 public function unsetExtensionSystem() {
2149 if ($this->origExtensionSystem
!== NULL) {
2150 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2151 $this->origExtensionSystem
= NULL;
2156 * Temporarily alter the settings-metadata to add a mock setting.
2158 * WARNING: The setting metadata will disappear on the next cache-clear.
2164 public function setMockSettingsMetaData($extras) {
2165 CRM_Utils_Hook
::singleton()
2166 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2167 $metadata = array_merge($metadata, $extras);
2170 Civi
::service('settings_manager')->flush();
2172 $fields = $this->callAPISuccess('setting', 'getfields', []);
2173 foreach ($extras as $key => $spec) {
2174 $this->assertNotEmpty($spec['title']);
2175 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2180 * @param string $name
2182 public function financialAccountDelete($name) {
2183 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2184 $financialAccount->name
= $name;
2185 if ($financialAccount->find(TRUE)) {
2186 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2187 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2188 $entityFinancialType->delete();
2189 $financialAccount->delete();
2194 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2195 * (NB unclear if this is still required)
2197 public function _sethtmlGlobals() {
2198 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2200 'html_quickform_rule_required',
2201 'HTML/QuickForm/Rule/Required.php',
2204 'html_quickform_rule_range',
2205 'HTML/QuickForm/Rule/Range.php',
2208 'html_quickform_rule_range',
2209 'HTML/QuickForm/Rule/Range.php',
2212 'html_quickform_rule_range',
2213 'HTML/QuickForm/Rule/Range.php',
2216 'html_quickform_rule_email',
2217 'HTML/QuickForm/Rule/Email.php',
2220 'html_quickform_rule_regex',
2221 'HTML/QuickForm/Rule/Regex.php',
2224 'html_quickform_rule_regex',
2225 'HTML/QuickForm/Rule/Regex.php',
2228 'html_quickform_rule_regex',
2229 'HTML/QuickForm/Rule/Regex.php',
2232 'html_quickform_rule_regex',
2233 'HTML/QuickForm/Rule/Regex.php',
2235 'nopunctuation' => [
2236 'html_quickform_rule_regex',
2237 'HTML/QuickForm/Rule/Regex.php',
2240 'html_quickform_rule_regex',
2241 'HTML/QuickForm/Rule/Regex.php',
2244 'html_quickform_rule_callback',
2245 'HTML/QuickForm/Rule/Callback.php',
2248 'html_quickform_rule_compare',
2249 'HTML/QuickForm/Rule/Compare.php',
2252 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2253 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2255 'HTML/QuickForm/group.php',
2256 'HTML_QuickForm_group',
2259 'HTML/QuickForm/hidden.php',
2260 'HTML_QuickForm_hidden',
2263 'HTML/QuickForm/reset.php',
2264 'HTML_QuickForm_reset',
2267 'HTML/QuickForm/checkbox.php',
2268 'HTML_QuickForm_checkbox',
2271 'HTML/QuickForm/file.php',
2272 'HTML_QuickForm_file',
2275 'HTML/QuickForm/image.php',
2276 'HTML_QuickForm_image',
2279 'HTML/QuickForm/password.php',
2280 'HTML_QuickForm_password',
2283 'HTML/QuickForm/radio.php',
2284 'HTML_QuickForm_radio',
2287 'HTML/QuickForm/button.php',
2288 'HTML_QuickForm_button',
2291 'HTML/QuickForm/submit.php',
2292 'HTML_QuickForm_submit',
2295 'HTML/QuickForm/select.php',
2296 'HTML_QuickForm_select',
2299 'HTML/QuickForm/hiddenselect.php',
2300 'HTML_QuickForm_hiddenselect',
2303 'HTML/QuickForm/text.php',
2304 'HTML_QuickForm_text',
2307 'HTML/QuickForm/textarea.php',
2308 'HTML_QuickForm_textarea',
2311 'HTML/QuickForm/fckeditor.php',
2312 'HTML_QuickForm_FCKEditor',
2315 'HTML/QuickForm/tinymce.php',
2316 'HTML_QuickForm_TinyMCE',
2319 'HTML/QuickForm/dojoeditor.php',
2320 'HTML_QuickForm_dojoeditor',
2323 'HTML/QuickForm/link.php',
2324 'HTML_QuickForm_link',
2327 'HTML/QuickForm/advcheckbox.php',
2328 'HTML_QuickForm_advcheckbox',
2331 'HTML/QuickForm/date.php',
2332 'HTML_QuickForm_date',
2335 'HTML/QuickForm/static.php',
2336 'HTML_QuickForm_static',
2339 'HTML/QuickForm/header.php',
2340 'HTML_QuickForm_header',
2343 'HTML/QuickForm/html.php',
2344 'HTML_QuickForm_html',
2347 'HTML/QuickForm/hierselect.php',
2348 'HTML_QuickForm_hierselect',
2351 'HTML/QuickForm/autocomplete.php',
2352 'HTML_QuickForm_autocomplete',
2355 'HTML/QuickForm/xbutton.php',
2356 'HTML_QuickForm_xbutton',
2358 'advmultiselect' => [
2359 'HTML/QuickForm/advmultiselect.php',
2360 'HTML_QuickForm_advmultiselect',
2366 * Set up an acl allowing contact to see 2 specified groups
2367 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2369 * You need to have pre-created these groups & created the user e.g
2370 * $this->createLoggedInUser();
2371 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2372 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2374 * @param bool $isProfile
2376 public function setupACL($isProfile = FALSE) {
2378 $_REQUEST = $this->_params
;
2380 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2381 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2382 $ov = new CRM_Core_DAO_OptionValue();
2383 $ov->option_group_id
= $optionGroupID;
2385 if ($ov->find(TRUE)) {
2386 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2388 $optionValue = $this->callAPISuccess('option_value', 'create', [
2389 'option_group_id' => $optionGroupID,
2390 'label' => 'pick me',
2394 CRM_Core_DAO
::executeQuery("
2395 TRUNCATE civicrm_acl_cache
2398 CRM_Core_DAO
::executeQuery("
2399 TRUNCATE civicrm_acl_contact_cache
2402 CRM_Core_DAO
::executeQuery("
2403 INSERT INTO civicrm_acl_entity_role (
2404 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2405 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2409 CRM_Core_DAO
::executeQuery("
2410 INSERT INTO civicrm_acl (
2411 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2414 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2419 CRM_Core_DAO
::executeQuery("
2420 INSERT INTO civicrm_acl (
2421 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2424 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2428 CRM_Core_DAO
::executeQuery("
2429 INSERT INTO civicrm_acl (
2430 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2433 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2438 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2439 $this->callAPISuccess('group_contact', 'create', [
2440 'group_id' => $this->_permissionedGroup
,
2441 'contact_id' => $this->_loggedInUser
,
2446 CRM_ACL_BAO_Cache
::resetCache();
2447 CRM_ACL_API
::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2452 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2454 public function offsetDefaultPriceSet() {
2455 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2456 $firstID = $contributionPriceSet['id'];
2457 $this->callAPISuccess('price_set', 'create', [
2458 'id' => $contributionPriceSet['id'],
2462 unset($contributionPriceSet['id']);
2463 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2464 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2465 'price_set_id' => $firstID,
2466 'options' => ['limit' => 1],
2468 unset($priceField['id']);
2469 $priceField['price_set_id'] = $newPriceSet['id'];
2470 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2471 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2472 'price_set_id' => $firstID,
2474 'options' => ['limit' => 1],
2477 unset($priceFieldValue['id']);
2478 //create some padding to use up ids
2479 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2480 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2481 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2485 * Create an instance of the paypal processor.
2487 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2488 * this parent class & we don't have a structure for that yet
2489 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2490 * & the best protection against that is the functions this class affords
2492 * @param array $params
2494 * @return int $result['id'] payment processor id
2496 public function paymentProcessorCreate($params = []) {
2497 $params = array_merge([
2499 'domain_id' => CRM_Core_Config
::domainID(),
2500 'payment_processor_type_id' => 'PayPal',
2504 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2505 'password' => '1183377788',
2506 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2507 'url_site' => 'https://www.sandbox.paypal.com/',
2508 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2509 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2510 'class_name' => 'Payment_PayPalImpl',
2511 'billing_mode' => 3,
2512 'financial_type_id' => 1,
2513 'financial_account_id' => 12,
2514 // Credit card = 1 so can pass 'by accident'.
2515 'payment_instrument_id' => 'Debit Card',
2517 if (!is_numeric($params['payment_processor_type_id'])) {
2518 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2520 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2521 'name' => $params['payment_processor_type_id'],
2525 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2526 return $result['id'];
2530 * Set up initial recurring payment allowing subsequent IPN payments.
2532 * @param array $recurParams (Optional)
2533 * @param array $contributionParams (Optional)
2535 * @throws \CRM_Core_Exception
2537 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2538 $this->ids
['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
2539 $contributionParams = array_merge([
2540 'total_amount' => '200',
2541 'invoice_id' => $this->_invoiceID
,
2542 'financial_type_id' => 'Donation',
2543 'contribution_status_id' => 'Pending',
2544 'contact_id' => $this->_contactID
,
2545 'contribution_page_id' => $this->_contributionPageID
,
2546 'payment_processor_id' => $this->_paymentProcessorID
,
2548 'receive_date' => '2019-07-25 07:34:23',
2549 'skipCleanMoney' => TRUE,
2550 'amount_level' => 'expensive',
2551 'campaign_id' => $this->ids
['campaign'][0],
2552 'source' => 'Online Contribution: Page name',
2553 ], $contributionParams);
2554 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2555 'contact_id' => $this->_contactID
,
2558 'installments' => 5,
2559 'frequency_unit' => 'Month',
2560 'frequency_interval' => 1,
2561 'invoice_id' => $this->_invoiceID
,
2562 'contribution_status_id' => 2,
2563 'payment_processor_id' => $this->_paymentProcessorID
,
2564 // processor provided ID - use contact ID as proxy.
2565 'processor_id' => $this->_contactID
,
2566 'api.Order.create' => $contributionParams,
2567 ], $recurParams))['values'][0];
2568 $this->_contributionRecurID
= $contributionRecur['id'];
2569 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2570 $this->ids
['Contribution'][0] = $this->_contributionID
;
2574 * 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
2576 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2578 * @throws \CRM_Core_Exception
2580 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2581 $membershipParams = $recurParams = [];
2582 if (!empty($params['duration_unit'])) {
2583 $membershipParams['duration_unit'] = $params['duration_unit'];
2585 if (!empty($params['frequency_unit'])) {
2586 $recurParams['frequency_unit'] = $params['frequency_unit'];
2589 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2590 //create a contribution so our membership & contribution don't both have id = 1
2591 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2592 $this->contributionCreate([
2593 'contact_id' => $this->_contactID
,
2595 'financial_type_id' => 1,
2596 'invoice_id' => 'abcd',
2598 'receive_date' => '2019-07-25 07:34:23',
2602 $this->ids
['membership'] = $this->callAPISuccess('Membership', 'create', [
2603 'contact_id' => $this->_contactID
,
2604 'membership_type_id' => $this->ids
['membership_type'],
2605 'format.only_id' => TRUE,
2606 'source' => 'Payment',
2607 'skipLineItem' => TRUE,
2609 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2614 'entity_table' => 'civicrm_membership',
2615 'entity_id' => $this->ids
['membership'],
2616 'label' => 'General',
2618 'unit_price' => 200,
2619 'line_total' => 200,
2620 'financial_type_id' => 1,
2621 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2623 'label' => 'Membership Amount',
2624 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2626 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2628 'label' => 'General',
2629 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2636 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids
['membership'], 'contribution_recur_id' => $this->_contributionRecurID
]);
2644 public function CiviUnitTestCase_fatalErrorHandler($message) {
2645 throw new Exception("{$message['message']}: {$message['code']}");
2649 * Wrap the entire test case in a transaction.
2651 * Only subsequent DB statements will be wrapped in TX -- this cannot
2652 * retroactively wrap old DB statements. Therefore, it makes sense to
2653 * call this at the beginning of setUp().
2655 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2656 * this option does not work with, e.g., custom-data.
2658 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2659 * if TRUNCATE or ALTER is called while using a transaction.
2662 * Whether to use nesting or reference-counting.
2664 public function useTransaction($nest = TRUE) {
2666 $this->tx
= new CRM_Core_Transaction($nest);
2667 $this->tx
->rollback();
2672 * Assert the attachment exists.
2674 * @param bool $exists
2675 * @param array $apiResult
2677 protected function assertAttachmentExistence($exists, $apiResult) {
2678 $fileId = $apiResult['id'];
2679 $this->assertTrue(is_numeric($fileId));
2680 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2681 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2682 1 => [$fileId, 'Int'],
2684 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2685 1 => [$fileId, 'Int'],
2690 * Assert 2 sql strings are the same, ignoring double spaces.
2692 * @param string $expectedSQL
2693 * @param string $actualSQL
2694 * @param string $message
2696 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2697 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2698 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2699 $this->assertEquals($expected, $actual, $message);
2703 * Create a price set for an event.
2705 * @param int $feeTotal
2706 * @param int $minAmt
2707 * @param string $type
2709 * @param array $options
2713 * @throws \CRM_Core_Exception
2715 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2716 // creating price set, price field
2717 $paramsSet['title'] = 'Price Set';
2718 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2719 $paramsSet['is_active'] = FALSE;
2720 $paramsSet['extends'] = 1;
2721 $paramsSet['min_amount'] = $minAmt;
2723 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2724 $this->_ids
['price_set'] = $priceSet->id
;
2727 'label' => 'Price Field',
2728 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2729 'html_type' => $type,
2730 'price' => $feeTotal,
2731 'option_label' => ['1' => 'Price Field'],
2732 'option_value' => ['1' => $feeTotal],
2733 'option_name' => ['1' => $feeTotal],
2734 'option_weight' => ['1' => 1],
2735 'option_amount' => ['1' => 1],
2736 'is_display_amounts' => 1,
2738 'options_per_line' => 1,
2739 'is_active' => ['1' => 1],
2740 'price_set_id' => $this->_ids
['price_set'],
2741 'is_enter_qty' => 1,
2742 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2744 if ($type === 'Radio') {
2745 foreach ($options as $index => $option) {
2746 $paramsField['is_enter_qty'] = 0;
2747 $optionID = $index +
2;
2748 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2749 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2753 $this->callAPISuccess('PriceField', 'create', $paramsField);
2754 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2755 $this->_ids
['price_field'] = array_keys($fields['values']);
2756 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2757 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2759 return $this->_ids
['price_set'];
2763 * Add a profile to a contribution page.
2765 * @param string $name
2766 * @param int $contributionPageID
2767 * @param string $module
2769 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2771 'uf_group_id' => $name,
2772 'module' => $module,
2773 'entity_table' => 'civicrm_contribution_page',
2774 'entity_id' => $contributionPageID,
2777 if ($module !== 'CiviContribute') {
2778 $params['module_data'] = [$module => []];
2780 $this->callAPISuccess('UFJoin', 'create', $params);
2784 * Add participant with contribution
2788 * @throws \CRM_Core_Exception
2790 protected function createPartiallyPaidParticipantOrder() {
2791 $orderParams = $this->getParticipantOrderParams();
2792 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2793 return $this->callAPISuccess('Order', 'create', $orderParams);
2799 * @param string $component
2800 * @param int $componentId
2801 * @param array $priceFieldOptions
2805 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2806 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2807 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2808 $paramsSet['is_active'] = TRUE;
2809 $paramsSet['financial_type_id'] = 'Event Fee';
2810 $paramsSet['extends'] = 1;
2811 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2812 $priceSetId = $priceSet['id'];
2813 //Checking for priceset added in the table.
2814 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2815 'id', $paramsSet['title'], 'Check DB for created priceset'
2817 $paramsField = array_merge([
2818 'label' => 'Price Field',
2819 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2820 'html_type' => 'CheckBox',
2821 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2822 'option_value' => ['1' => 100, '2' => 200],
2823 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2824 'option_weight' => ['1' => 1, '2' => 2],
2825 'option_amount' => ['1' => 100, '2' => 200],
2826 'is_display_amounts' => 1,
2828 'options_per_line' => 1,
2829 'is_active' => ['1' => 1, '2' => 1],
2830 'price_set_id' => $priceSet['id'],
2831 'is_enter_qty' => 1,
2832 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2833 ], $priceFieldOptions);
2835 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2837 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSetId);
2839 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2843 * Replace the template with a test-oriented template designed to show all the variables.
2845 * @param string $templateName
2846 * @param string $type
2848 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2849 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2850 CRM_Core_DAO
::executeQuery(
2851 "UPDATE civicrm_msg_template
2852 SET msg_{$type} = %1
2853 WHERE workflow_name = '{$templateName}'
2854 AND is_default = 1", [1 => [$testTemplate, 'String']]
2859 * Reinstate the default template.
2861 * @param string $templateName
2862 * @param string $type
2864 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2865 CRM_Core_DAO
::executeQuery(
2866 "UPDATE civicrm_option_group og
2867 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2868 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2869 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2870 SET m.msg_{$type} = m2.msg_{$type}
2871 WHERE og.name = 'msg_tpl_workflow_contribution'
2872 AND ov.name = '{$templateName}'
2873 AND m.is_default = 1"
2878 * Flush statics relating to financial type.
2880 protected function flushFinancialTypeStatics() {
2881 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2882 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2884 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2885 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2887 CRM_Contribute_PseudoConstant
::flush('financialType');
2888 CRM_Contribute_PseudoConstant
::flush('membershipType');
2889 // Pseudoconstants may be saved to the cache table.
2890 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2891 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2892 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2896 * Set the permissions to the supplied array.
2898 * @param array $permissions
2900 protected function setPermissions($permissions) {
2901 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2902 $this->flushFinancialTypeStatics();
2906 * @param array $params
2909 public function _checkFinancialRecords($params, $context) {
2911 'entity_id' => $params['id'],
2912 'entity_table' => 'civicrm_contribution',
2914 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2915 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2916 if ($context == 'pending') {
2917 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2918 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2921 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2923 'id' => $trxn['financial_trxn_id'],
2925 if ($context != 'online' && $context != 'payLater') {
2927 'to_financial_account_id' => 6,
2928 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2932 if ($context == 'feeAmount') {
2933 $compareParams['fee_amount'] = 50;
2935 elseif ($context == 'online') {
2937 'to_financial_account_id' => 12,
2938 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2940 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2943 elseif ($context == 'payLater') {
2945 'to_financial_account_id' => 7,
2946 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2950 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2952 'financial_trxn_id' => $trxn['financial_trxn_id'],
2953 'entity_table' => 'civicrm_financial_item',
2955 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2957 'id' => $entityTrxn['entity_id'],
2960 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2962 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2964 if ($context == 'payLater') {
2966 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2968 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2971 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2972 if ($context == 'feeAmount') {
2974 'entity_id' => $params['id'],
2975 'entity_table' => 'civicrm_contribution',
2977 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2979 'id' => $maxTrxn['financial_trxn_id'],
2982 'to_financial_account_id' => 5,
2983 'from_financial_account_id' => 6,
2984 'total_amount' => 50,
2987 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2988 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2990 'entity_id' => $trxnId['financialTrxnId'],
2991 'entity_table' => 'civicrm_financial_trxn',
2996 'financial_account_id' => 5,
2998 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
3000 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
3001 // line should be copied into all the functions that call this function & evaluated there
3002 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
3003 // when calling completeTransaction or repeatTransaction.
3004 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
3008 * Return financial type id on basis of name
3010 * @param string $name Financial type m/c name
3014 public function getFinancialTypeId($name) {
3015 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
3019 * Cleanup function for contents of $this->ids.
3021 * This is a best effort cleanup to use in tear downs etc.
3023 * It will not fail if the data has already been removed (some tests may do
3024 * their own cleanup).
3026 protected function cleanUpSetUpIDs() {
3027 foreach ($this->setupIDs
as $entity => $id) {
3029 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
3031 catch (CiviCRM_API3_Exception
$e) {
3032 // This is a best-effort cleanup function, ignore.
3038 * Create Financial Type.
3040 * @param array $params
3044 protected function createFinancialType($params = []) {
3045 $params = array_merge($params,
3047 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
3051 return $this->callAPISuccess('FinancialType', 'create', $params);
3055 * Create Payment Instrument.
3057 * @param array $params
3058 * @param string $financialAccountName
3062 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3063 $params = array_merge([
3064 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3065 'option_group_id' => 'payment_instrument',
3068 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3070 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3072 $financialAccountParams = [
3073 'entity_table' => 'civicrm_option_value',
3074 'entity_id' => $newPaymentInstrument,
3075 'account_relationship' => $relationTypeID,
3076 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3078 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3080 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3084 * Enable Tax and Invoicing
3086 * @param array $params
3088 * @return \Civi\Core\SettingsBag
3090 protected function enableTaxAndInvoicing($params = []) {
3091 // Enable component contribute setting
3092 $contributeSetting = array_merge($params,
3095 'invoice_prefix' => 'INV_',
3097 'due_date_period' => 'days',
3099 'is_email_pdf' => 1,
3100 'tax_term' => 'Sales Tax',
3101 'tax_display_settings' => 'Inclusive',
3104 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3108 * Enable Tax and Invoicing
3110 * @throws \CRM_Core_Exception
3112 protected function disableTaxAndInvoicing() {
3113 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3114 foreach ($accounts as $account) {
3115 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3116 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3119 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3120 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3122 return Civi
::settings()->set('invoicing', FALSE);
3126 * Add Sales Tax Account for the financial type.
3128 * @param int $financialTypeId
3130 * @param array $accountParams
3132 * @return CRM_Financial_DAO_EntityFinancialAccount
3133 * @throws \CRM_Core_Exception
3135 protected function addTaxAccountToFinancialType(int $financialTypeId, $accountParams = []) {
3136 $params = array_merge([
3137 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3138 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3139 'is_deductible' => 1,
3144 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3146 'entity_table' => 'civicrm_financial_type',
3147 'entity_id' => $financialTypeId,
3148 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3151 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3152 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = $params['tax_rate'];
3154 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3155 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3156 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3157 $dao->copyValues($entityParams);
3159 if ($dao->fetch()) {
3160 $entityParams['id'] = $dao->id
;
3162 $entityParams['financial_account_id'] = $account->id
;
3164 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3168 * Create price set with contribution test for test setup.
3170 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3171 * on parent class at some point (fn is not in 4.4).
3174 * @param array $params
3176 public function createPriceSetWithPage($entity = NULL, $params = []) {
3177 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3178 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3179 'title' => "Test Contribution Page",
3180 'financial_type_id' => 1,
3181 'currency' => 'NZD',
3182 'goal_amount' => 50,
3183 'is_pay_later' => 1,
3184 'is_monetary' => TRUE,
3185 'is_email_receipt' => FALSE,
3187 $priceSet = $this->callAPISuccess('price_set', 'create', [
3188 'is_quick_config' => 0,
3189 'extends' => 'CiviMember',
3190 'financial_type_id' => 1,
3191 'title' => 'my Page',
3193 $priceSetID = $priceSet['id'];
3195 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3196 $priceField = $this->callAPISuccess('price_field', 'create', [
3197 'price_set_id' => $priceSetID,
3198 'label' => 'Goat Breed',
3199 'html_type' => 'Radio',
3201 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3202 'price_set_id' => $priceSetID,
3203 'price_field_id' => $priceField['id'],
3204 'label' => 'Long Haired Goat',
3206 'financial_type_id' => 'Donation',
3207 'membership_type_id' => $membershipTypeID,
3208 'membership_num_terms' => 1,
3210 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3211 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3212 'price_set_id' => $priceSetID,
3213 'price_field_id' => $priceField['id'],
3214 'label' => 'Shoe-eating Goat',
3216 'financial_type_id' => 'Donation',
3217 'membership_type_id' => $membershipTypeID,
3218 'membership_num_terms' => 2,
3220 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3222 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3223 'price_set_id' => $priceSetID,
3224 'price_field_id' => $priceField['id'],
3225 'label' => 'Shoe-eating Goat',
3227 'financial_type_id' => 'Donation',
3229 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3231 $this->_ids
['price_set'] = $priceSetID;
3232 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3233 $this->_ids
['price_field'] = [$priceField['id']];
3235 $this->_ids
['membership_type'] = $membershipTypeID;
3239 * Only specified contact returned.
3241 * @implements CRM_Utils_Hook::aclWhereClause
3245 * @param $whereTables
3249 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3250 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3254 * @implements CRM_Utils_Hook::selectWhereClause
3256 * @param string $entity
3257 * @param array $clauses
3259 public function selectWhereClauseHook($entity, &$clauses) {
3260 if ($entity == 'Event') {
3261 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3266 * An implementation of hook_civicrm_post used with all our test cases.
3269 * @param string $objectName
3270 * @param int $objectId
3273 public function onPost($op, $objectName, $objectId, &$objectRef) {
3274 if ($op == 'create' && $objectName == 'Individual') {
3275 CRM_Core_DAO
::executeQuery(
3276 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3278 1 => [$objectId, 'Integer'],
3283 if ($op == 'edit' && $objectName == 'Participant') {
3285 1 => [$objectId, 'Integer'],
3287 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3288 CRM_Core_DAO
::executeQuery($query, $params);
3293 * Instantiate form object.
3295 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3297 * @param string $class
3298 * Name of form class.
3300 * @param array $formValues
3302 * @param string $pageName
3304 * @return \CRM_Core_Form
3305 * @throws \CRM_Core_Exception
3307 public function getFormObject($class, $formValues = [], $pageName = '') {
3308 $_POST = $formValues;
3309 /* @var CRM_Core_Form $form */
3310 $form = new $class();
3311 $_SERVER['REQUEST_METHOD'] = 'GET';
3313 case 'CRM_Event_Cart_Form_Checkout_Payment':
3314 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3315 $form->controller
= new CRM_Event_Cart_Controller_Checkout();
3319 $form->controller
= new CRM_Core_Controller();
3322 $pageName = $form->getName();
3324 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3325 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3330 * Get possible thousand separators.
3334 public function getThousandSeparators() {
3335 return [['.'], [',']];
3339 * Get the boolean options as a provider.
3343 public function getBooleanDataProvider() {
3344 return [[TRUE], [FALSE]];
3348 * Set the separators for thousands and decimal points.
3350 * Note that this only covers some common scenarios.
3352 * It does not cater for a situation where the thousand separator is a [space]
3353 * Latter is the Norwegian localization. At least some tests need to
3354 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3355 * to provide broader coverage.
3357 * @param string $thousandSeparator
3359 protected function setCurrencySeparators($thousandSeparator) {
3360 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3361 Civi
::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3365 * Sets the thousand separator.
3367 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3369 * @param $thousandSeparator
3371 protected function setMonetaryThousandSeparator($thousandSeparator) {
3372 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3376 * Sets the decimal separator.
3378 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3380 * @param $decimalPoint
3382 protected function setMonetaryDecimalPoint($decimalPoint) {
3383 Civi
::settings()->set('monetaryDecimalPoint', $decimalPoint);
3387 * Sets the default currency.
3391 protected function setDefaultCurrency($currency) {
3392 Civi
::settings()->set('defaultCurrency', $currency);
3396 * Format money as it would be input.
3398 * @param string $amount
3402 protected function formatMoneyInput($amount) {
3403 return CRM_Utils_Money
::format($amount, NULL, '%a');
3407 * Get the contribution object.
3409 * @param int $contributionID
3411 * @return \CRM_Contribute_BAO_Contribution
3413 protected function getContributionObject($contributionID) {
3414 $contributionObj = new CRM_Contribute_BAO_Contribution();
3415 $contributionObj->id
= $contributionID;
3416 $contributionObj->find(TRUE);
3417 return $contributionObj;
3421 * Enable multilingual.
3423 public function enableMultilingual() {
3424 $this->callAPISuccess('Setting', 'create', [
3425 'lcMessages' => 'en_US',
3426 'languageLimit' => [
3431 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3434 $dbLocale = '_en_US';
3438 * Setup or clean up SMS tests
3440 * @param bool $teardown
3442 * @throws \CiviCRM_API3_Exception
3444 public function setupForSmsTests($teardown = FALSE) {
3445 require_once 'CiviTest/CiviTestSMSProvider.php';
3447 // Option value params for CiviTestSMSProvider
3448 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3450 'option_group_id' => $groupID,
3451 'label' => 'unittestSMS',
3452 'value' => 'unit.test.sms',
3453 'name' => 'CiviTestSMSProvider',
3460 // Test completed, delete provider
3461 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3462 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3466 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3467 // in CRM/Sms/Provider.php even though it is not an extension.
3468 return civicrm_api3('option_value', 'create', $params);
3472 * Start capturing browser output.
3474 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3476 protected function startCapturingOutput() {
3478 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3482 * Stop capturing browser output and return as a csv.
3484 * @param bool $isFirstRowHeaders
3486 * @return \League\Csv\Reader
3488 * @throws \League\Csv\Exception
3490 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3491 $output = ob_get_flush();
3492 $stream = fopen('php://memory', 'r+');
3493 fwrite($stream, $output);
3495 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3496 $csv = Reader
::createFromString($output);
3497 if ($isFirstRowHeaders) {
3498 $csv->setHeaderOffset(0);
3505 * Rename various labels to not match the names.
3507 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3508 * code that expects it to.
3510 protected function renameLabels() {
3511 $replacements = ['Pending', 'Refunded'];
3512 foreach ($replacements as $name) {
3513 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3518 * Undo any label renaming.
3520 protected function resetLabels() {
3521 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3525 * Get parameters to set up a multi-line participant order.
3528 * @throws \CRM_Core_Exception
3530 protected function getParticipantOrderParams(): array {
3531 $this->_contactId
= $this->individualCreate();
3532 $event = $this->eventCreate();
3533 $this->_eventId
= $event['id'];
3535 'id' => $this->_eventId
,
3536 'financial_type_id' => 4,
3539 $this->callAPISuccess('event', 'create', $eventParams);
3540 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3541 $participantParams = [
3542 'financial_type_id' => 4,
3543 'event_id' => $this->_eventId
,
3546 'fee_currency' => 'USD',
3547 'contact_id' => $this->_contactId
,
3549 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3551 'total_amount' => 300,
3552 'currency' => 'USD',
3553 'contact_id' => $this->_contactId
,
3554 'financial_type_id' => 4,
3555 'contribution_status_id' => 'Pending',
3556 'contribution_mode' => 'participant',
3557 'participant_id' => $participant['id'],
3559 foreach ($priceFields['values'] as $key => $priceField) {
3560 $orderParams['line_items'][] = [
3563 'price_field_id' => $priceField['price_field_id'],
3564 'price_field_value_id' => $priceField['id'],
3565 'label' => $priceField['label'],
3566 'field_title' => $priceField['label'],
3568 'unit_price' => $priceField['amount'],
3569 'line_total' => $priceField['amount'],
3570 'financial_type_id' => $priceField['financial_type_id'],
3571 'entity_table' => 'civicrm_participant',
3574 'params' => $participant,
3577 return $orderParams;
3583 * @throws \CRM_Core_Exception
3585 protected function validatePayments($payments) {
3586 foreach ($payments as $payment) {
3587 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3588 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3589 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3590 // This is a pretty rough way at guessing which payment is the overpayment - but
3591 // for the test suite it should be enough.
3594 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3595 'financial_trxn_id' => $payment['id'],
3596 'entity_table' => 'civicrm_financial_item',
3597 'return' => ['amount'],
3600 foreach ($items as $item) {
3601 $itemTotal +
= $item['amount'];
3603 $this->assertEquals($payment['total_amount'], $itemTotal);
3608 * Validate all created payments.
3610 * @throws \CRM_Core_Exception
3612 protected function validateAllPayments() {
3613 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3614 $this->validatePayments($payments);
3618 * Validate all created contributions.
3620 * @throws \CRM_Core_Exception
3622 protected function validateAllContributions() {
3623 $contributions = $this->callAPISuccess('Contribution', 'get', ['return' => ['tax_amount', 'total_amount']])['values'];
3624 foreach ($contributions as $contribution) {
3625 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3628 foreach ($lineItems as $lineItem) {
3629 $total +
= $lineItem['line_total'];
3630 $taxTotal +
= (float) ($lineItem['tax_amount'] ??
0);
3632 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ??
0));
3633 $this->assertEquals($total, $contribution['total_amount']);
3639 * @throws \CRM_Core_Exception
3641 protected function createRuleGroup() {
3642 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3643 'contact_type' => 'Individual',
3645 'used' => 'General',
3646 'name' => 'TestRule',
3647 'title' => 'TestRule',
3654 * Generic create test.
3656 * @param int $version
3658 * @throws \CRM_Core_Exception
3660 protected function basicCreateTest(int $version) {
3661 $this->_apiversion
= $version;
3662 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
3663 $this->assertEquals(1, $result['count']);
3664 $this->assertNotNull($result['values'][$result['id']]['id']);
3665 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
3669 * Generic delete test.
3671 * @param int $version
3673 * @throws \CRM_Core_Exception
3675 protected function basicDeleteTest($version) {
3676 $this->_apiversion
= $version;
3677 $result = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
3678 $deleteParams = ['id' => $result['id']];
3679 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
3680 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
3681 $this->assertEquals(0, $checkDeleted['count']);
3685 * Create and return a case object for the given Client ID.
3687 * @param int $clientId
3688 * @param int $loggedInUser
3689 * Omit or pass NULL to use the same as clientId
3690 * @param array $extra
3691 * Optional specific parameters such as start_date
3693 * @return CRM_Case_BAO_Case
3695 public function createCase($clientId, $loggedInUser = NULL, $extra = []) {
3696 if (empty($loggedInUser)) {
3697 // backwards compatibility - but it's more typical that the creator is a different person than the client
3698 $loggedInUser = $clientId;
3700 $caseParams = array_merge([
3701 'activity_subject' => 'Case Subject',
3702 'client_id' => $clientId,
3703 'case_type_id' => 1,
3705 'case_type' => 'housing_support',
3706 'subject' => 'Case Subject',
3707 'start_date' => date("Y-m-d"),
3708 'start_date_time' => date("YmdHis"),
3710 'activity_details' => '',
3712 $form = new CRM_Case_Form_Case();
3713 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3717 * Validate that all location entities have exactly one primary.
3719 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3721 public function assertLocationValidity() {
3722 $this->assertEquals(0, CRM_Core_DAO
::singleValueQuery('SELECT COUNT(*) FROM
3724 (SELECT a1.contact_id
3725 FROM civicrm_address a1
3726 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3727 AND a1.contact_id = a2.contact_id
3730 AND a2.id IS NOT NULL
3731 AND a1.contact_id IS NOT NULL
3733 SELECT a1.contact_id
3734 FROM civicrm_address a1
3735 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3736 AND a1.contact_id = a2.contact_id
3737 WHERE a1.is_primary = 0
3739 AND a1.contact_id IS NOT NULL
3743 SELECT a1.contact_id
3744 FROM civicrm_email a1
3745 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3746 AND a1.contact_id = a2.contact_id
3749 AND a2.id IS NOT NULL
3750 AND a1.contact_id IS NOT NULL
3752 SELECT a1.contact_id
3753 FROM civicrm_email a1
3754 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3755 AND a1.contact_id = a2.contact_id
3756 WHERE a1.is_primary = 0
3758 AND a1.contact_id IS NOT NULL
3762 SELECT a1.contact_id
3763 FROM civicrm_phone a1
3764 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3765 AND a1.contact_id = a2.contact_id
3768 AND a2.id IS NOT NULL
3769 AND a1.contact_id IS NOT NULL
3771 SELECT a1.contact_id
3772 FROM civicrm_phone a1
3773 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3774 AND a1.contact_id = a2.contact_id
3775 WHERE a1.is_primary = 0
3777 AND a1.contact_id IS NOT NULL
3781 SELECT a1.contact_id
3783 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3784 AND a1.contact_id = a2.contact_id
3787 AND a2.id IS NOT NULL
3788 AND a1.contact_id IS NOT NULL
3790 SELECT a1.contact_id
3792 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3793 AND a1.contact_id = a2.contact_id
3794 WHERE a1.is_primary = 0
3796 AND a1.contact_id IS NOT NULL
3800 SELECT a1.contact_id
3801 FROM civicrm_openid a1
3802 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3803 AND a1.contact_id = a2.contact_id
3804 WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3807 SELECT a1.contact_id
3808 FROM civicrm_openid a1
3809 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3810 AND a1.contact_id = a2.contact_id
3813 AND a2.id IS NOT NULL
3814 AND a1.contact_id IS NOT NULL
3816 SELECT a1.contact_id
3817 FROM civicrm_openid a1
3818 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3819 AND a1.contact_id = a2.contact_id
3820 WHERE a1.is_primary = 0
3822 AND a1.contact_id IS NOT NULL) as primary_descrepancies
3827 * Ensure the specified mysql mode/s are activated.
3829 * @param array $modes
3831 protected function ensureMySQLMode(array $modes): void
{
3832 $currentModes = array_fill_keys(CRM_Utils_SQL
::getSqlModes(), 1);
3833 $currentModes = array_merge($currentModes, array_fill_keys($modes, 1));
3834 CRM_Core_DAO
::executeQuery("SET GLOBAL sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
3835 CRM_Core_DAO
::executeQuery("SET sql_mode = '" . implode(',', array_keys($currentModes)) . "'");