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;
92 protected $_apiversion = 3;
95 * Track tables we have modified during a test.
99 protected $_tablesToTruncate = [];
103 * Array of temporary directory names
109 * populateOnce allows to skip db resets in setUp
111 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
112 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
115 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
117 * @see http://forum.civicrm.org/index.php/topic,18065.0.html
119 public static $populateOnce = FALSE;
122 * DBResetRequired allows skipping DB reset
123 * in specific test case. If you still need
124 * to reset single test (method) of such case, call
125 * $this->cleanDB() in the first line of this
129 public $DBResetRequired = TRUE;
132 * @var CRM_Core_Transaction|null
137 * Array of IDs created to support the test.
140 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
147 * Should financials be checked after the test but before tear down.
149 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
150 * are some test data issues and some real bugs currently blockinng.
154 protected $isValidateFinancialsOnPostAssert = FALSE;
157 * Should location types be checked to ensure primary addresses are correctly assigned after each test.
161 protected $isLocationTypesOnPostAssert = TRUE;
164 * Class used for hooks during tests.
166 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
168 * $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
170 * @var \CRM_Utils_Hook_UnitTests
176 * Common values to be re-used multiple times within a class - usually to create the relevant entity
178 protected $_params = [];
181 * @var CRM_Extension_System
183 protected $origExtensionSystem;
186 * Array of IDs created during test setup routine.
188 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
192 public $setupIDs = [];
197 * Because we are overriding the parent class constructor, we
198 * need to show the same arguments as exist in the constructor of
199 * PHPUnit_Framework_TestCase, since
200 * PHPUnit_Framework_TestSuite::createTest() creates a
201 * ReflectionClass of the Test class and checks the constructor
202 * of that class to decide how to set up the test.
204 * @param string $name
206 * @param string $dataName
208 public function __construct($name = NULL, array $data = [], $dataName = '') {
209 parent
::__construct($name, $data, $dataName);
211 // we need full error reporting
212 error_reporting(E_ALL
& ~E_NOTICE
);
214 self
::$_dbName = self
::getDBName();
216 // also load the class loader
217 require_once 'CRM/Core/ClassLoader.php';
218 CRM_Core_ClassLoader
::singleton()->register();
219 if (function_exists('_civix_phpunit_setUp')) {
220 // FIXME: loosen coupling
221 _civix_phpunit_setUp();
226 * Override to run the test and assert its state.
230 * @throws \PHPUnit_Framework_IncompleteTest
231 * @throws \PHPUnit_Framework_SkippedTest
233 protected function runTest() {
235 return parent
::runTest();
237 catch (PEAR_Exception
$e) {
238 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
239 throw new Exception(\CRM_Core_Error
::formatTextException($e), $e->getCode());
246 public function requireDBReset() {
247 return $this->DBResetRequired
;
253 public static function getDBName() {
254 static $dbName = NULL;
255 if ($dbName === NULL) {
256 require_once "DB.php";
257 $dsn = CRM_Utils_SQL
::autoSwitchDSN(CIVICRM_DSN
);
258 $dsninfo = DB
::parseDSN($dsn);
259 $dbName = $dsninfo['database'];
265 * Create database connection for this instance.
267 * Initialize the test database if it hasn't been initialized
270 protected function getConnection() {
271 if (!self
::$dbInit) {
272 $dbName = self
::getDBName();
274 // install test database
275 echo PHP_EOL
. "Installing {$dbName} database" . PHP_EOL
;
277 static::_populateDB(FALSE, $this);
279 self
::$dbInit = TRUE;
285 * Required implementation of abstract method.
287 protected function getDataSet() {
291 * @param bool $perClass
292 * @param null $object
295 * TRUE if the populate logic runs; FALSE if it is skipped
297 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
298 if (CIVICRM_UF
!== 'UnitTests') {
299 throw new \
RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
302 if ($perClass ||
$object == NULL) {
306 $dbreset = $object->requireDBReset();
309 if (self
::$populateOnce ||
!$dbreset) {
312 self
::$populateOnce = NULL;
314 Civi\Test
::data()->populate();
319 public static function setUpBeforeClass() {
320 static::_populateDB(TRUE);
322 // also set this global hack
323 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
327 * Common setup functions for all unit tests.
329 protected function setUp(): void
{
330 $session = CRM_Core_Session
::singleton();
331 $session->set('userID', NULL);
333 $this->_apiversion
= 3;
335 // Use a temporary file for STDIN
336 $GLOBALS['stdin'] = tmpfile();
337 if ($GLOBALS['stdin'] === FALSE) {
338 echo "Couldn't open temporary file\n";
342 // Get and save a connection to the database
343 $this->_dbconn
= $this->getConnection();
345 // reload database before each test
346 // $this->_populateDB();
348 // "initialize" CiviCRM to avoid problems when running single tests
349 // FIXME: look at it closer in second stage
351 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
352 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
354 // disable any left-over test extensions
355 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
357 // reset all the caches
358 CRM_Utils_System
::flushCache();
360 // initialize the object once db is loaded
361 \Civi
::$statics = [];
363 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
365 // when running unit tests, use mockup user framework
366 $this->hookClass
= CRM_Utils_Hook
::singleton();
368 // Make sure the DB connection is setup properly
369 $config->userSystem
->setMySQLTimeZone();
370 $env = new CRM_Utils_Check_Component_Env();
371 CRM_Utils_Check
::singleton()->assertValid($env->checkMysqlTime());
373 // clear permissions stub to not check permissions
374 $config->userPermissionClass
->permissions
= NULL;
376 //flush component settings
377 CRM_Core_Component
::getEnabledComponents(TRUE);
379 $_REQUEST = $_GET = $_POST = [];
380 error_reporting(E_ALL
);
382 $this->renameLabels();
383 $this->_sethtmlGlobals();
384 $this->ensureMySQLMode(['IGNORE_SPACE', 'ERROR_FOR_DIVISION_BY_ZERO', 'STRICT_TRANS_TABLES']);
388 * Read everything from the datasets directory and insert into the db.
390 public function loadAllFixtures(): void
{
391 $fixturesDir = __DIR__
. '/../../fixtures';
393 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
395 $jsonFiles = glob($fixturesDir . '/*.json');
396 foreach ($jsonFiles as $jsonFixture) {
397 $json = json_decode(file_get_contents($jsonFixture));
398 foreach ($json as $tableName => $vars) {
399 if ($tableName === 'civicrm_contact') {
400 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');
403 CRM_Core_DAO
::executeQuery("TRUNCATE $tableName");
405 foreach ($vars as $entity) {
406 $keys = $values = [];
407 foreach ($entity as $key => $value) {
409 $values[] = is_numeric($value) ?
$value : "'{$value}'";
411 CRM_Core_DAO
::executeQuery("
412 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
419 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
423 * Load the data that used to be handled by the discontinued dbunit class.
425 * This could do with further tidy up - the initial priority is simply to get rid of
426 * the dbunity package which is no longer supported.
428 * @param string $fileName
430 protected function loadXMLDataSet($fileName) {
431 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
432 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
433 foreach ($xml as $tableName => $element) {
434 if (!empty($element)) {
435 foreach ($element as $row) {
436 $keys = $values = [];
437 if (isset($row['@attributes'])) {
438 foreach ($row['@attributes'] as $key => $value) {
440 $values[] = is_numeric($value) ?
$value : "'{$value}'";
443 elseif (!empty($row)) {
444 // cos we copied it & it is inconsistent....
445 foreach ($row as $key => $value) {
447 $values[] = is_numeric($value) ?
$value : "'{$value}'";
451 if (!empty($values)) {
452 CRM_Core_DAO
::executeQuery("
453 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
459 CRM_Core_DAO
::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
463 * Create default domain contacts for the two domains added during test class.
464 * database population.
466 * @throws \CiviCRM_API3_Exception
467 * @throws \API_Exception
469 public function createDomainContacts(): void
{
470 $this->organizationCreate(['api.Email.create' => ['email' => 'fixme.domainemail@example.org']]);
471 $this->organizationCreate([
472 'organization_name' => 'Second Domain',
473 'api.Email.create' => ['email' => 'domainemail2@example.org'],
474 'api.Address.create' => [
475 'street_address' => '15 Main St',
476 'location_type_id' => 1,
477 'city' => 'Collinsville',
478 'country_id' => 1228,
479 'state_province_id' => 1003,
480 'postal_code' => 6022,
483 OptionValue
::replace(FALSE)->addWhere(
484 'option_group_id:name', '=', 'from_email_address'
487 'name' => '"FIXME" <info@EXAMPLE.ORG>',
488 'label' => '"FIXME" <info@EXAMPLE.ORG>',
489 ])->setRecords([['domain_id' => 1], ['domain_id' => 2]])->execute();
493 * Common teardown functions for all unit tests.
495 * @throws \CiviCRM_API3_Exception
496 * @throws \CRM_Core_Exception
498 protected function tearDown(): void
{
499 $this->_apiversion
= 3;
500 $this->resetLabels();
502 error_reporting(E_ALL
& ~E_NOTICE
);
503 CRM_Utils_Hook
::singleton()->reset();
504 if ($this->hookClass
) {
505 $this->hookClass
->reset();
507 CRM_Core_Session
::singleton()->reset(1);
510 $this->tx
->rollback()->commit();
513 CRM_Core_Transaction
::forceRollbackIfEnabled();
514 \Civi\Core\Transaction\Manager
::singleton(TRUE);
517 CRM_Core_Transaction
::forceRollbackIfEnabled();
518 \Civi\Core\Transaction\Manager
::singleton(TRUE);
520 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match', 'civicrm_email', 'civicrm_address'];
521 $this->quickCleanup($tablesToTruncate);
522 $this->createDomainContacts();
525 $this->cleanTempDirs();
526 $this->unsetExtensionSystem();
527 $this->assertEquals([], CRM_Core_DAO
::$_nullArray);
528 $this->assertEquals(NULL, CRM_Core_DAO
::$_nullObject);
532 * CHeck that all tests that have created payments have created them with the right financial entities.
534 * @throws \API_Exception
535 * @throws \CRM_Core_Exception
537 protected function assertPostConditions() {
538 // Reset to version 3 as not all (e.g payments) work on v4
539 $this->_apiversion
= 3;
540 if ($this->isLocationTypesOnPostAssert
) {
541 $this->assertLocationValidity();
543 $this->assertCount(1, OptionGroup
::get(FALSE)
544 ->addWhere('name', '=', 'from_email_address')
546 if (!$this->isValidateFinancialsOnPostAssert
) {
549 $this->validateAllPayments();
550 $this->validateAllContributions();
554 * Create a batch of external API calls which can
555 * be executed concurrently.
558 * $calls = $this->createExternalAPI()
559 * ->addCall('Contact', 'get', ...)
560 * ->addCall('Contact', 'get', ...)
566 * @return \Civi\API\ExternalBatch
567 * @throws PHPUnit_Framework_SkippedTestError
569 public function createExternalAPI() {
570 global $civicrm_root;
572 'version' => $this->_apiversion
,
576 $calls = new \Civi\API\
ExternalBatch($defaultParams);
578 if (!$calls->isSupported()) {
579 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
586 * Create required data based on $this->entity & $this->params
587 * This is just a way to set up the test data for delete & get functions
588 * so the distinction between set
589 * up & tested functions is clearer
594 public function createTestEntity() {
595 return $entity = $this->callAPISuccess($this->entity
, 'create', $this->params
);
599 * @param int $contactTypeId
603 public function contactTypeDelete($contactTypeId) {
604 $result = CRM_Contact_BAO_ContactType
::del($contactTypeId);
606 throw new Exception('Could not delete contact type');
611 * @param array $params
615 public function membershipTypeCreate($params = []) {
616 CRM_Member_PseudoConstant
::flush('membershipType');
617 CRM_Core_Config
::clearDBCache();
618 $this->setupIDs
['contact'] = $memberOfOrganization = $this->organizationCreate();
619 $params = array_merge([
621 'duration_unit' => 'year',
622 'duration_interval' => 1,
623 'period_type' => 'rolling',
624 'member_of_contact_id' => $memberOfOrganization,
626 'financial_type_id' => 2,
629 'visibility' => 'Public',
632 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
634 CRM_Member_PseudoConstant
::flush('membershipType');
635 CRM_Utils_Cache
::singleton()->flush();
637 return (int) $result['id'];
643 * @param array $params
646 * @throws \CRM_Core_Exception
648 public function contactMembershipCreate($params) {
649 $params = array_merge([
650 'join_date' => '2007-01-21',
651 'start_date' => '2007-01-21',
652 'end_date' => '2007-12-21',
653 'source' => 'Payment',
654 'membership_type_id' => 'General',
656 if (!is_numeric($params['membership_type_id'])) {
657 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
658 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
659 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
663 $result = $this->callAPISuccess('Membership', 'create', $params);
664 return $result['id'];
668 * Delete Membership Type.
670 * @param array $params
672 public function membershipTypeDelete($params) {
673 $this->callAPISuccess('MembershipType', 'Delete', $params);
677 * @param int $membershipID
679 public function membershipDelete($membershipID) {
680 $deleteParams = ['id' => $membershipID];
681 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
685 * @param string $name
689 * @throws \CRM_Core_Exception
691 public function membershipStatusCreate($name = 'test member status') {
692 $params['name'] = $name;
693 $params['start_event'] = 'start_date';
694 $params['end_event'] = 'end_date';
695 $params['is_current_member'] = 1;
696 $params['is_active'] = 1;
698 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
699 CRM_Member_PseudoConstant
::flush('membershipStatus');
700 return (int) $result['id'];
704 * Delete the given membership status, deleting any memberships of the status first.
706 * @param int $membershipStatusID
708 * @throws \CRM_Core_Exception
710 public function membershipStatusDelete(int $membershipStatusID) {
711 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
712 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
715 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
716 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
717 $renewedMembershipEndDate = new DateTime($membershipEndDate);
718 switch ($durationUnit) {
720 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
724 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
725 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
726 $renewedMembershipEndDate->add(new DateInterval('P1D'));
727 $renewedMembershipEndDate->add(new DateInterval('P1M'));
728 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
731 return $renewedMembershipEndDate->format('Y-m-d');
735 * Create a relationship type.
737 * @param array $params
741 * @throws \CRM_Core_Exception
743 public function relationshipTypeCreate($params = []) {
744 $params = array_merge([
745 'name_a_b' => 'Relation 1 for relationship type create',
746 'name_b_a' => 'Relation 2 for relationship type create',
747 'contact_type_a' => 'Individual',
748 'contact_type_b' => 'Organization',
753 $result = $this->callAPISuccess('relationship_type', 'create', $params);
754 CRM_Core_PseudoConstant
::flush('relationshipType');
756 return $result['id'];
760 * Delete Relatinship Type.
762 * @param int $relationshipTypeID
764 public function relationshipTypeDelete($relationshipTypeID) {
765 $params['id'] = $relationshipTypeID;
766 $check = $this->callAPISuccess('relationship_type', 'get', $params);
767 if (!empty($check['count'])) {
768 $this->callAPISuccess('relationship_type', 'delete', $params);
773 * @param array $params
776 * @throws \CRM_Core_Exception
778 public function paymentProcessorTypeCreate($params = []) {
779 $params = array_merge([
780 'name' => 'API_Test_PP',
781 'title' => 'API Test Payment Processor',
782 'class_name' => 'CRM_Core_Payment_APITest',
783 'billing_mode' => 'form',
788 $result = $this->callAPISuccess('PaymentProcessorType', 'create', $params);
790 CRM_Core_PseudoConstant
::flush('paymentProcessorType');
792 return $result['id'];
796 * Create test Authorize.net instance.
798 * @param array $params
801 * @throws \CRM_Core_Exception
803 public function paymentProcessorAuthorizeNetCreate($params = []) {
804 $params = array_merge([
805 'name' => 'Authorize',
806 'domain_id' => CRM_Core_Config
::domainID(),
807 'payment_processor_type_id' => 'AuthNet',
808 'title' => 'AuthNet',
813 'user_name' => '4y5BfuW7jm',
814 'password' => '4cAmW927n8uLf5J8',
815 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
816 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
817 'class_name' => 'Payment_AuthorizeNet',
821 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
822 return (int) $result['id'];
826 * Create Participant.
828 * @param array $params
829 * Array of contact id and event id values.
832 * $id of participant created
834 public function participantCreate($params = []) {
835 if (empty($params['contact_id'])) {
836 $params['contact_id'] = $this->individualCreate();
838 if (empty($params['event_id'])) {
839 $event = $this->eventCreate();
840 $params['event_id'] = $event['id'];
845 'register_date' => 20070219,
846 'source' => 'Wimbeldon',
847 'event_level' => 'Payment',
851 $params = array_merge($defaults, $params);
852 $result = $this->callAPISuccess('Participant', 'create', $params);
853 return $result['id'];
857 * Create Payment Processor.
860 * Id Payment Processor
862 public function processorCreate($params = []) {
866 'payment_processor_type_id' => 'Dummy',
867 'financial_account_id' => 12,
871 'url_site' => 'http://dummy.com',
872 'url_recur' => 'http://dummy.com',
875 'payment_instrument_id' => 'Debit Card',
877 $processorParams = array_merge($processorParams, $params);
878 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
879 return $processor['id'];
883 * Create Payment Processor.
885 * @param array $processorParams
887 * @return \CRM_Core_Payment_Dummy
888 * Instance of Dummy Payment Processor
890 * @throws \CiviCRM_API3_Exception
892 public function dummyProcessorCreate($processorParams = []) {
893 $paymentProcessorID = $this->processorCreate($processorParams);
894 // 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
895 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
896 $processorParams['is_test'] = FALSE;
897 $this->processorCreate($processorParams);
898 return System
::singleton()->getById($paymentProcessorID);
902 * Create contribution page.
904 * @param array $params
907 * Array of contribution page
909 public function contributionPageCreate($params = []) {
910 $this->_pageParams
= array_merge([
911 'title' => 'Test Contribution Page',
912 'financial_type_id' => 1,
914 'financial_account_id' => 1,
916 'is_allow_other_amount' => 1,
918 'max_amount' => 1000,
920 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams
);
924 * Create a sample batch.
926 public function batchCreate() {
927 $params = $this->_params
;
928 $params['name'] = $params['title'] = 'Batch_433397';
929 $params['status_id'] = 1;
930 $result = $this->callAPISuccess('batch', 'create', $params);
931 return $result['id'];
937 * @param array $params
940 * result of created tag
942 public function tagCreate($params = []) {
944 'name' => 'New Tag3',
945 'description' => 'This is description for Our New Tag ',
948 $params = array_merge($defaults, $params);
949 $result = $this->callAPISuccess('Tag', 'create', $params);
950 return $result['values'][$result['id']];
957 * Id of the tag to be deleted.
961 public function tagDelete($tagId) {
962 require_once 'api/api.php';
966 $result = $this->callAPISuccess('Tag', 'delete', $params);
967 return $result['id'];
971 * Add entity(s) to the tag
973 * @param array $params
977 public function entityTagAdd($params) {
978 $result = $this->callAPISuccess('entity_tag', 'create', $params);
985 * @param array $params
989 * id of created pledge
991 * @throws \CRM_Core_Exception
993 public function pledgeCreate($params) {
994 $params = array_merge([
995 'pledge_create_date' => date('Ymd'),
996 'start_date' => date('Ymd'),
997 'scheduled_date' => date('Ymd'),
999 'pledge_status_id' => '2',
1000 'financial_type_id' => '1',
1001 'pledge_original_installment_amount' => 20,
1002 'frequency_interval' => 5,
1003 'frequency_unit' => 'year',
1004 'frequency_day' => 15,
1005 'installments' => 5,
1009 $result = $this->callAPISuccess('Pledge', 'create', $params);
1010 return $result['id'];
1014 * Delete contribution.
1016 * @param int $pledgeId
1018 * @throws \CRM_Core_Exception
1020 public function pledgeDelete($pledgeId) {
1022 'pledge_id' => $pledgeId,
1024 $this->callAPISuccess('Pledge', 'delete', $params);
1028 * Create contribution.
1030 * @param array $params
1031 * Array of parameters.
1034 * id of created contribution
1035 * @throws \CRM_Core_Exception
1037 public function contributionCreate($params) {
1039 $params = array_merge([
1041 'receive_date' => date('Ymd'),
1042 'total_amount' => 100.00,
1043 'fee_amount' => 5.00,
1044 'financial_type_id' => 1,
1045 'payment_instrument_id' => 1,
1046 'non_deductible_amount' => 10.00,
1048 'contribution_status_id' => 1,
1051 $result = $this->callAPISuccess('contribution', 'create', $params);
1052 return $result['id'];
1056 * Delete contribution.
1058 * @param int $contributionId
1061 * @throws \CRM_Core_Exception
1063 public function contributionDelete($contributionId) {
1065 'contribution_id' => $contributionId,
1067 $result = $this->callAPISuccess('contribution', 'delete', $params);
1074 * @param array $params
1075 * Name-value pair for an event.
1078 * @throws \CRM_Core_Exception
1080 public function eventCreate($params = []) {
1081 // if no contact was passed, make up a dummy event creator
1082 if (!isset($params['contact_id'])) {
1083 $params['contact_id'] = $this->_contactCreate([
1084 'contact_type' => 'Individual',
1085 'first_name' => 'Event',
1086 'last_name' => 'Creator',
1090 // set defaults for missing params
1091 $params = array_merge([
1092 'title' => 'Annual CiviCRM meet',
1093 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1094 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1095 'event_type_id' => 1,
1097 'start_date' => 20081021,
1098 'end_date' => 20081023,
1099 'is_online_registration' => 1,
1100 'registration_start_date' => 20080601,
1101 'registration_end_date' => 20081015,
1102 'max_participants' => 100,
1103 'event_full_text' => 'Sorry! We are already full',
1106 'is_show_location' => 0,
1107 'is_email_confirm' => 1,
1110 return $this->callAPISuccess('Event', 'create', $params);
1114 * Create a paid event.
1116 * @param array $params
1118 * @param array $options
1120 * @param string $key
1121 * Index for storing event ID in ids array.
1125 * @throws \CRM_Core_Exception
1127 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1128 $params['is_monetary'] = TRUE;
1129 $event = $this->eventCreate($params);
1130 $this->ids
['Event'][$key] = (int) $event['id'];
1131 $this->ids
['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1132 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $event['id'], $this->ids
['PriceSet'][$key]);
1133 $priceSet = CRM_Price_BAO_PriceSet
::getSetDetail($this->ids
['PriceSet'][$key], TRUE, FALSE);
1134 $priceSet = $priceSet[$this->ids
['PriceSet'][$key]] ??
NULL;
1135 $this->eventFeeBlock
= $priceSet['fields'] ??
NULL;
1147 public function eventDelete($id) {
1151 return $this->callAPISuccess('event', 'delete', $params);
1155 * Delete participant.
1157 * @param int $participantID
1161 public function participantDelete($participantID) {
1163 'id' => $participantID,
1165 $check = $this->callAPISuccess('Participant', 'get', $params);
1166 if ($check['count'] > 0) {
1167 return $this->callAPISuccess('Participant', 'delete', $params);
1172 * Create participant payment.
1174 * @param int $participantID
1175 * @param int $contributionID
1178 * $id of created payment
1180 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1181 //Create Participant Payment record With Values
1183 'participant_id' => $participantID,
1184 'contribution_id' => $contributionID,
1187 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1188 return $result['id'];
1192 * Delete participant payment.
1194 * @param int $paymentID
1196 public function participantPaymentDelete($paymentID) {
1200 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1206 * @param int $contactID
1209 * location id of created location
1211 public function locationAdd($contactID) {
1214 'location_type' => 'New Location Type',
1216 'name' => 'Saint Helier St',
1217 'county' => 'Marin',
1218 'country' => 'UNITED STATES',
1219 'state_province' => 'Michigan',
1220 'supplemental_address_1' => 'Hallmark Ct',
1221 'supplemental_address_2' => 'Jersey Village',
1222 'supplemental_address_3' => 'My Town',
1227 'contact_id' => $contactID,
1228 'address' => $address,
1229 'location_format' => '2.0',
1230 'location_type' => 'New Location Type',
1233 $result = $this->callAPISuccess('Location', 'create', $params);
1238 * Delete Locations of contact.
1240 * @param array $params
1243 public function locationDelete($params) {
1244 $this->callAPISuccess('Location', 'delete', $params);
1248 * Add a Location Type.
1250 * @param array $params
1252 * @return CRM_Core_DAO_LocationType
1253 * location id of created location
1255 public function locationTypeCreate($params = NULL) {
1256 if ($params === NULL) {
1258 'name' => 'New Location Type',
1259 'vcard_name' => 'New Location Type',
1260 'description' => 'Location Type for Delete',
1265 $locationType = new CRM_Core_DAO_LocationType();
1266 $locationType->copyValues($params);
1267 $locationType->save();
1268 // clear getfields cache
1269 CRM_Core_PseudoConstant
::flush();
1270 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1271 return $locationType->id
;
1275 * Delete a Location Type.
1277 * @param int $locationTypeId
1279 public function locationTypeDelete($locationTypeId) {
1280 $locationType = new CRM_Core_DAO_LocationType();
1281 $locationType->id
= $locationTypeId;
1282 $locationType->delete();
1288 * @param array $params
1290 * @return CRM_Core_DAO_Mapping
1291 * Mapping id of created mapping
1293 public function mappingCreate($params = NULL) {
1294 if ($params === NULL) {
1296 'name' => 'Mapping name',
1297 'description' => 'Mapping description',
1298 // 'Export Contact' mapping.
1299 'mapping_type_id' => 7,
1303 $mapping = new CRM_Core_DAO_Mapping();
1304 $mapping->copyValues($params);
1306 // clear getfields cache
1307 CRM_Core_PseudoConstant
::flush();
1308 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1315 * @param int $mappingId
1317 public function mappingDelete($mappingId) {
1318 $mapping = new CRM_Core_DAO_Mapping();
1319 $mapping->id
= $mappingId;
1324 * Prepare class for ACLs.
1326 protected function prepareForACLs() {
1327 $config = CRM_Core_Config
::singleton();
1328 $config->userPermissionClass
->permissions
= [];
1334 protected function cleanUpAfterACLs() {
1335 CRM_Utils_Hook
::singleton()->reset();
1336 $tablesToTruncate = [
1338 'civicrm_acl_cache',
1339 'civicrm_acl_entity_role',
1340 'civicrm_acl_contact_cache',
1342 $this->quickCleanup($tablesToTruncate);
1343 $config = CRM_Core_Config
::singleton();
1344 unset($config->userPermissionClass
->permissions
);
1348 * Create a smart group.
1350 * By default it will be a group of households.
1352 * @param array $smartGroupParams
1353 * @param array $groupParams
1354 * @param string $contactType
1358 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1359 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1360 $savedSearch = CRM_Contact_BAO_SavedSearch
::create($smartGroupParams);
1362 $groupParams['saved_search_id'] = $savedSearch->id
;
1363 return $this->groupCreate($groupParams);
1369 * @param array $params
1371 public function uFFieldCreate($params = []) {
1372 $params = array_merge([
1374 'field_name' => 'first_name',
1377 'visibility' => 'Public Pages and Listings',
1378 'is_searchable' => '1',
1379 'label' => 'first_name',
1380 'field_type' => 'Individual',
1383 $this->callAPISuccess('uf_field', 'create', $params);
1387 * Add a UF Join Entry.
1389 * @param array $params
1392 * $id of created UF Join
1394 public function ufjoinCreate($params = NULL) {
1395 if ($params === NULL) {
1398 'module' => 'CiviEvent',
1399 'entity_table' => 'civicrm_event',
1405 $result = $this->callAPISuccess('uf_join', 'create', $params);
1410 * @param array $params
1411 * Optional parameters.
1412 * @param bool $reloadConfig
1413 * While enabling CiviCampaign component, we shouldn't always forcibly
1414 * reload config as this hinder hook call in test environment
1419 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1420 $this->enableCiviCampaign($reloadConfig);
1421 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1422 'name' => 'big_campaign',
1423 'title' => 'Campaign',
1425 return $campaign['id'];
1429 * Create Group for a contact.
1431 * @param int $contactId
1433 public function contactGroupCreate($contactId) {
1435 'contact_id.1' => $contactId,
1439 $this->callAPISuccess('GroupContact', 'Create', $params);
1443 * Delete Group for a contact.
1445 * @param int $contactId
1447 public function contactGroupDelete($contactId) {
1449 'contact_id.1' => $contactId,
1452 $this->civicrm_api('GroupContact', 'Delete', $params);
1458 * @param array $params
1462 * @throws \CRM_Core_Exception
1463 * @throws \CiviCRM_API3_Exception
1465 public function activityCreate($params = []) {
1466 $params = array_merge([
1467 'subject' => 'Discussion on warm beer',
1468 'activity_date_time' => date('Ymd'),
1470 'location' => 'Baker Street',
1471 'details' => 'Lets schedule a meeting',
1473 'activity_type_id' => 'Meeting',
1475 if (!isset($params['source_contact_id'])) {
1476 $params['source_contact_id'] = $this->individualCreate();
1478 if (!isset($params['target_contact_id'])) {
1479 $params['target_contact_id'] = $this->individualCreate([
1480 'first_name' => 'Julia',
1481 'last_name' => 'Anderson',
1483 'email' => 'julia_anderson@civicrm.org',
1484 'contact_type' => 'Individual',
1487 if (!isset($params['assignee_contact_id'])) {
1488 $params['assignee_contact_id'] = $params['target_contact_id'];
1491 $result = civicrm_api3('Activity', 'create', $params);
1493 $result['target_contact_id'] = $params['target_contact_id'];
1494 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1499 * Create an activity type.
1501 * @param array $params
1506 public function activityTypeCreate($params) {
1507 return $this->callAPISuccess('ActivityType', 'create', $params);
1511 * Delete activity type.
1513 * @param int $activityTypeId
1514 * Id of the activity type.
1518 public function activityTypeDelete($activityTypeId) {
1519 $params['activity_type_id'] = $activityTypeId;
1520 return $this->callAPISuccess('ActivityType', 'delete', $params);
1524 * Create custom group.
1526 * @param array $params
1530 public function customGroupCreate($params = []) {
1532 'title' => 'new custom group',
1533 'extends' => 'Contact',
1535 'style' => 'Inline',
1539 $params = array_merge($defaults, $params);
1541 return $this->callAPISuccess('custom_group', 'create', $params);
1545 * Existing function doesn't allow params to be over-ridden so need a new one
1546 * this one allows you to only pass in the params you want to change
1548 * @param array $params
1552 public function CustomGroupCreateByParams($params = []) {
1554 'title' => "API Custom Group",
1555 'extends' => 'Contact',
1557 'style' => 'Inline',
1560 $params = array_merge($defaults, $params);
1561 return $this->callAPISuccess('custom_group', 'create', $params);
1565 * Create custom group with multi fields.
1567 * @param array $params
1571 public function CustomGroupMultipleCreateByParams($params = []) {
1576 $params = array_merge($defaults, $params);
1577 return $this->CustomGroupCreateByParams($params);
1581 * Create custom group with multi fields.
1583 * @param array $params
1587 public function CustomGroupMultipleCreateWithFields($params = []) {
1588 // also need to pass on $params['custom_field'] if not set but not in place yet
1590 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1591 $ids['custom_group_id'] = $customGroup['id'];
1593 $customField = $this->customFieldCreate([
1594 'custom_group_id' => $ids['custom_group_id'],
1595 'label' => 'field_1' . $ids['custom_group_id'],
1599 $ids['custom_field_id'][] = $customField['id'];
1601 $customField = $this->customFieldCreate([
1602 'custom_group_id' => $ids['custom_group_id'],
1603 'default_value' => '',
1604 'label' => 'field_2' . $ids['custom_group_id'],
1607 $ids['custom_field_id'][] = $customField['id'];
1609 $customField = $this->customFieldCreate([
1610 'custom_group_id' => $ids['custom_group_id'],
1611 'default_value' => '',
1612 'label' => 'field_3' . $ids['custom_group_id'],
1615 $ids['custom_field_id'][] = $customField['id'];
1621 * Create a custom group with a single text custom field. See
1622 * participant:testCreateWithCustom for how to use this
1624 * @param string $function
1626 * @param string $filename
1630 * ids of created objects
1632 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1633 $params = ['title' => $function];
1634 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1635 $params['extends'] = $entity ?
$entity : 'Contact';
1636 $customGroup = $this->customGroupCreate($params);
1637 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1638 CRM_Core_PseudoConstant
::flush();
1640 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1644 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1645 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1647 * @param string $function
1649 * @param string $filename
1653 * ids of created objects
1655 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1656 $params = ['title' => $function];
1657 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1658 $params['extends'] = $entity ?
$entity : 'Contact';
1659 $customGroup = $this->customGroupCreate($params);
1660 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1661 CRM_Core_PseudoConstant
::flush();
1663 'defaultValue' => 'Default Value',
1664 'lowercasevalue' => 'Lowercase Value',
1665 1 => 'Integer Value',
1668 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1669 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1670 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1671 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1672 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1673 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1674 foreach ($options as $option_value => $option_label) {
1675 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1676 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1680 'custom_group_id' => $customGroup['id'],
1681 'custom_field_id' => $customField['id'],
1682 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1683 'custom_field_group_options' => $options,
1688 * Delete custom group.
1690 * @param int $customGroupID
1694 public function customGroupDelete($customGroupID) {
1695 $params['id'] = $customGroupID;
1696 return $this->callAPISuccess('custom_group', 'delete', $params);
1700 * Create custom field.
1702 * @param array $params
1703 * (custom_group_id) is required.
1707 public function customFieldCreate($params) {
1708 $params = array_merge([
1709 'label' => 'Custom Field',
1710 'data_type' => 'String',
1711 'html_type' => 'Text',
1712 'is_searchable' => 1,
1714 'default_value' => 'defaultValue',
1717 $result = $this->callAPISuccess('custom_field', 'create', $params);
1718 // these 2 functions are called with force to flush static caches
1719 CRM_Core_BAO_CustomField
::getTableColumnGroup($result['id'], 1);
1720 CRM_Core_Component
::getEnabledComponents(1);
1725 * Delete custom field.
1727 * @param int $customFieldID
1731 public function customFieldDelete($customFieldID) {
1733 $params['id'] = $customFieldID;
1734 return $this->callAPISuccess('custom_field', 'delete', $params);
1744 public function noteCreate($cId) {
1746 'entity_table' => 'civicrm_contact',
1747 'entity_id' => $cId,
1748 'note' => 'hello I am testing Note',
1749 'contact_id' => $cId,
1750 'modified_date' => date('Ymd'),
1751 'subject' => 'Test Note',
1754 return $this->callAPISuccess('Note', 'create', $params);
1758 * Enable CiviCampaign Component.
1760 * @param bool $reloadConfig
1761 * Force relaod config or not
1763 public function enableCiviCampaign($reloadConfig = TRUE) {
1764 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCampaign');
1765 if ($reloadConfig) {
1766 // force reload of config object
1767 $config = CRM_Core_Config
::singleton(TRUE, TRUE);
1769 //flush cache by calling with reset
1770 $activityTypes = CRM_Core_PseudoConstant
::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1774 * Create custom field with Option Values.
1776 * @param array $customGroup
1777 * @param string $name
1778 * Name of custom field.
1779 * @param array $extraParams
1780 * Additional parameters to pass through.
1784 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1786 'custom_group_id' => $customGroup['id'],
1787 'name' => 'test_custom_group',
1788 'label' => 'Country',
1789 'html_type' => 'Select',
1790 'data_type' => 'String',
1793 'is_searchable' => 0,
1799 'name' => 'option_group1',
1800 'label' => 'option_group_label1',
1804 'option_label' => ['Label1', 'Label2'],
1805 'option_value' => ['value1', 'value2'],
1806 'option_name' => [$name . '_1', $name . '_2'],
1807 'option_weight' => [1, 2],
1808 'option_status' => [1, 1],
1811 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1813 return $this->callAPISuccess('custom_field', 'create', $params);
1821 public function confirmEntitiesDeleted($entities) {
1822 foreach ($entities as $entity) {
1824 $result = $this->callAPISuccess($entity, 'Get', []);
1825 if ($result['error'] == 1 ||
$result['count'] > 0) {
1826 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1834 * Quick clean by emptying tables created for the test.
1836 * @param array $tablesToTruncate
1837 * @param bool $dropCustomValueTables
1839 * @throws \CRM_Core_Exception
1841 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1843 throw new \
CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1845 if ($dropCustomValueTables) {
1846 $optionGroupResult = CRM_Core_DAO
::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1847 while ($optionGroupResult->fetch()) {
1848 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1849 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1850 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1851 // generic cleanup routine.
1852 if (!empty($optionGroupResult->option_group_id
) && $optionGroupResult->option_group_id
> 50) {
1853 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id
);
1856 $tablesToTruncate[] = 'civicrm_custom_group';
1857 $tablesToTruncate[] = 'civicrm_custom_field';
1860 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate
, $tablesToTruncate));
1862 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1863 foreach ($tablesToTruncate as $table) {
1864 $sql = "TRUNCATE TABLE $table";
1865 CRM_Core_DAO
::executeQuery($sql);
1867 CRM_Core_DAO
::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1869 if ($dropCustomValueTables) {
1870 $dbName = self
::getDBName();
1872 SELECT TABLE_NAME as tableName
1873 FROM INFORMATION_SCHEMA.TABLES
1874 WHERE TABLE_SCHEMA = '{$dbName}'
1875 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1878 $tableDAO = CRM_Core_DAO
::executeQuery($query);
1879 while ($tableDAO->fetch()) {
1880 $sql = "DROP TABLE {$tableDAO->tableName}";
1881 CRM_Core_DAO
::executeQuery($sql);
1887 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1889 * @throws \CRM_Core_Exception
1891 public function quickCleanUpFinancialEntities() {
1892 $tablesToTruncate = [
1894 'civicrm_activity_contact',
1895 'civicrm_contribution',
1896 'civicrm_contribution_soft',
1897 'civicrm_contribution_product',
1898 'civicrm_financial_trxn',
1899 'civicrm_financial_item',
1900 'civicrm_contribution_recur',
1901 'civicrm_line_item',
1902 'civicrm_contribution_page',
1903 'civicrm_payment_processor',
1904 'civicrm_entity_financial_trxn',
1905 'civicrm_membership',
1906 'civicrm_membership_type',
1907 'civicrm_membership_payment',
1908 'civicrm_membership_log',
1909 'civicrm_membership_block',
1911 'civicrm_participant',
1912 'civicrm_participant_payment',
1914 'civicrm_pcp_block',
1916 'civicrm_pledge_block',
1917 'civicrm_pledge_payment',
1918 'civicrm_price_set_entity',
1919 'civicrm_price_field_value',
1920 'civicrm_price_field',
1922 $this->quickCleanup($tablesToTruncate);
1923 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1924 $this->restoreDefaultPriceSetConfig();
1925 $this->disableTaxAndInvoicing();
1926 $this->setCurrencySeparators(',');
1927 CRM_Core_PseudoConstant
::flush('taxRates');
1928 System
::singleton()->flushProcessors();
1929 // @fixme this parameter is leaking - it should not be defined as a class static
1930 // but for now we just handle in tear down.
1931 CRM_Contribute_BAO_Query
::$_contribOrSoftCredit = 'only contribs';
1935 * Reset the price set config so results exist.
1937 public function restoreDefaultPriceSetConfig() {
1938 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1939 CRM_Core_DAO
::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1940 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)");
1941 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)");
1945 * Recreate default membership types.
1947 public function restoreMembershipTypes() {
1948 CRM_Core_DAO
::executeQuery(
1949 "REPLACE INTO civicrm_membership_type
1950 (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)
1952 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1953 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1954 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1959 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1960 * Default behaviour is to also delete the entity
1961 * @param array $params
1962 * Params array to check against.
1964 * Id of the entity concerned.
1965 * @param string $entity
1966 * Name of entity concerned (e.g. membership).
1967 * @param bool $delete
1968 * Should the entity be deleted as part of this check.
1969 * @param string $errorText
1970 * Text to print on error.
1974 * @param array $params
1977 * @param int $delete
1978 * @param string $errorText
1980 * @throws CRM_Core_Exception
1982 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1984 $result = $this->callAPISuccessGetSingle($entity, [
1989 $this->callAPISuccess($entity, 'Delete', [
1993 $dateFields = $keys = $dateTimeFields = [];
1994 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1995 foreach ($fields['values'] as $field => $settings) {
1996 if (array_key_exists($field, $result)) {
1997 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = $field;
2000 $keys[CRM_Utils_Array
::value('name', $settings, $field)] = CRM_Utils_Array
::value('name', $settings, $field);
2002 $type = $settings['type'] ??
NULL;
2003 if ($type == CRM_Utils_Type
::T_DATE
) {
2004 $dateFields[] = $settings['name'];
2005 // we should identify both real names & unique names as dates
2006 if ($field != $settings['name']) {
2007 $dateFields[] = $field;
2010 if ($type == CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
) {
2011 $dateTimeFields[] = $settings['name'];
2012 // we should identify both real names & unique names as dates
2013 if ($field != $settings['name']) {
2014 $dateTimeFields[] = $field;
2019 if (strtolower($entity) == 'contribution') {
2020 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
2021 // this is not returned in id format
2022 unset($params['payment_instrument_id']);
2023 $params['contribution_source'] = $params['source'];
2024 unset($params['source']);
2027 foreach ($params as $key => $value) {
2028 if ($key == 'version' ||
substr($key, 0, 3) == 'api' ||
!array_key_exists($keys[$key], $result)) {
2031 if (in_array($key, $dateFields)) {
2032 $value = date('Y-m-d', strtotime($value));
2033 $result[$key] = date('Y-m-d', strtotime($result[$key]));
2035 if (in_array($key, $dateTimeFields)) {
2036 $value = date('Y-m-d H:i:s', strtotime($value));
2037 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array
::value($keys[$key], $result, CRM_Utils_Array
::value($key, $result))));
2039 $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);
2044 * Get formatted values in the actual and expected result.
2046 * @param array $actual
2047 * Actual calculated values.
2048 * @param array $expected
2051 public function checkArrayEquals(&$actual, &$expected) {
2052 self
::unsetId($actual);
2053 self
::unsetId($expected);
2054 $this->assertEquals($expected, $actual);
2058 * Unset the key 'id' from the array
2060 * @param array $unformattedArray
2061 * The array from which the 'id' has to be unset.
2063 public static function unsetId(&$unformattedArray) {
2064 $formattedArray = [];
2065 if (array_key_exists('id', $unformattedArray)) {
2066 unset($unformattedArray['id']);
2068 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
2069 foreach ($unformattedArray['values'] as $key => $value) {
2070 if (is_array($value)) {
2071 foreach ($value as $k => $v) {
2077 elseif ($key == 'id') {
2078 $unformattedArray[$key];
2080 $formattedArray = [$value];
2082 $unformattedArray['values'] = $formattedArray;
2087 * Helper to enable/disable custom directory support
2089 * @param array $customDirs
2091 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2092 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2094 public function customDirectories($customDirs) {
2095 $config = CRM_Core_Config
::singleton();
2097 if (empty($customDirs['php_path']) ||
$customDirs['php_path'] === FALSE) {
2098 unset($config->customPHPPathDir
);
2100 elseif ($customDirs['php_path'] === TRUE) {
2101 $config->customPHPPathDir
= dirname(dirname(__FILE__
)) . '/custom_directories/php/';
2104 $config->customPHPPathDir
= $php_path;
2107 if (empty($customDirs['template_path']) ||
$customDirs['template_path'] === FALSE) {
2108 unset($config->customTemplateDir
);
2110 elseif ($customDirs['template_path'] === TRUE) {
2111 $config->customTemplateDir
= dirname(dirname(__FILE__
)) . '/custom_directories/templates/';
2114 $config->customTemplateDir
= $template_path;
2119 * Generate a temporary folder.
2121 * @param string $prefix
2125 public function createTempDir($prefix = 'test-') {
2126 $tempDir = CRM_Utils_File
::tempdir($prefix);
2127 $this->tempDirs
[] = $tempDir;
2131 public function cleanTempDirs() {
2132 if (!is_array($this->tempDirs
)) {
2133 // fix test errors where this is not set
2136 foreach ($this->tempDirs
as $tempDir) {
2137 if (is_dir($tempDir)) {
2138 CRM_Utils_File
::cleanDir($tempDir, TRUE, FALSE);
2144 * Temporarily replace the singleton extension with a different one.
2146 * @param \CRM_Extension_System $system
2148 public function setExtensionSystem(CRM_Extension_System
$system) {
2149 if ($this->origExtensionSystem
== NULL) {
2150 $this->origExtensionSystem
= CRM_Extension_System
::singleton();
2152 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2155 public function unsetExtensionSystem() {
2156 if ($this->origExtensionSystem
!== NULL) {
2157 CRM_Extension_System
::setSingleton($this->origExtensionSystem
);
2158 $this->origExtensionSystem
= NULL;
2163 * Temporarily alter the settings-metadata to add a mock setting.
2165 * WARNING: The setting metadata will disappear on the next cache-clear.
2171 public function setMockSettingsMetaData($extras) {
2172 CRM_Utils_Hook
::singleton()
2173 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2174 $metadata = array_merge($metadata, $extras);
2177 Civi
::service('settings_manager')->flush();
2179 $fields = $this->callAPISuccess('setting', 'getfields', []);
2180 foreach ($extras as $key => $spec) {
2181 $this->assertNotEmpty($spec['title']);
2182 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2187 * @param string $name
2189 public function financialAccountDelete($name) {
2190 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2191 $financialAccount->name
= $name;
2192 if ($financialAccount->find(TRUE)) {
2193 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2194 $entityFinancialType->financial_account_id
= $financialAccount->id
;
2195 $entityFinancialType->delete();
2196 $financialAccount->delete();
2201 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2202 * (NB unclear if this is still required)
2204 public function _sethtmlGlobals() {
2205 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2207 'html_quickform_rule_required',
2208 'HTML/QuickForm/Rule/Required.php',
2211 'html_quickform_rule_range',
2212 'HTML/QuickForm/Rule/Range.php',
2215 'html_quickform_rule_range',
2216 'HTML/QuickForm/Rule/Range.php',
2219 'html_quickform_rule_range',
2220 'HTML/QuickForm/Rule/Range.php',
2223 'html_quickform_rule_email',
2224 'HTML/QuickForm/Rule/Email.php',
2227 'html_quickform_rule_regex',
2228 'HTML/QuickForm/Rule/Regex.php',
2231 'html_quickform_rule_regex',
2232 'HTML/QuickForm/Rule/Regex.php',
2235 'html_quickform_rule_regex',
2236 'HTML/QuickForm/Rule/Regex.php',
2239 'html_quickform_rule_regex',
2240 'HTML/QuickForm/Rule/Regex.php',
2242 'nopunctuation' => [
2243 'html_quickform_rule_regex',
2244 'HTML/QuickForm/Rule/Regex.php',
2247 'html_quickform_rule_regex',
2248 'HTML/QuickForm/Rule/Regex.php',
2251 'html_quickform_rule_callback',
2252 'HTML/QuickForm/Rule/Callback.php',
2255 'html_quickform_rule_compare',
2256 'HTML/QuickForm/Rule/Compare.php',
2259 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2260 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2262 'HTML/QuickForm/group.php',
2263 'HTML_QuickForm_group',
2266 'HTML/QuickForm/hidden.php',
2267 'HTML_QuickForm_hidden',
2270 'HTML/QuickForm/reset.php',
2271 'HTML_QuickForm_reset',
2274 'HTML/QuickForm/checkbox.php',
2275 'HTML_QuickForm_checkbox',
2278 'HTML/QuickForm/file.php',
2279 'HTML_QuickForm_file',
2282 'HTML/QuickForm/image.php',
2283 'HTML_QuickForm_image',
2286 'HTML/QuickForm/password.php',
2287 'HTML_QuickForm_password',
2290 'HTML/QuickForm/radio.php',
2291 'HTML_QuickForm_radio',
2294 'HTML/QuickForm/button.php',
2295 'HTML_QuickForm_button',
2298 'HTML/QuickForm/submit.php',
2299 'HTML_QuickForm_submit',
2302 'HTML/QuickForm/select.php',
2303 'HTML_QuickForm_select',
2306 'HTML/QuickForm/hiddenselect.php',
2307 'HTML_QuickForm_hiddenselect',
2310 'HTML/QuickForm/text.php',
2311 'HTML_QuickForm_text',
2314 'HTML/QuickForm/textarea.php',
2315 'HTML_QuickForm_textarea',
2318 'HTML/QuickForm/fckeditor.php',
2319 'HTML_QuickForm_FCKEditor',
2322 'HTML/QuickForm/tinymce.php',
2323 'HTML_QuickForm_TinyMCE',
2326 'HTML/QuickForm/dojoeditor.php',
2327 'HTML_QuickForm_dojoeditor',
2330 'HTML/QuickForm/link.php',
2331 'HTML_QuickForm_link',
2334 'HTML/QuickForm/advcheckbox.php',
2335 'HTML_QuickForm_advcheckbox',
2338 'HTML/QuickForm/date.php',
2339 'HTML_QuickForm_date',
2342 'HTML/QuickForm/static.php',
2343 'HTML_QuickForm_static',
2346 'HTML/QuickForm/header.php',
2347 'HTML_QuickForm_header',
2350 'HTML/QuickForm/html.php',
2351 'HTML_QuickForm_html',
2354 'HTML/QuickForm/hierselect.php',
2355 'HTML_QuickForm_hierselect',
2358 'HTML/QuickForm/autocomplete.php',
2359 'HTML_QuickForm_autocomplete',
2362 'HTML/QuickForm/xbutton.php',
2363 'HTML_QuickForm_xbutton',
2365 'advmultiselect' => [
2366 'HTML/QuickForm/advmultiselect.php',
2367 'HTML_QuickForm_advmultiselect',
2373 * Set up an acl allowing contact to see 2 specified groups
2374 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2376 * You need to have pre-created these groups & created the user e.g
2377 * $this->createLoggedInUser();
2378 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2379 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2381 * @param bool $isProfile
2383 public function setupACL($isProfile = FALSE) {
2385 $_REQUEST = $this->_params
;
2387 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
2388 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2389 $ov = new CRM_Core_DAO_OptionValue();
2390 $ov->option_group_id
= $optionGroupID;
2392 if ($ov->find(TRUE)) {
2393 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2395 $optionValue = $this->callAPISuccess('option_value', 'create', [
2396 'option_group_id' => $optionGroupID,
2397 'label' => 'pick me',
2401 CRM_Core_DAO
::executeQuery("
2402 TRUNCATE civicrm_acl_cache
2405 CRM_Core_DAO
::executeQuery("
2406 TRUNCATE civicrm_acl_contact_cache
2409 CRM_Core_DAO
::executeQuery("
2410 INSERT INTO civicrm_acl_entity_role (
2411 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2412 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2416 CRM_Core_DAO
::executeQuery("
2417 INSERT INTO civicrm_acl (
2418 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2421 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2426 CRM_Core_DAO
::executeQuery("
2427 INSERT INTO civicrm_acl (
2428 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2431 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2435 CRM_Core_DAO
::executeQuery("
2436 INSERT INTO civicrm_acl (
2437 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2440 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2445 $this->_loggedInUser
= CRM_Core_Session
::singleton()->get('userID');
2446 $this->callAPISuccess('group_contact', 'create', [
2447 'group_id' => $this->_permissionedGroup
,
2448 'contact_id' => $this->_loggedInUser
,
2453 CRM_ACL_BAO_Cache
::resetCache();
2454 CRM_ACL_API
::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2459 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2461 public function offsetDefaultPriceSet() {
2462 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2463 $firstID = $contributionPriceSet['id'];
2464 $this->callAPISuccess('price_set', 'create', [
2465 'id' => $contributionPriceSet['id'],
2469 unset($contributionPriceSet['id']);
2470 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2471 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2472 'price_set_id' => $firstID,
2473 'options' => ['limit' => 1],
2475 unset($priceField['id']);
2476 $priceField['price_set_id'] = $newPriceSet['id'];
2477 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2478 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2479 'price_set_id' => $firstID,
2481 'options' => ['limit' => 1],
2484 unset($priceFieldValue['id']);
2485 //create some padding to use up ids
2486 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2487 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2488 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2492 * Create an instance of the paypal processor.
2494 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2495 * this parent class & we don't have a structure for that yet
2496 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2497 * & the best protection against that is the functions this class affords
2499 * @param array $params
2501 * @return int $result['id'] payment processor id
2503 public function paymentProcessorCreate($params = []) {
2504 $params = array_merge([
2506 'domain_id' => CRM_Core_Config
::domainID(),
2507 'payment_processor_type_id' => 'PayPal',
2511 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2512 'password' => '1183377788',
2513 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2514 'url_site' => 'https://www.sandbox.paypal.com/',
2515 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2516 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2517 'class_name' => 'Payment_PayPalImpl',
2518 'billing_mode' => 3,
2519 'financial_type_id' => 1,
2520 'financial_account_id' => 12,
2521 // Credit card = 1 so can pass 'by accident'.
2522 'payment_instrument_id' => 'Debit Card',
2524 if (!is_numeric($params['payment_processor_type_id'])) {
2525 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2527 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2528 'name' => $params['payment_processor_type_id'],
2532 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2533 return $result['id'];
2537 * Set up initial recurring payment allowing subsequent IPN payments.
2539 * @param array $recurParams (Optional)
2540 * @param array $contributionParams (Optional)
2542 * @throws \CRM_Core_Exception
2544 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2545 $this->ids
['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
2546 $contributionParams = array_merge([
2547 'total_amount' => '200',
2548 'invoice_id' => $this->_invoiceID
,
2549 'financial_type_id' => 'Donation',
2550 'contribution_status_id' => 'Pending',
2551 'contact_id' => $this->_contactID
,
2552 'contribution_page_id' => $this->_contributionPageID
,
2553 'payment_processor_id' => $this->_paymentProcessorID
,
2555 'receive_date' => '2019-07-25 07:34:23',
2556 'skipCleanMoney' => TRUE,
2557 'amount_level' => 'expensive',
2558 'campaign_id' => $this->ids
['campaign'][0],
2559 'source' => 'Online Contribution: Page name',
2560 ], $contributionParams);
2561 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2562 'contact_id' => $this->_contactID
,
2565 'installments' => 5,
2566 'frequency_unit' => 'Month',
2567 'frequency_interval' => 1,
2568 'invoice_id' => $this->_invoiceID
,
2569 'contribution_status_id' => 2,
2570 'payment_processor_id' => $this->_paymentProcessorID
,
2571 // processor provided ID - use contact ID as proxy.
2572 'processor_id' => $this->_contactID
,
2573 'api.Order.create' => $contributionParams,
2574 ], $recurParams))['values'][0];
2575 $this->_contributionRecurID
= $contributionRecur['id'];
2576 $this->_contributionID
= $contributionRecur['api.Order.create']['id'];
2577 $this->ids
['Contribution'][0] = $this->_contributionID
;
2581 * 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
2583 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2585 * @throws \CRM_Core_Exception
2587 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2588 $membershipParams = $recurParams = [];
2589 if (!empty($params['duration_unit'])) {
2590 $membershipParams['duration_unit'] = $params['duration_unit'];
2592 if (!empty($params['frequency_unit'])) {
2593 $recurParams['frequency_unit'] = $params['frequency_unit'];
2596 $this->ids
['membership_type'] = $this->membershipTypeCreate($membershipParams);
2597 //create a contribution so our membership & contribution don't both have id = 1
2598 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2599 $this->contributionCreate([
2600 'contact_id' => $this->_contactID
,
2602 'financial_type_id' => 1,
2603 'invoice_id' => 'abcd',
2605 'receive_date' => '2019-07-25 07:34:23',
2609 $this->ids
['membership'] = $this->callAPISuccess('Membership', 'create', [
2610 'contact_id' => $this->_contactID
,
2611 'membership_type_id' => $this->ids
['membership_type'],
2612 'format.only_id' => TRUE,
2613 'source' => 'Payment',
2614 'skipLineItem' => TRUE,
2616 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2621 'entity_table' => 'civicrm_membership',
2622 'entity_id' => $this->ids
['membership'],
2623 'label' => 'General',
2625 'unit_price' => 200,
2626 'line_total' => 200,
2627 'financial_type_id' => 1,
2628 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2630 'label' => 'Membership Amount',
2631 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2633 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2635 'label' => 'General',
2636 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2643 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids
['membership'], 'contribution_recur_id' => $this->_contributionRecurID
]);
2651 public function CiviUnitTestCase_fatalErrorHandler($message) {
2652 throw new Exception("{$message['message']}: {$message['code']}");
2656 * Wrap the entire test case in a transaction.
2658 * Only subsequent DB statements will be wrapped in TX -- this cannot
2659 * retroactively wrap old DB statements. Therefore, it makes sense to
2660 * call this at the beginning of setUp().
2662 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2663 * this option does not work with, e.g., custom-data.
2665 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2666 * if TRUNCATE or ALTER is called while using a transaction.
2669 * Whether to use nesting or reference-counting.
2671 public function useTransaction($nest = TRUE) {
2673 $this->tx
= new CRM_Core_Transaction($nest);
2674 $this->tx
->rollback();
2679 * Assert the attachment exists.
2681 * @param bool $exists
2682 * @param array $apiResult
2684 protected function assertAttachmentExistence($exists, $apiResult) {
2685 $fileId = $apiResult['id'];
2686 $this->assertTrue(is_numeric($fileId));
2687 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2688 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2689 1 => [$fileId, 'Int'],
2691 $this->assertDBQuery($exists ?
1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2692 1 => [$fileId, 'Int'],
2697 * Assert 2 sql strings are the same, ignoring double spaces.
2699 * @param string $expectedSQL
2700 * @param string $actualSQL
2701 * @param string $message
2703 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2704 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2705 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2706 $this->assertEquals($expected, $actual, $message);
2710 * Create a price set for an event.
2712 * @param int $feeTotal
2713 * @param int $minAmt
2714 * @param string $type
2716 * @param array $options
2720 * @throws \CRM_Core_Exception
2722 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2723 // creating price set, price field
2724 $paramsSet['title'] = 'Price Set';
2725 $paramsSet['name'] = CRM_Utils_String
::titleToVar('Price Set');
2726 $paramsSet['is_active'] = FALSE;
2727 $paramsSet['extends'] = 1;
2728 $paramsSet['min_amount'] = $minAmt;
2730 $priceSet = CRM_Price_BAO_PriceSet
::create($paramsSet);
2731 $this->_ids
['price_set'] = $priceSet->id
;
2734 'label' => 'Price Field',
2735 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2736 'html_type' => $type,
2737 'price' => $feeTotal,
2738 'option_label' => ['1' => 'Price Field'],
2739 'option_value' => ['1' => $feeTotal],
2740 'option_name' => ['1' => $feeTotal],
2741 'option_weight' => ['1' => 1],
2742 'option_amount' => ['1' => 1],
2743 'is_display_amounts' => 1,
2745 'options_per_line' => 1,
2746 'is_active' => ['1' => 1],
2747 'price_set_id' => $this->_ids
['price_set'],
2748 'is_enter_qty' => 1,
2749 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2751 if ($type === 'Radio') {
2752 foreach ($options as $index => $option) {
2753 $paramsField['is_enter_qty'] = 0;
2754 $optionID = $index +
2;
2755 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2756 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2760 $this->callAPISuccess('PriceField', 'create', $paramsField);
2761 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids
['price_set']]);
2762 $this->_ids
['price_field'] = array_keys($fields['values']);
2763 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids
['price_field'][0]]);
2764 $this->_ids
['price_field_value'] = array_keys($fieldValues['values']);
2766 return $this->_ids
['price_set'];
2770 * Add a profile to a contribution page.
2772 * @param string $name
2773 * @param int $contributionPageID
2774 * @param string $module
2776 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2778 'uf_group_id' => $name,
2779 'module' => $module,
2780 'entity_table' => 'civicrm_contribution_page',
2781 'entity_id' => $contributionPageID,
2784 if ($module !== 'CiviContribute') {
2785 $params['module_data'] = [$module => []];
2787 $this->callAPISuccess('UFJoin', 'create', $params);
2791 * Add participant with contribution
2795 * @throws \CRM_Core_Exception
2797 protected function createPartiallyPaidParticipantOrder() {
2798 $orderParams = $this->getParticipantOrderParams();
2799 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2800 return $this->callAPISuccess('Order', 'create', $orderParams);
2806 * @param string $component
2807 * @param int $componentId
2808 * @param array $priceFieldOptions
2812 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2813 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2814 $paramsSet['name'] = CRM_Utils_String
::titleToVar($paramsSet['title']);
2815 $paramsSet['is_active'] = TRUE;
2816 $paramsSet['financial_type_id'] = 'Event Fee';
2817 $paramsSet['extends'] = 1;
2818 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2819 $priceSetId = $priceSet['id'];
2820 //Checking for priceset added in the table.
2821 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2822 'id', $paramsSet['title'], 'Check DB for created priceset'
2824 $paramsField = array_merge([
2825 'label' => 'Price Field',
2826 'name' => CRM_Utils_String
::titleToVar('Price Field'),
2827 'html_type' => 'CheckBox',
2828 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2829 'option_value' => ['1' => 100, '2' => 200],
2830 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2831 'option_weight' => ['1' => 1, '2' => 2],
2832 'option_amount' => ['1' => 100, '2' => 200],
2833 'is_display_amounts' => 1,
2835 'options_per_line' => 1,
2836 'is_active' => ['1' => 1, '2' => 1],
2837 'price_set_id' => $priceSet['id'],
2838 'is_enter_qty' => 1,
2839 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2840 ], $priceFieldOptions);
2842 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
2844 CRM_Price_BAO_PriceSet
::addTo('civicrm_' . $component, $componentId, $priceSetId);
2846 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
2850 * Replace the template with a test-oriented template designed to show all the variables.
2852 * @param string $templateName
2853 * @param string $type
2855 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2856 $testTemplate = file_get_contents(__DIR__
. '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2857 CRM_Core_DAO
::executeQuery(
2858 "UPDATE civicrm_msg_template
2859 SET msg_{$type} = %1
2860 WHERE workflow_name = '{$templateName}'
2861 AND is_default = 1", [1 => [$testTemplate, 'String']]
2866 * Reinstate the default template.
2868 * @param string $templateName
2869 * @param string $type
2871 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2872 CRM_Core_DAO
::executeQuery(
2873 "UPDATE civicrm_option_group og
2874 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2875 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2876 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2877 SET m.msg_{$type} = m2.msg_{$type}
2878 WHERE og.name = 'msg_tpl_workflow_contribution'
2879 AND ov.name = '{$templateName}'
2880 AND m.is_default = 1"
2885 * Flush statics relating to financial type.
2887 protected function flushFinancialTypeStatics() {
2888 if (isset(\Civi
::$statics['CRM_Financial_BAO_FinancialType'])) {
2889 unset(\Civi
::$statics['CRM_Financial_BAO_FinancialType']);
2891 if (isset(\Civi
::$statics['CRM_Contribute_PseudoConstant'])) {
2892 unset(\Civi
::$statics['CRM_Contribute_PseudoConstant']);
2894 CRM_Contribute_PseudoConstant
::flush('financialType');
2895 CRM_Contribute_PseudoConstant
::flush('membershipType');
2896 // Pseudoconstants may be saved to the cache table.
2897 CRM_Core_DAO
::executeQuery("TRUNCATE civicrm_cache");
2898 CRM_Financial_BAO_FinancialType
::$_statusACLFt = [];
2899 CRM_Financial_BAO_FinancialType
::$_availableFinancialTypes = NULL;
2903 * Set the permissions to the supplied array.
2905 * @param array $permissions
2907 protected function setPermissions($permissions) {
2908 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= $permissions;
2909 $this->flushFinancialTypeStatics();
2913 * @param array $params
2916 public function _checkFinancialRecords($params, $context) {
2918 'entity_id' => $params['id'],
2919 'entity_table' => 'civicrm_contribution',
2921 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2922 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2923 if ($context == 'pending') {
2924 $trxn = CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams);
2925 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2928 $trxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2930 'id' => $trxn['financial_trxn_id'],
2932 if ($context != 'online' && $context != 'payLater') {
2934 'to_financial_account_id' => 6,
2935 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2939 if ($context == 'feeAmount') {
2940 $compareParams['fee_amount'] = 50;
2942 elseif ($context == 'online') {
2944 'to_financial_account_id' => 12,
2945 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2947 'payment_instrument_id' => CRM_Utils_Array
::value('payment_instrument_id', $params, 1),
2950 elseif ($context == 'payLater') {
2952 'to_financial_account_id' => 7,
2953 'total_amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2957 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2959 'financial_trxn_id' => $trxn['financial_trxn_id'],
2960 'entity_table' => 'civicrm_financial_item',
2962 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
2964 'id' => $entityTrxn['entity_id'],
2967 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2969 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2971 if ($context == 'payLater') {
2973 'amount' => (float) CRM_Utils_Array
::value('total_amount', $params, 100.00),
2975 'financial_account_id' => CRM_Utils_Array
::value('financial_account_id', $params, 1),
2978 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2979 if ($context == 'feeAmount') {
2981 'entity_id' => $params['id'],
2982 'entity_table' => 'civicrm_contribution',
2984 $maxTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($maxParams, TRUE));
2986 'id' => $maxTrxn['financial_trxn_id'],
2989 'to_financial_account_id' => 5,
2990 'from_financial_account_id' => 6,
2991 'total_amount' => 50,
2994 $trxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($params['id'], 'DESC');
2995 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2997 'entity_id' => $trxnId['financialTrxnId'],
2998 'entity_table' => 'civicrm_financial_trxn',
3003 'financial_account_id' => 5,
3005 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
3007 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
3008 // line should be copied into all the functions that call this function & evaluated there
3009 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
3010 // when calling completeTransaction or repeatTransaction.
3011 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
3015 * Return financial type id on basis of name
3017 * @param string $name Financial type m/c name
3021 public function getFinancialTypeId($name) {
3022 return CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
3026 * Cleanup function for contents of $this->ids.
3028 * This is a best effort cleanup to use in tear downs etc.
3030 * It will not fail if the data has already been removed (some tests may do
3031 * their own cleanup).
3033 protected function cleanUpSetUpIDs() {
3034 foreach ($this->setupIDs
as $entity => $id) {
3036 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
3038 catch (CiviCRM_API3_Exception
$e) {
3039 // This is a best-effort cleanup function, ignore.
3045 * Create Financial Type.
3047 * @param array $params
3051 protected function createFinancialType($params = []) {
3052 $params = array_merge($params,
3054 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
3058 return $this->callAPISuccess('FinancialType', 'create', $params);
3062 * Create Payment Instrument.
3064 * @param array $params
3065 * @param string $financialAccountName
3069 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3070 $params = array_merge([
3071 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3072 'option_group_id' => 'payment_instrument',
3075 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3077 $relationTypeID = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3079 $financialAccountParams = [
3080 'entity_table' => 'civicrm_option_value',
3081 'entity_id' => $newPaymentInstrument,
3082 'account_relationship' => $relationTypeID,
3083 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3085 CRM_Financial_BAO_FinancialTypeAccount
::add($financialAccountParams);
3087 return CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3091 * Enable Tax and Invoicing
3093 * @param array $params
3095 * @return \Civi\Core\SettingsBag
3097 protected function enableTaxAndInvoicing($params = []) {
3098 // Enable component contribute setting
3099 $contributeSetting = array_merge($params,
3102 'invoice_prefix' => 'INV_',
3104 'due_date_period' => 'days',
3106 'is_email_pdf' => 1,
3107 'tax_term' => 'Sales Tax',
3108 'tax_display_settings' => 'Inclusive',
3111 return Civi
::settings()->set('contribution_invoice_settings', $contributeSetting);
3115 * Enable Tax and Invoicing
3117 * @throws \CRM_Core_Exception
3119 protected function disableTaxAndInvoicing() {
3120 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3121 foreach ($accounts as $account) {
3122 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3123 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3126 if (!empty(\Civi
::$statics['CRM_Core_PseudoConstant']) && isset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3127 unset(\Civi
::$statics['CRM_Core_PseudoConstant']['taxRates']);
3129 return Civi
::settings()->set('invoicing', FALSE);
3133 * Add Sales Tax Account for the financial type.
3135 * @param int $financialTypeId
3137 * @param array $accountParams
3139 * @return CRM_Financial_DAO_EntityFinancialAccount
3140 * @throws \CRM_Core_Exception
3142 protected function addTaxAccountToFinancialType(int $financialTypeId, $accountParams = []) {
3143 $params = array_merge([
3144 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3145 'financial_account_type_id' => key(CRM_Core_PseudoConstant
::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3146 'is_deductible' => 1,
3151 $account = CRM_Financial_BAO_FinancialAccount
::add($params);
3153 'entity_table' => 'civicrm_financial_type',
3154 'entity_id' => $financialTypeId,
3155 'account_relationship' => key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3158 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3159 \Civi
::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = $params['tax_rate'];
3161 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3162 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3163 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3164 $dao->copyValues($entityParams);
3166 if ($dao->fetch()) {
3167 $entityParams['id'] = $dao->id
;
3169 $entityParams['financial_account_id'] = $account->id
;
3171 return CRM_Financial_BAO_FinancialTypeAccount
::add($entityParams);
3175 * Create price set with contribution test for test setup.
3177 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3178 * on parent class at some point (fn is not in 4.4).
3181 * @param array $params
3183 public function createPriceSetWithPage($entity = NULL, $params = []) {
3184 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3185 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3186 'title' => "Test Contribution Page",
3187 'financial_type_id' => 1,
3188 'currency' => 'NZD',
3189 'goal_amount' => 50,
3190 'is_pay_later' => 1,
3191 'is_monetary' => TRUE,
3192 'is_email_receipt' => FALSE,
3194 $priceSet = $this->callAPISuccess('price_set', 'create', [
3195 'is_quick_config' => 0,
3196 'extends' => 'CiviMember',
3197 'financial_type_id' => 1,
3198 'title' => 'my Page',
3200 $priceSetID = $priceSet['id'];
3202 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3203 $priceField = $this->callAPISuccess('price_field', 'create', [
3204 'price_set_id' => $priceSetID,
3205 'label' => 'Goat Breed',
3206 'html_type' => 'Radio',
3208 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3209 'price_set_id' => $priceSetID,
3210 'price_field_id' => $priceField['id'],
3211 'label' => 'Long Haired Goat',
3213 'financial_type_id' => 'Donation',
3214 'membership_type_id' => $membershipTypeID,
3215 'membership_num_terms' => 1,
3217 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
3218 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3219 'price_set_id' => $priceSetID,
3220 'price_field_id' => $priceField['id'],
3221 'label' => 'Shoe-eating Goat',
3223 'financial_type_id' => 'Donation',
3224 'membership_type_id' => $membershipTypeID,
3225 'membership_num_terms' => 2,
3227 $this->_ids
['price_field_value'][] = $priceFieldValue['id'];
3229 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3230 'price_set_id' => $priceSetID,
3231 'price_field_id' => $priceField['id'],
3232 'label' => 'Shoe-eating Goat',
3234 'financial_type_id' => 'Donation',
3236 $this->_ids
['price_field_value']['cont'] = $priceFieldValue['id'];
3238 $this->_ids
['price_set'] = $priceSetID;
3239 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
3240 $this->_ids
['price_field'] = [$priceField['id']];
3242 $this->_ids
['membership_type'] = $membershipTypeID;
3246 * Only specified contact returned.
3248 * @implements CRM_Utils_Hook::aclWhereClause
3252 * @param $whereTables
3256 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3257 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts
) . ")";
3261 * @implements CRM_Utils_Hook::selectWhereClause
3263 * @param string $entity
3264 * @param array $clauses
3266 public function selectWhereClauseHook($entity, &$clauses) {
3267 if ($entity == 'Event') {
3268 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3273 * An implementation of hook_civicrm_post used with all our test cases.
3276 * @param string $objectName
3277 * @param int $objectId
3280 public function onPost($op, $objectName, $objectId, &$objectRef) {
3281 if ($op == 'create' && $objectName == 'Individual') {
3282 CRM_Core_DAO
::executeQuery(
3283 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3285 1 => [$objectId, 'Integer'],
3290 if ($op == 'edit' && $objectName == 'Participant') {
3292 1 => [$objectId, 'Integer'],
3294 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3295 CRM_Core_DAO
::executeQuery($query, $params);
3300 * Instantiate form object.
3302 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3304 * @param string $class
3305 * Name of form class.
3307 * @param array $formValues
3309 * @param string $pageName
3311 * @return \CRM_Core_Form
3312 * @throws \CRM_Core_Exception
3314 public function getFormObject($class, $formValues = [], $pageName = '') {
3315 $_POST = $formValues;
3316 /* @var CRM_Core_Form $form */
3317 $form = new $class();
3318 $_SERVER['REQUEST_METHOD'] = 'GET';
3320 case 'CRM_Event_Cart_Form_Checkout_Payment':
3321 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3322 $form->controller
= new CRM_Event_Cart_Controller_Checkout();
3326 $form->controller
= new CRM_Core_Controller();
3329 $pageName = $form->getName();
3331 $form->controller
->setStateMachine(new CRM_Core_StateMachine($form->controller
));
3332 $_SESSION['_' . $form->controller
->_name
. '_container']['values'][$pageName] = $formValues;
3337 * Get possible thousand separators.
3341 public function getThousandSeparators() {
3342 return [['.'], [',']];
3346 * Get the boolean options as a provider.
3350 public function getBooleanDataProvider() {
3351 return [[TRUE], [FALSE]];
3355 * Set the separators for thousands and decimal points.
3357 * Note that this only covers some common scenarios.
3359 * It does not cater for a situation where the thousand separator is a [space]
3360 * Latter is the Norwegian localization. At least some tests need to
3361 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3362 * to provide broader coverage.
3364 * @param string $thousandSeparator
3366 protected function setCurrencySeparators($thousandSeparator) {
3367 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3368 Civi
::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ?
'.' : ','));
3372 * Sets the thousand separator.
3374 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3376 * @param $thousandSeparator
3378 protected function setMonetaryThousandSeparator($thousandSeparator) {
3379 Civi
::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3383 * Sets the decimal separator.
3385 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3387 * @param $decimalPoint
3389 protected function setMonetaryDecimalPoint($decimalPoint) {
3390 Civi
::settings()->set('monetaryDecimalPoint', $decimalPoint);
3394 * Sets the default currency.
3398 protected function setDefaultCurrency($currency) {
3399 Civi
::settings()->set('defaultCurrency', $currency);
3403 * Format money as it would be input.
3405 * @param string $amount
3409 protected function formatMoneyInput($amount) {
3410 return CRM_Utils_Money
::format($amount, NULL, '%a');
3414 * Get the contribution object.
3416 * @param int $contributionID
3418 * @return \CRM_Contribute_BAO_Contribution
3420 protected function getContributionObject($contributionID) {
3421 $contributionObj = new CRM_Contribute_BAO_Contribution();
3422 $contributionObj->id
= $contributionID;
3423 $contributionObj->find(TRUE);
3424 return $contributionObj;
3428 * Enable multilingual.
3430 public function enableMultilingual() {
3431 $this->callAPISuccess('Setting', 'create', [
3432 'lcMessages' => 'en_US',
3433 'languageLimit' => [
3438 CRM_Core_I18n_Schema
::makeMultilingual('en_US');
3441 $dbLocale = '_en_US';
3445 * Setup or clean up SMS tests
3447 * @param bool $teardown
3449 * @throws \CiviCRM_API3_Exception
3451 public function setupForSmsTests($teardown = FALSE) {
3452 require_once 'CiviTest/CiviTestSMSProvider.php';
3454 // Option value params for CiviTestSMSProvider
3455 $groupID = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3457 'option_group_id' => $groupID,
3458 'label' => 'unittestSMS',
3459 'value' => 'unit.test.sms',
3460 'name' => 'CiviTestSMSProvider',
3467 // Test completed, delete provider
3468 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3469 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3473 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3474 // in CRM/Sms/Provider.php even though it is not an extension.
3475 return civicrm_api3('option_value', 'create', $params);
3479 * Start capturing browser output.
3481 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3483 protected function startCapturingOutput() {
3485 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3489 * Stop capturing browser output and return as a csv.
3491 * @param bool $isFirstRowHeaders
3493 * @return \League\Csv\Reader
3495 * @throws \League\Csv\Exception
3497 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3498 $output = ob_get_flush();
3499 $stream = fopen('php://memory', 'r+');
3500 fwrite($stream, $output);
3502 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3503 $csv = Reader
::createFromString($output);
3504 if ($isFirstRowHeaders) {
3505 $csv->setHeaderOffset(0);
3512 * Rename various labels to not match the names.
3514 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3515 * code that expects it to.
3517 protected function renameLabels() {
3518 $replacements = ['Pending', 'Refunded'];
3519 foreach ($replacements as $name) {
3520 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3525 * Undo any label renaming.
3527 protected function resetLabels() {
3528 CRM_Core_DAO
::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3532 * Get parameters to set up a multi-line participant order.
3535 * @throws \CRM_Core_Exception
3537 protected function getParticipantOrderParams(): array {
3538 $this->_contactId
= $this->individualCreate();
3539 $event = $this->eventCreate();
3540 $this->_eventId
= $event['id'];
3542 'id' => $this->_eventId
,
3543 'financial_type_id' => 4,
3546 $this->callAPISuccess('event', 'create', $eventParams);
3547 $priceFields = $this->createPriceSet('event', $this->_eventId
);
3548 $participantParams = [
3549 'financial_type_id' => 4,
3550 'event_id' => $this->_eventId
,
3553 'fee_currency' => 'USD',
3554 'contact_id' => $this->_contactId
,
3556 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3558 'total_amount' => 300,
3559 'currency' => 'USD',
3560 'contact_id' => $this->_contactId
,
3561 'financial_type_id' => 4,
3562 'contribution_status_id' => 'Pending',
3563 'contribution_mode' => 'participant',
3564 'participant_id' => $participant['id'],
3566 foreach ($priceFields['values'] as $key => $priceField) {
3567 $orderParams['line_items'][] = [
3570 'price_field_id' => $priceField['price_field_id'],
3571 'price_field_value_id' => $priceField['id'],
3572 'label' => $priceField['label'],
3573 'field_title' => $priceField['label'],
3575 'unit_price' => $priceField['amount'],
3576 'line_total' => $priceField['amount'],
3577 'financial_type_id' => $priceField['financial_type_id'],
3578 'entity_table' => 'civicrm_participant',
3581 'params' => $participant,
3584 return $orderParams;
3590 * @throws \CRM_Core_Exception
3592 protected function validatePayments($payments) {
3593 foreach ($payments as $payment) {
3594 $balance = CRM_Contribute_BAO_Contribution
::getContributionBalance($payment['contribution_id']);
3595 if ($balance < 0 && $balance +
$payment['total_amount'] === 0.0) {
3596 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3597 // This is a pretty rough way at guessing which payment is the overpayment - but
3598 // for the test suite it should be enough.
3601 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3602 'financial_trxn_id' => $payment['id'],
3603 'entity_table' => 'civicrm_financial_item',
3604 'return' => ['amount'],
3607 foreach ($items as $item) {
3608 $itemTotal +
= $item['amount'];
3610 $this->assertEquals($payment['total_amount'], $itemTotal);
3615 * Validate all created payments.
3617 * @throws \CRM_Core_Exception
3619 protected function validateAllPayments() {
3620 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3621 $this->validatePayments($payments);
3625 * Validate all created contributions.
3627 * @throws \CRM_Core_Exception
3629 protected function validateAllContributions() {
3630 $contributions = $this->callAPISuccess('Contribution', 'get', ['return' => ['tax_amount', 'total_amount']])['values'];
3631 foreach ($contributions as $contribution) {
3632 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3635 foreach ($lineItems as $lineItem) {
3636 $total +
= $lineItem['line_total'];
3637 $taxTotal +
= (float) ($lineItem['tax_amount'] ??
0);
3639 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ??
0));
3640 $this->assertEquals($total, $contribution['total_amount']);
3646 * @throws \CRM_Core_Exception
3648 protected function createRuleGroup() {
3649 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3650 'contact_type' => 'Individual',
3652 'used' => 'General',
3653 'name' => 'TestRule',
3654 'title' => 'TestRule',
3661 * Generic create test.
3663 * @param int $version
3665 * @throws \CRM_Core_Exception
3667 protected function basicCreateTest(int $version) {
3668 $this->_apiversion
= $version;
3669 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
3670 $this->assertEquals(1, $result['count']);
3671 $this->assertNotNull($result['values'][$result['id']]['id']);
3672 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
3676 * Generic delete test.
3678 * @param int $version
3680 * @throws \CRM_Core_Exception
3682 protected function basicDeleteTest($version) {
3683 $this->_apiversion
= $version;
3684 $result = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
3685 $deleteParams = ['id' => $result['id']];
3686 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
3687 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
3688 $this->assertEquals(0, $checkDeleted['count']);
3692 * Create and return a case object for the given Client ID.
3694 * @param int $clientId
3695 * @param int $loggedInUser
3696 * Omit or pass NULL to use the same as clientId
3697 * @param array $extra
3698 * Optional specific parameters such as start_date
3700 * @return CRM_Case_BAO_Case
3702 public function createCase($clientId, $loggedInUser = NULL, $extra = []) {
3703 if (empty($loggedInUser)) {
3704 // backwards compatibility - but it's more typical that the creator is a different person than the client
3705 $loggedInUser = $clientId;
3707 $caseParams = array_merge([
3708 'activity_subject' => 'Case Subject',
3709 'client_id' => $clientId,
3710 'case_type_id' => 1,
3712 'case_type' => 'housing_support',
3713 'subject' => 'Case Subject',
3714 'start_date' => date("Y-m-d"),
3715 'start_date_time' => date("YmdHis"),
3717 'activity_details' => '',
3719 $form = new CRM_Case_Form_Case();
3720 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3724 * Validate that all location entities have exactly one primary.
3726 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3728 public function assertLocationValidity() {
3729 $this->assertEquals(0, CRM_Core_DAO
::singleValueQuery('SELECT COUNT(*) FROM
3731 (SELECT a1.contact_id
3732 FROM civicrm_address a1
3733 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3734 AND a1.contact_id = a2.contact_id
3737 AND a2.id IS NOT NULL
3738 AND a1.contact_id IS NOT NULL
3740 SELECT a1.contact_id
3741 FROM civicrm_address a1
3742 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3743 AND a1.contact_id = a2.contact_id
3744 WHERE a1.is_primary = 0
3746 AND a1.contact_id IS NOT NULL
3750 SELECT a1.contact_id
3751 FROM civicrm_email a1
3752 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3753 AND a1.contact_id = a2.contact_id
3756 AND a2.id IS NOT NULL
3757 AND a1.contact_id IS NOT NULL
3759 SELECT a1.contact_id
3760 FROM civicrm_email a1
3761 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3762 AND a1.contact_id = a2.contact_id
3763 WHERE a1.is_primary = 0
3765 AND a1.contact_id IS NOT NULL
3769 SELECT a1.contact_id
3770 FROM civicrm_phone a1
3771 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3772 AND a1.contact_id = a2.contact_id
3775 AND a2.id IS NOT NULL
3776 AND a1.contact_id IS NOT NULL
3778 SELECT a1.contact_id
3779 FROM civicrm_phone a1
3780 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3781 AND a1.contact_id = a2.contact_id
3782 WHERE a1.is_primary = 0
3784 AND a1.contact_id IS NOT NULL
3788 SELECT a1.contact_id
3790 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3791 AND a1.contact_id = a2.contact_id
3794 AND a2.id IS NOT NULL
3795 AND a1.contact_id IS NOT NULL
3797 SELECT a1.contact_id
3799 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3800 AND a1.contact_id = a2.contact_id
3801 WHERE a1.is_primary = 0
3803 AND a1.contact_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
3811 WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3814 SELECT a1.contact_id
3815 FROM civicrm_openid a1
3816 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3817 AND a1.contact_id = a2.contact_id
3820 AND a2.id IS NOT NULL
3821 AND a1.contact_id IS NOT NULL
3823 SELECT a1.contact_id
3824 FROM civicrm_openid a1
3825 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3826 AND a1.contact_id = a2.contact_id
3827 WHERE a1.is_primary = 0
3829 AND a1.contact_id IS NOT NULL) as primary_descrepancies
3834 * Ensure the specified mysql mode/s are activated.
3836 * @param array $modes
3838 protected function ensureMySQLMode(array $modes): void
{
3839 $currentModes = array_fill_keys(CRM_Utils_SQL
::getSqlModes(), 1);
3840 $currentModes = array_merge($currentModes, array_fill_keys($modes, 1));
3841 CRM_Core_DAO
::executeQuery("SET GLOBAL sql_mode = '" . implode(',', array_keys($currentModes)) . "'");
3842 CRM_Core_DAO
::executeQuery("SET sql_mode = '" . implode(',', array_keys($currentModes)) . "'");