7b8859e597e9539206b696967fe8b30cbc8f59a1
[civicrm-core.git] / tests / phpunit / CiviTest / CiviUnitTestCase.php
1 <?php
2 /**
3 * File for the CiviUnitTestCase class
4 *
5 * (PHP 5)
6 *
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
10 * @package CiviCRM
11 *
12 * This file is part of CiviCRM
13 *
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.
18 *
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.
23 *
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/>.
27 */
28
29 use Civi\Payment\System;
30 use League\Csv\Reader;
31
32 /**
33 * Include class definitions
34 */
35 require_once 'api/api.php';
36 define('API_LATEST_VERSION', 3);
37
38 /**
39 * Base class for CiviCRM unit tests
40 *
41 * This class supports two (mutually-exclusive) techniques for cleaning up test data. Subclasses
42 * may opt for one or neither:
43 *
44 * 1. quickCleanup() is a helper which truncates a series of tables. Call quickCleanup()
45 * as part of setUp() and/or tearDown(). quickCleanup() is thorough - but it can
46 * be cumbersome to use (b/c you must identify the tables to cleanup) and slow to execute.
47 * 2. useTransaction() executes the test inside a transaction. It's easier to use
48 * (because you don't need to identify specific tables), but it doesn't work for tests
49 * which manipulate schema or truncate data -- and could behave inconsistently
50 * for tests which specifically examine DB transactions.
51 *
52 * Common functions for unit tests
53 *
54 * @package CiviCRM
55 */
56 class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
57
58 use \Civi\Test\Api3DocTrait;
59 use \Civi\Test\GenericAssertionsTrait;
60 use \Civi\Test\DbTestTrait;
61 use \Civi\Test\ContactTestTrait;
62 use \Civi\Test\MailingTestTrait;
63
64 /**
65 * Database has been initialized.
66 *
67 * @var bool
68 */
69 private static $dbInit = FALSE;
70
71 /**
72 * Database connection.
73 *
74 * @var PHPUnit_Extensions_Database_DB_IDatabaseConnection
75 */
76 protected $_dbconn;
77
78 /**
79 * The database name.
80 *
81 * @var string
82 */
83 static protected $_dbName;
84
85 /**
86 * Track tables we have modified during a test.
87 *
88 * @var array
89 */
90 protected $_tablesToTruncate = [];
91
92 /**
93 * @var array
94 * Array of temporary directory names
95 */
96 protected $tempDirs;
97
98 /**
99 * @var bool
100 * populateOnce allows to skip db resets in setUp
101 *
102 * WARNING! USE WITH CAUTION - IT'LL RENDER DATA DEPENDENCIES
103 * BETWEEN TESTS WHEN RUN IN SUITE. SUITABLE FOR LOCAL, LIMITED
104 * "CHECK RUNS" ONLY!
105 *
106 * IF POSSIBLE, USE $this->DBResetRequired = FALSE IN YOUR TEST CASE!
107 *
108 * @see http://forum.civicrm.org/index.php/topic,18065.0.html
109 */
110 public static $populateOnce = FALSE;
111
112 /**
113 * DBResetRequired allows skipping DB reset
114 * in specific test case. If you still need
115 * to reset single test (method) of such case, call
116 * $this->cleanDB() in the first line of this
117 * test (method).
118 * @var bool
119 */
120 public $DBResetRequired = TRUE;
121
122 /**
123 * @var CRM_Core_Transaction|null
124 */
125 private $tx = NULL;
126
127 /**
128 * Array of IDs created to support the test.
129 *
130 * e.g
131 * $this->ids = ['Contact' => ['descriptive_key' => $contactID], 'Group' => [$groupID]];
132 *
133 * @var array
134 */
135 protected $ids = [];
136
137 /**
138 * Should financials be checked after the test but before tear down.
139 *
140 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
141 * are some test data issues and some real bugs currently blockinng.
142 *
143 * @var bool
144 */
145 protected $isValidateFinancialsOnPostAssert = FALSE;
146
147 /**
148 * Should location types be checked to ensure primary addresses are correctly assigned after each test.
149 *
150 * @var bool
151 */
152 protected $isLocationTypesOnPostAssert = TRUE;
153
154 /**
155 * Class used for hooks during tests.
156 *
157 * This can be used to test hooks within tests. For example in the ACL_PermissionTrait:
158 *
159 * $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
160 *
161 * @var \CRM_Utils_Hook_UnitTests
162 */
163 public $hookClass = NULL;
164
165 /**
166 * @var array
167 * Common values to be re-used multiple times within a class - usually to create the relevant entity
168 */
169 protected $_params = [];
170
171 /**
172 * @var CRM_Extension_System
173 */
174 protected $origExtensionSystem;
175
176 /**
177 * Array of IDs created during test setup routine.
178 *
179 * The cleanUpSetUpIds method can be used to clear these at the end of the test.
180 *
181 * @var array
182 */
183 public $setupIDs = [];
184
185 /**
186 * Constructor.
187 *
188 * Because we are overriding the parent class constructor, we
189 * need to show the same arguments as exist in the constructor of
190 * PHPUnit_Framework_TestCase, since
191 * PHPUnit_Framework_TestSuite::createTest() creates a
192 * ReflectionClass of the Test class and checks the constructor
193 * of that class to decide how to set up the test.
194 *
195 * @param string $name
196 * @param array $data
197 * @param string $dataName
198 */
199 public function __construct($name = NULL, array $data = [], $dataName = '') {
200 parent::__construct($name, $data, $dataName);
201
202 // we need full error reporting
203 error_reporting(E_ALL & ~E_NOTICE);
204
205 self::$_dbName = self::getDBName();
206
207 // also load the class loader
208 require_once 'CRM/Core/ClassLoader.php';
209 CRM_Core_ClassLoader::singleton()->register();
210 if (function_exists('_civix_phpunit_setUp')) {
211 // FIXME: loosen coupling
212 _civix_phpunit_setUp();
213 }
214 }
215
216 /**
217 * Override to run the test and assert its state.
218 *
219 * @return mixed
220 * @throws \Exception
221 * @throws \PHPUnit_Framework_IncompleteTest
222 * @throws \PHPUnit_Framework_SkippedTest
223 */
224 protected function runTest() {
225 try {
226 return parent::runTest();
227 }
228 catch (PEAR_Exception $e) {
229 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
230 throw new Exception(\CRM_Core_Error::formatTextException($e), $e->getCode());
231 }
232 }
233
234 /**
235 * @return bool
236 */
237 public function requireDBReset() {
238 return $this->DBResetRequired;
239 }
240
241 /**
242 * @return string
243 */
244 public static function getDBName() {
245 static $dbName = NULL;
246 if ($dbName === NULL) {
247 require_once "DB.php";
248 $dsn = CRM_Utils_SQL::autoSwitchDSN(CIVICRM_DSN);
249 $dsninfo = DB::parseDSN($dsn);
250 $dbName = $dsninfo['database'];
251 }
252 return $dbName;
253 }
254
255 /**
256 * Create database connection for this instance.
257 *
258 * Initialize the test database if it hasn't been initialized
259 *
260 */
261 protected function getConnection() {
262 if (!self::$dbInit) {
263 $dbName = self::getDBName();
264
265 // install test database
266 echo PHP_EOL . "Installing {$dbName} database" . PHP_EOL;
267
268 static::_populateDB(FALSE, $this);
269
270 self::$dbInit = TRUE;
271 }
272
273 }
274
275 /**
276 * Required implementation of abstract method.
277 */
278 protected function getDataSet() {
279 }
280
281 /**
282 * @param bool $perClass
283 * @param null $object
284 *
285 * @return bool
286 * TRUE if the populate logic runs; FALSE if it is skipped
287 */
288 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
289 if (CIVICRM_UF !== 'UnitTests') {
290 throw new \RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
291 }
292
293 if ($perClass || $object == NULL) {
294 $dbreset = TRUE;
295 }
296 else {
297 $dbreset = $object->requireDBReset();
298 }
299
300 if (self::$populateOnce || !$dbreset) {
301 return FALSE;
302 }
303 self::$populateOnce = NULL;
304
305 Civi\Test::data()->populate();
306
307 return TRUE;
308 }
309
310 public static function setUpBeforeClass() {
311 static::_populateDB(TRUE);
312
313 // also set this global hack
314 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
315 }
316
317 /**
318 * Common setup functions for all unit tests.
319 */
320 protected function setUp() {
321 $session = CRM_Core_Session::singleton();
322 $session->set('userID', NULL);
323
324 $this->_apiversion = 3;
325
326 // REVERT
327 $this->errorScope = CRM_Core_TemporaryErrorScope::useException();
328 // Use a temporary file for STDIN
329 $GLOBALS['stdin'] = tmpfile();
330 if ($GLOBALS['stdin'] === FALSE) {
331 echo "Couldn't open temporary file\n";
332 exit(1);
333 }
334
335 // Get and save a connection to the database
336 $this->_dbconn = $this->getConnection();
337
338 // reload database before each test
339 // $this->_populateDB();
340
341 // "initialize" CiviCRM to avoid problems when running single tests
342 // FIXME: look at it closer in second stage
343
344 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
345 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
346
347 // disable any left-over test extensions
348 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
349
350 // reset all the caches
351 CRM_Utils_System::flushCache();
352
353 // initialize the object once db is loaded
354 \Civi::$statics = [];
355 // ugh, performance
356 $config = CRM_Core_Config::singleton(TRUE, TRUE);
357
358 // when running unit tests, use mockup user framework
359 $this->hookClass = CRM_Utils_Hook::singleton();
360
361 // Make sure the DB connection is setup properly
362 $config->userSystem->setMySQLTimeZone();
363 $env = new CRM_Utils_Check_Component_Env();
364 CRM_Utils_Check::singleton()->assertValid($env->checkMysqlTime());
365
366 // clear permissions stub to not check permissions
367 $config->userPermissionClass->permissions = NULL;
368
369 //flush component settings
370 CRM_Core_Component::getEnabledComponents(TRUE);
371
372 $_REQUEST = $_GET = $_POST = [];
373 error_reporting(E_ALL);
374
375 $this->renameLabels();
376 $this->_sethtmlGlobals();
377 }
378
379 /**
380 * Read everything from the datasets directory and insert into the db.
381 */
382 public function loadAllFixtures() {
383 $fixturesDir = __DIR__ . '/../../fixtures';
384
385 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
386
387 $jsonFiles = glob($fixturesDir . '/*.json');
388 foreach ($jsonFiles as $jsonFixture) {
389 $json = json_decode(file_get_contents($jsonFixture));
390 foreach ($json as $tableName => $vars) {
391 if ($tableName === 'civicrm_contact') {
392 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');
393 }
394 else {
395 CRM_Core_DAO::executeQuery("TRUNCATE $tableName");
396 }
397 foreach ($vars as $entity) {
398 $keys = $values = [];
399 foreach ($entity as $key => $value) {
400 $keys[] = $key;
401 $values[] = is_numeric($value) ? $value : "'{$value}'";
402 }
403 CRM_Core_DAO::executeQuery("
404 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
405 );
406 }
407
408 }
409 }
410
411 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
412 }
413
414 /**
415 * Load the data that used to be handled by the discontinued dbunit class.
416 *
417 * This could do with further tidy up - the initial priority is simply to get rid of
418 * the dbunity package which is no longer supported.
419 *
420 * @param string $fileName
421 */
422 protected function loadXMLDataSet($fileName) {
423 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
424 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
425 foreach ($xml as $tableName => $element) {
426 if (!empty($element)) {
427 foreach ($element as $row) {
428 $keys = $values = [];
429 if (isset($row['@attributes'])) {
430 foreach ($row['@attributes'] as $key => $value) {
431 $keys[] = $key;
432 $values[] = is_numeric($value) ? $value : "'{$value}'";
433 }
434 }
435 elseif (!empty($row)) {
436 // cos we copied it & it is inconsistent....
437 foreach ($row as $key => $value) {
438 $keys[] = $key;
439 $values[] = is_numeric($value) ? $value : "'{$value}'";
440 }
441 }
442
443 if (!empty($values)) {
444 CRM_Core_DAO::executeQuery("
445 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
446 );
447 }
448 }
449 }
450 }
451 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
452 }
453
454 /**
455 * Create default domain contacts for the two domains added during test class.
456 * database population.
457 */
458 public function createDomainContacts() {
459 $this->organizationCreate();
460 $this->organizationCreate(['organization_name' => 'Second Domain']);
461 }
462
463 /**
464 * Common teardown functions for all unit tests.
465 */
466 protected function tearDown() {
467 $this->_apiversion = 3;
468 $this->resetLabels();
469
470 error_reporting(E_ALL & ~E_NOTICE);
471 CRM_Utils_Hook::singleton()->reset();
472 if ($this->hookClass) {
473 $this->hookClass->reset();
474 }
475 CRM_Core_Session::singleton()->reset(1);
476
477 if ($this->tx) {
478 $this->tx->rollback()->commit();
479 $this->tx = NULL;
480
481 CRM_Core_Transaction::forceRollbackIfEnabled();
482 \Civi\Core\Transaction\Manager::singleton(TRUE);
483 }
484 else {
485 CRM_Core_Transaction::forceRollbackIfEnabled();
486 \Civi\Core\Transaction\Manager::singleton(TRUE);
487
488 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match'];
489 $this->quickCleanup($tablesToTruncate);
490 $this->createDomainContacts();
491 }
492
493 $this->cleanTempDirs();
494 $this->unsetExtensionSystem();
495 $this->assertEquals([], CRM_Core_DAO::$_nullArray);
496 $this->assertEquals(NULL, CRM_Core_DAO::$_nullObject);
497 }
498
499 /**
500 * CHeck that all tests that have created payments have created them with the right financial entities.
501 *
502 * @throws \CRM_Core_Exception
503 */
504 protected function assertPostConditions() {
505 if ($this->isLocationTypesOnPostAssert) {
506 $this->assertLocationValidity();
507 }
508 if (!$this->isValidateFinancialsOnPostAssert) {
509 return;
510 }
511 $this->validateAllPayments();
512 $this->validateAllContributions();
513 }
514
515 /**
516 * Create a batch of external API calls which can
517 * be executed concurrently.
518 *
519 * ```
520 * $calls = $this->createExternalAPI()
521 * ->addCall('Contact', 'get', ...)
522 * ->addCall('Contact', 'get', ...)
523 * ...
524 * ->run()
525 * ->getResults();
526 * ```
527 *
528 * @return \Civi\API\ExternalBatch
529 * @throws PHPUnit_Framework_SkippedTestError
530 */
531 public function createExternalAPI() {
532 global $civicrm_root;
533 $defaultParams = [
534 'version' => $this->_apiversion,
535 'debug' => 1,
536 ];
537
538 $calls = new \Civi\API\ExternalBatch($defaultParams);
539
540 if (!$calls->isSupported()) {
541 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
542 }
543
544 return $calls;
545 }
546
547 /**
548 * Create required data based on $this->entity & $this->params
549 * This is just a way to set up the test data for delete & get functions
550 * so the distinction between set
551 * up & tested functions is clearer
552 *
553 * @return array
554 * api Result
555 */
556 public function createTestEntity() {
557 return $entity = $this->callAPISuccess($this->entity, 'create', $this->params);
558 }
559
560 /**
561 * @param int $contactTypeId
562 *
563 * @throws Exception
564 */
565 public function contactTypeDelete($contactTypeId) {
566 $result = CRM_Contact_BAO_ContactType::del($contactTypeId);
567 if (!$result) {
568 throw new Exception('Could not delete contact type');
569 }
570 }
571
572 /**
573 * @param array $params
574 *
575 * @return mixed
576 */
577 public function membershipTypeCreate($params = []) {
578 CRM_Member_PseudoConstant::flush('membershipType');
579 CRM_Core_Config::clearDBCache();
580 $this->setupIDs['contact'] = $memberOfOrganization = $this->organizationCreate();
581 $params = array_merge([
582 'name' => 'General',
583 'duration_unit' => 'year',
584 'duration_interval' => 1,
585 'period_type' => 'rolling',
586 'member_of_contact_id' => $memberOfOrganization,
587 'domain_id' => 1,
588 'financial_type_id' => 2,
589 'is_active' => 1,
590 'sequential' => 1,
591 'visibility' => 'Public',
592 ], $params);
593
594 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
595
596 CRM_Member_PseudoConstant::flush('membershipType');
597 CRM_Utils_Cache::singleton()->flush();
598
599 return $result['id'];
600 }
601
602 /**
603 * Create membership.
604 *
605 * @param array $params
606 *
607 * @return int
608 * @throws \CRM_Core_Exception
609 */
610 public function contactMembershipCreate($params) {
611 $params = array_merge([
612 'join_date' => '2007-01-21',
613 'start_date' => '2007-01-21',
614 'end_date' => '2007-12-21',
615 'source' => 'Payment',
616 'membership_type_id' => 'General',
617 ], $params);
618 if (!is_numeric($params['membership_type_id'])) {
619 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
620 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
621 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
622 }
623 }
624
625 $result = $this->callAPISuccess('Membership', 'create', $params);
626 return $result['id'];
627 }
628
629 /**
630 * Delete Membership Type.
631 *
632 * @param array $params
633 */
634 public function membershipTypeDelete($params) {
635 $this->callAPISuccess('MembershipType', 'Delete', $params);
636 }
637
638 /**
639 * @param int $membershipID
640 */
641 public function membershipDelete($membershipID) {
642 $deleteParams = ['id' => $membershipID];
643 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
644 }
645
646 /**
647 * @param string $name
648 *
649 * @return mixed
650 *
651 * @throws \CRM_Core_Exception
652 */
653 public function membershipStatusCreate($name = 'test member status') {
654 $params['name'] = $name;
655 $params['start_event'] = 'start_date';
656 $params['end_event'] = 'end_date';
657 $params['is_current_member'] = 1;
658 $params['is_active'] = 1;
659
660 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
661 CRM_Member_PseudoConstant::flush('membershipStatus');
662 return (int) $result['id'];
663 }
664
665 /**
666 * Delete the given membership status, deleting any memberships of the status first.
667 *
668 * @param int $membershipStatusID
669 *
670 * @throws \CRM_Core_Exception
671 */
672 public function membershipStatusDelete(int $membershipStatusID) {
673 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
674 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
675 }
676
677 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
678 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
679 $renewedMembershipEndDate = new DateTime($membershipEndDate);
680 switch ($durationUnit) {
681 case 'year':
682 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
683 break;
684
685 case 'month':
686 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
687 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
688 $renewedMembershipEndDate->add(new DateInterval('P1D'));
689 $renewedMembershipEndDate->add(new DateInterval('P1M'));
690 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
691 break;
692 }
693 return $renewedMembershipEndDate->format('Y-m-d');
694 }
695
696 /**
697 * Create a relationship type.
698 *
699 * @param array $params
700 *
701 * @return int
702 *
703 * @throws \CRM_Core_Exception
704 */
705 public function relationshipTypeCreate($params = []) {
706 $params = array_merge([
707 'name_a_b' => 'Relation 1 for relationship type create',
708 'name_b_a' => 'Relation 2 for relationship type create',
709 'contact_type_a' => 'Individual',
710 'contact_type_b' => 'Organization',
711 'is_reserved' => 1,
712 'is_active' => 1,
713 ], $params);
714
715 $result = $this->callAPISuccess('relationship_type', 'create', $params);
716 CRM_Core_PseudoConstant::flush('relationshipType');
717
718 return $result['id'];
719 }
720
721 /**
722 * Delete Relatinship Type.
723 *
724 * @param int $relationshipTypeID
725 */
726 public function relationshipTypeDelete($relationshipTypeID) {
727 $params['id'] = $relationshipTypeID;
728 $check = $this->callAPISuccess('relationship_type', 'get', $params);
729 if (!empty($check['count'])) {
730 $this->callAPISuccess('relationship_type', 'delete', $params);
731 }
732 }
733
734 /**
735 * @param array $params
736 *
737 * @return mixed
738 * @throws \CRM_Core_Exception
739 */
740 public function paymentProcessorTypeCreate($params = []) {
741 $params = array_merge([
742 'name' => 'API_Test_PP',
743 'title' => 'API Test Payment Processor',
744 'class_name' => 'CRM_Core_Payment_APITest',
745 'billing_mode' => 'form',
746 'is_recur' => 0,
747 'is_reserved' => 1,
748 'is_active' => 1,
749 ], $params);
750 $result = $this->callAPISuccess('PaymentProcessorType', 'create', $params);
751
752 CRM_Core_PseudoConstant::flush('paymentProcessorType');
753
754 return $result['id'];
755 }
756
757 /**
758 * Create test Authorize.net instance.
759 *
760 * @param array $params
761 *
762 * @return mixed
763 * @throws \CRM_Core_Exception
764 */
765 public function paymentProcessorAuthorizeNetCreate($params = []) {
766 $params = array_merge([
767 'name' => 'Authorize',
768 'domain_id' => CRM_Core_Config::domainID(),
769 'payment_processor_type_id' => 'AuthNet',
770 'title' => 'AuthNet',
771 'is_active' => 1,
772 'is_default' => 0,
773 'is_test' => 1,
774 'is_recur' => 1,
775 'user_name' => '4y5BfuW7jm',
776 'password' => '4cAmW927n8uLf5J8',
777 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
778 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
779 'class_name' => 'Payment_AuthorizeNet',
780 'billing_mode' => 1,
781 ], $params);
782
783 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
784 return (int) $result['id'];
785 }
786
787 /**
788 * Create Participant.
789 *
790 * @param array $params
791 * Array of contact id and event id values.
792 *
793 * @return int
794 * $id of participant created
795 */
796 public function participantCreate($params = []) {
797 if (empty($params['contact_id'])) {
798 $params['contact_id'] = $this->individualCreate();
799 }
800 if (empty($params['event_id'])) {
801 $event = $this->eventCreate();
802 $params['event_id'] = $event['id'];
803 }
804 $defaults = [
805 'status_id' => 2,
806 'role_id' => 1,
807 'register_date' => 20070219,
808 'source' => 'Wimbeldon',
809 'event_level' => 'Payment',
810 'debug' => 1,
811 ];
812
813 $params = array_merge($defaults, $params);
814 $result = $this->callAPISuccess('Participant', 'create', $params);
815 return $result['id'];
816 }
817
818 /**
819 * Create Payment Processor.
820 *
821 * @return int
822 * Id Payment Processor
823 */
824 public function processorCreate($params = []) {
825 $processorParams = [
826 'domain_id' => 1,
827 'name' => 'Dummy',
828 'payment_processor_type_id' => 'Dummy',
829 'financial_account_id' => 12,
830 'is_test' => TRUE,
831 'is_active' => 1,
832 'user_name' => '',
833 'url_site' => 'http://dummy.com',
834 'url_recur' => 'http://dummy.com',
835 'billing_mode' => 1,
836 'sequential' => 1,
837 'payment_instrument_id' => 'Debit Card',
838 ];
839 $processorParams = array_merge($processorParams, $params);
840 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
841 return $processor['id'];
842 }
843
844 /**
845 * Create Payment Processor.
846 *
847 * @param array $processorParams
848 *
849 * @return \CRM_Core_Payment_Dummy
850 * Instance of Dummy Payment Processor
851 *
852 * @throws \CiviCRM_API3_Exception
853 */
854 public function dummyProcessorCreate($processorParams = []) {
855 $paymentProcessorID = $this->processorCreate($processorParams);
856 // 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
857 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
858 $processorParams['is_test'] = FALSE;
859 $this->processorCreate($processorParams);
860 return System::singleton()->getById($paymentProcessorID);
861 }
862
863 /**
864 * Create contribution page.
865 *
866 * @param array $params
867 *
868 * @return array
869 * Array of contribution page
870 */
871 public function contributionPageCreate($params = []) {
872 $this->_pageParams = array_merge([
873 'title' => 'Test Contribution Page',
874 'financial_type_id' => 1,
875 'currency' => 'USD',
876 'financial_account_id' => 1,
877 'is_active' => 1,
878 'is_allow_other_amount' => 1,
879 'min_amount' => 10,
880 'max_amount' => 1000,
881 ], $params);
882 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
883 }
884
885 /**
886 * Create a sample batch.
887 */
888 public function batchCreate() {
889 $params = $this->_params;
890 $params['name'] = $params['title'] = 'Batch_433397';
891 $params['status_id'] = 1;
892 $result = $this->callAPISuccess('batch', 'create', $params);
893 return $result['id'];
894 }
895
896 /**
897 * Create Tag.
898 *
899 * @param array $params
900 *
901 * @return array
902 * result of created tag
903 */
904 public function tagCreate($params = []) {
905 $defaults = [
906 'name' => 'New Tag3',
907 'description' => 'This is description for Our New Tag ',
908 'domain_id' => '1',
909 ];
910 $params = array_merge($defaults, $params);
911 $result = $this->callAPISuccess('Tag', 'create', $params);
912 return $result['values'][$result['id']];
913 }
914
915 /**
916 * Delete Tag.
917 *
918 * @param int $tagId
919 * Id of the tag to be deleted.
920 *
921 * @return int
922 */
923 public function tagDelete($tagId) {
924 require_once 'api/api.php';
925 $params = [
926 'tag_id' => $tagId,
927 ];
928 $result = $this->callAPISuccess('Tag', 'delete', $params);
929 return $result['id'];
930 }
931
932 /**
933 * Add entity(s) to the tag
934 *
935 * @param array $params
936 *
937 * @return bool
938 */
939 public function entityTagAdd($params) {
940 $result = $this->callAPISuccess('entity_tag', 'create', $params);
941 return TRUE;
942 }
943
944 /**
945 * Create pledge.
946 *
947 * @param array $params
948 * Parameters.
949 *
950 * @return int
951 * id of created pledge
952 *
953 * @throws \CRM_Core_Exception
954 */
955 public function pledgeCreate($params) {
956 $params = array_merge([
957 'pledge_create_date' => date('Ymd'),
958 'start_date' => date('Ymd'),
959 'scheduled_date' => date('Ymd'),
960 'amount' => 100.00,
961 'pledge_status_id' => '2',
962 'financial_type_id' => '1',
963 'pledge_original_installment_amount' => 20,
964 'frequency_interval' => 5,
965 'frequency_unit' => 'year',
966 'frequency_day' => 15,
967 'installments' => 5,
968 ],
969 $params);
970
971 $result = $this->callAPISuccess('Pledge', 'create', $params);
972 return $result['id'];
973 }
974
975 /**
976 * Delete contribution.
977 *
978 * @param int $pledgeId
979 *
980 * @throws \CRM_Core_Exception
981 */
982 public function pledgeDelete($pledgeId) {
983 $params = [
984 'pledge_id' => $pledgeId,
985 ];
986 $this->callAPISuccess('Pledge', 'delete', $params);
987 }
988
989 /**
990 * Create contribution.
991 *
992 * @param array $params
993 * Array of parameters.
994 *
995 * @return int
996 * id of created contribution
997 * @throws \CRM_Core_Exception
998 */
999 public function contributionCreate($params) {
1000
1001 $params = array_merge([
1002 'domain_id' => 1,
1003 'receive_date' => date('Ymd'),
1004 'total_amount' => 100.00,
1005 'fee_amount' => 5.00,
1006 'financial_type_id' => 1,
1007 'payment_instrument_id' => 1,
1008 'non_deductible_amount' => 10.00,
1009 'source' => 'SSF',
1010 'contribution_status_id' => 1,
1011 ], $params);
1012
1013 $result = $this->callAPISuccess('contribution', 'create', $params);
1014 return $result['id'];
1015 }
1016
1017 /**
1018 * Delete contribution.
1019 *
1020 * @param int $contributionId
1021 *
1022 * @return array|int
1023 * @throws \CRM_Core_Exception
1024 */
1025 public function contributionDelete($contributionId) {
1026 $params = [
1027 'contribution_id' => $contributionId,
1028 ];
1029 $result = $this->callAPISuccess('contribution', 'delete', $params);
1030 return $result;
1031 }
1032
1033 /**
1034 * Create an Event.
1035 *
1036 * @param array $params
1037 * Name-value pair for an event.
1038 *
1039 * @return array
1040 * @throws \CRM_Core_Exception
1041 */
1042 public function eventCreate($params = []) {
1043 // if no contact was passed, make up a dummy event creator
1044 if (!isset($params['contact_id'])) {
1045 $params['contact_id'] = $this->_contactCreate([
1046 'contact_type' => 'Individual',
1047 'first_name' => 'Event',
1048 'last_name' => 'Creator',
1049 ]);
1050 }
1051
1052 // set defaults for missing params
1053 $params = array_merge([
1054 'title' => 'Annual CiviCRM meet',
1055 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1056 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1057 'event_type_id' => 1,
1058 'is_public' => 1,
1059 'start_date' => 20081021,
1060 'end_date' => 20081023,
1061 'is_online_registration' => 1,
1062 'registration_start_date' => 20080601,
1063 'registration_end_date' => 20081015,
1064 'max_participants' => 100,
1065 'event_full_text' => 'Sorry! We are already full',
1066 'is_monetary' => 0,
1067 'is_active' => 1,
1068 'is_show_location' => 0,
1069 'is_email_confirm' => 1,
1070 ], $params);
1071
1072 return $this->callAPISuccess('Event', 'create', $params);
1073 }
1074
1075 /**
1076 * Create a paid event.
1077 *
1078 * @param array $params
1079 *
1080 * @param array $options
1081 *
1082 * @param string $key
1083 * Index for storing event ID in ids array.
1084 *
1085 * @return array
1086 *
1087 * @throws \CRM_Core_Exception
1088 */
1089 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1090 $params['is_monetary'] = TRUE;
1091 $event = $this->eventCreate($params);
1092 $this->ids['Event'][$key] = (int) $event['id'];
1093 $this->ids['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1094 CRM_Price_BAO_PriceSet::addTo('civicrm_event', $event['id'], $this->ids['PriceSet'][$key]);
1095 $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($this->ids['PriceSet'][$key], TRUE, FALSE);
1096 $priceSet = $priceSet[$this->ids['PriceSet'][$key]] ?? NULL;
1097 $this->eventFeeBlock = $priceSet['fields'] ?? NULL;
1098 return $event;
1099 }
1100
1101 /**
1102 * Delete event.
1103 *
1104 * @param int $id
1105 * ID of the event.
1106 *
1107 * @return array|int
1108 */
1109 public function eventDelete($id) {
1110 $params = [
1111 'event_id' => $id,
1112 ];
1113 return $this->callAPISuccess('event', 'delete', $params);
1114 }
1115
1116 /**
1117 * Delete participant.
1118 *
1119 * @param int $participantID
1120 *
1121 * @return array|int
1122 */
1123 public function participantDelete($participantID) {
1124 $params = [
1125 'id' => $participantID,
1126 ];
1127 $check = $this->callAPISuccess('Participant', 'get', $params);
1128 if ($check['count'] > 0) {
1129 return $this->callAPISuccess('Participant', 'delete', $params);
1130 }
1131 }
1132
1133 /**
1134 * Create participant payment.
1135 *
1136 * @param int $participantID
1137 * @param int $contributionID
1138 *
1139 * @return int
1140 * $id of created payment
1141 */
1142 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1143 //Create Participant Payment record With Values
1144 $params = [
1145 'participant_id' => $participantID,
1146 'contribution_id' => $contributionID,
1147 ];
1148
1149 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1150 return $result['id'];
1151 }
1152
1153 /**
1154 * Delete participant payment.
1155 *
1156 * @param int $paymentID
1157 */
1158 public function participantPaymentDelete($paymentID) {
1159 $params = [
1160 'id' => $paymentID,
1161 ];
1162 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1163 }
1164
1165 /**
1166 * Add a Location.
1167 *
1168 * @param int $contactID
1169 *
1170 * @return int
1171 * location id of created location
1172 */
1173 public function locationAdd($contactID) {
1174 $address = [
1175 1 => [
1176 'location_type' => 'New Location Type',
1177 'is_primary' => 1,
1178 'name' => 'Saint Helier St',
1179 'county' => 'Marin',
1180 'country' => 'UNITED STATES',
1181 'state_province' => 'Michigan',
1182 'supplemental_address_1' => 'Hallmark Ct',
1183 'supplemental_address_2' => 'Jersey Village',
1184 'supplemental_address_3' => 'My Town',
1185 ],
1186 ];
1187
1188 $params = [
1189 'contact_id' => $contactID,
1190 'address' => $address,
1191 'location_format' => '2.0',
1192 'location_type' => 'New Location Type',
1193 ];
1194
1195 $result = $this->callAPISuccess('Location', 'create', $params);
1196 return $result;
1197 }
1198
1199 /**
1200 * Delete Locations of contact.
1201 *
1202 * @param array $params
1203 * Parameters.
1204 */
1205 public function locationDelete($params) {
1206 $this->callAPISuccess('Location', 'delete', $params);
1207 }
1208
1209 /**
1210 * Add a Location Type.
1211 *
1212 * @param array $params
1213 *
1214 * @return CRM_Core_DAO_LocationType
1215 * location id of created location
1216 */
1217 public function locationTypeCreate($params = NULL) {
1218 if ($params === NULL) {
1219 $params = [
1220 'name' => 'New Location Type',
1221 'vcard_name' => 'New Location Type',
1222 'description' => 'Location Type for Delete',
1223 'is_active' => 1,
1224 ];
1225 }
1226
1227 $locationType = new CRM_Core_DAO_LocationType();
1228 $locationType->copyValues($params);
1229 $locationType->save();
1230 // clear getfields cache
1231 CRM_Core_PseudoConstant::flush();
1232 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1233 return $locationType->id;
1234 }
1235
1236 /**
1237 * Delete a Location Type.
1238 *
1239 * @param int $locationTypeId
1240 */
1241 public function locationTypeDelete($locationTypeId) {
1242 $locationType = new CRM_Core_DAO_LocationType();
1243 $locationType->id = $locationTypeId;
1244 $locationType->delete();
1245 }
1246
1247 /**
1248 * Add a Mapping.
1249 *
1250 * @param array $params
1251 *
1252 * @return CRM_Core_DAO_Mapping
1253 * Mapping id of created mapping
1254 */
1255 public function mappingCreate($params = NULL) {
1256 if ($params === NULL) {
1257 $params = [
1258 'name' => 'Mapping name',
1259 'description' => 'Mapping description',
1260 // 'Export Contact' mapping.
1261 'mapping_type_id' => 7,
1262 ];
1263 }
1264
1265 $mapping = new CRM_Core_DAO_Mapping();
1266 $mapping->copyValues($params);
1267 $mapping->save();
1268 // clear getfields cache
1269 CRM_Core_PseudoConstant::flush();
1270 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1271 return $mapping;
1272 }
1273
1274 /**
1275 * Delete a Mapping
1276 *
1277 * @param int $mappingId
1278 */
1279 public function mappingDelete($mappingId) {
1280 $mapping = new CRM_Core_DAO_Mapping();
1281 $mapping->id = $mappingId;
1282 $mapping->delete();
1283 }
1284
1285 /**
1286 * Prepare class for ACLs.
1287 */
1288 protected function prepareForACLs() {
1289 $config = CRM_Core_Config::singleton();
1290 $config->userPermissionClass->permissions = [];
1291 }
1292
1293 /**
1294 * Reset after ACLs.
1295 */
1296 protected function cleanUpAfterACLs() {
1297 CRM_Utils_Hook::singleton()->reset();
1298 $tablesToTruncate = [
1299 'civicrm_acl',
1300 'civicrm_acl_cache',
1301 'civicrm_acl_entity_role',
1302 'civicrm_acl_contact_cache',
1303 ];
1304 $this->quickCleanup($tablesToTruncate);
1305 $config = CRM_Core_Config::singleton();
1306 unset($config->userPermissionClass->permissions);
1307 }
1308
1309 /**
1310 * Create a smart group.
1311 *
1312 * By default it will be a group of households.
1313 *
1314 * @param array $smartGroupParams
1315 * @param array $groupParams
1316 * @param string $contactType
1317 *
1318 * @return int
1319 */
1320 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1321 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1322 $savedSearch = CRM_Contact_BAO_SavedSearch::create($smartGroupParams);
1323
1324 $groupParams['saved_search_id'] = $savedSearch->id;
1325 return $this->groupCreate($groupParams);
1326 }
1327
1328 /**
1329 * Create a UFField.
1330 *
1331 * @param array $params
1332 */
1333 public function uFFieldCreate($params = []) {
1334 $params = array_merge([
1335 'uf_group_id' => 1,
1336 'field_name' => 'first_name',
1337 'is_active' => 1,
1338 'is_required' => 1,
1339 'visibility' => 'Public Pages and Listings',
1340 'is_searchable' => '1',
1341 'label' => 'first_name',
1342 'field_type' => 'Individual',
1343 'weight' => 1,
1344 ], $params);
1345 $this->callAPISuccess('uf_field', 'create', $params);
1346 }
1347
1348 /**
1349 * Add a UF Join Entry.
1350 *
1351 * @param array $params
1352 *
1353 * @return int
1354 * $id of created UF Join
1355 */
1356 public function ufjoinCreate($params = NULL) {
1357 if ($params === NULL) {
1358 $params = [
1359 'is_active' => 1,
1360 'module' => 'CiviEvent',
1361 'entity_table' => 'civicrm_event',
1362 'entity_id' => 3,
1363 'weight' => 1,
1364 'uf_group_id' => 1,
1365 ];
1366 }
1367 $result = $this->callAPISuccess('uf_join', 'create', $params);
1368 return $result;
1369 }
1370
1371 /**
1372 * @param array $params
1373 * Optional parameters.
1374 * @param bool $reloadConfig
1375 * While enabling CiviCampaign component, we shouldn't always forcibly
1376 * reload config as this hinder hook call in test environment
1377 *
1378 * @return int
1379 * Campaign ID.
1380 */
1381 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1382 $this->enableCiviCampaign($reloadConfig);
1383 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1384 'name' => 'big_campaign',
1385 'title' => 'Campaign',
1386 ], $params));
1387 return $campaign['id'];
1388 }
1389
1390 /**
1391 * Create Group for a contact.
1392 *
1393 * @param int $contactId
1394 */
1395 public function contactGroupCreate($contactId) {
1396 $params = [
1397 'contact_id.1' => $contactId,
1398 'group_id' => 1,
1399 ];
1400
1401 $this->callAPISuccess('GroupContact', 'Create', $params);
1402 }
1403
1404 /**
1405 * Delete Group for a contact.
1406 *
1407 * @param int $contactId
1408 */
1409 public function contactGroupDelete($contactId) {
1410 $params = [
1411 'contact_id.1' => $contactId,
1412 'group_id' => 1,
1413 ];
1414 $this->civicrm_api('GroupContact', 'Delete', $params);
1415 }
1416
1417 /**
1418 * Create Activity.
1419 *
1420 * @param array $params
1421 *
1422 * @return array|int
1423 *
1424 * @throws \CRM_Core_Exception
1425 * @throws \CiviCRM_API3_Exception
1426 */
1427 public function activityCreate($params = []) {
1428 $params = array_merge([
1429 'subject' => 'Discussion on warm beer',
1430 'activity_date_time' => date('Ymd'),
1431 'duration' => 90,
1432 'location' => 'Baker Street',
1433 'details' => 'Lets schedule a meeting',
1434 'status_id' => 1,
1435 'activity_type_id' => 'Meeting',
1436 ], $params);
1437 if (!isset($params['source_contact_id'])) {
1438 $params['source_contact_id'] = $this->individualCreate();
1439 }
1440 if (!isset($params['target_contact_id'])) {
1441 $params['target_contact_id'] = $this->individualCreate([
1442 'first_name' => 'Julia',
1443 'last_name' => 'Anderson',
1444 'prefix' => 'Ms.',
1445 'email' => 'julia_anderson@civicrm.org',
1446 'contact_type' => 'Individual',
1447 ]);
1448 }
1449 if (!isset($params['assignee_contact_id'])) {
1450 $params['assignee_contact_id'] = $params['target_contact_id'];
1451 }
1452
1453 $result = civicrm_api3('Activity', 'create', $params);
1454
1455 $result['target_contact_id'] = $params['target_contact_id'];
1456 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1457 return $result;
1458 }
1459
1460 /**
1461 * Create an activity type.
1462 *
1463 * @param array $params
1464 * Parameters.
1465 *
1466 * @return array
1467 */
1468 public function activityTypeCreate($params) {
1469 return $this->callAPISuccess('ActivityType', 'create', $params);
1470 }
1471
1472 /**
1473 * Delete activity type.
1474 *
1475 * @param int $activityTypeId
1476 * Id of the activity type.
1477 *
1478 * @return array
1479 */
1480 public function activityTypeDelete($activityTypeId) {
1481 $params['activity_type_id'] = $activityTypeId;
1482 return $this->callAPISuccess('ActivityType', 'delete', $params);
1483 }
1484
1485 /**
1486 * Create custom group.
1487 *
1488 * @param array $params
1489 *
1490 * @return array
1491 */
1492 public function customGroupCreate($params = []) {
1493 $defaults = [
1494 'title' => 'new custom group',
1495 'extends' => 'Contact',
1496 'domain_id' => 1,
1497 'style' => 'Inline',
1498 'is_active' => 1,
1499 ];
1500
1501 $params = array_merge($defaults, $params);
1502
1503 return $this->callAPISuccess('custom_group', 'create', $params);
1504 }
1505
1506 /**
1507 * Existing function doesn't allow params to be over-ridden so need a new one
1508 * this one allows you to only pass in the params you want to change
1509 *
1510 * @param array $params
1511 *
1512 * @return array|int
1513 */
1514 public function CustomGroupCreateByParams($params = []) {
1515 $defaults = [
1516 'title' => "API Custom Group",
1517 'extends' => 'Contact',
1518 'domain_id' => 1,
1519 'style' => 'Inline',
1520 'is_active' => 1,
1521 ];
1522 $params = array_merge($defaults, $params);
1523 return $this->callAPISuccess('custom_group', 'create', $params);
1524 }
1525
1526 /**
1527 * Create custom group with multi fields.
1528 *
1529 * @param array $params
1530 *
1531 * @return array|int
1532 */
1533 public function CustomGroupMultipleCreateByParams($params = []) {
1534 $defaults = [
1535 'style' => 'Tab',
1536 'is_multiple' => 1,
1537 ];
1538 $params = array_merge($defaults, $params);
1539 return $this->CustomGroupCreateByParams($params);
1540 }
1541
1542 /**
1543 * Create custom group with multi fields.
1544 *
1545 * @param array $params
1546 *
1547 * @return array
1548 */
1549 public function CustomGroupMultipleCreateWithFields($params = []) {
1550 // also need to pass on $params['custom_field'] if not set but not in place yet
1551 $ids = [];
1552 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1553 $ids['custom_group_id'] = $customGroup['id'];
1554
1555 $customField = $this->customFieldCreate([
1556 'custom_group_id' => $ids['custom_group_id'],
1557 'label' => 'field_1' . $ids['custom_group_id'],
1558 'in_selector' => 1,
1559 ]);
1560
1561 $ids['custom_field_id'][] = $customField['id'];
1562
1563 $customField = $this->customFieldCreate([
1564 'custom_group_id' => $ids['custom_group_id'],
1565 'default_value' => '',
1566 'label' => 'field_2' . $ids['custom_group_id'],
1567 'in_selector' => 1,
1568 ]);
1569 $ids['custom_field_id'][] = $customField['id'];
1570
1571 $customField = $this->customFieldCreate([
1572 'custom_group_id' => $ids['custom_group_id'],
1573 'default_value' => '',
1574 'label' => 'field_3' . $ids['custom_group_id'],
1575 'in_selector' => 1,
1576 ]);
1577 $ids['custom_field_id'][] = $customField['id'];
1578
1579 return $ids;
1580 }
1581
1582 /**
1583 * Create a custom group with a single text custom field. See
1584 * participant:testCreateWithCustom for how to use this
1585 *
1586 * @param string $function
1587 * __FUNCTION__.
1588 * @param string $filename
1589 * $file __FILE__.
1590 *
1591 * @return array
1592 * ids of created objects
1593 */
1594 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1595 $params = ['title' => $function];
1596 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1597 $params['extends'] = $entity ? $entity : 'Contact';
1598 $customGroup = $this->customGroupCreate($params);
1599 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1600 CRM_Core_PseudoConstant::flush();
1601
1602 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1603 }
1604
1605 /**
1606 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1607 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1608 *
1609 * @param string $function
1610 * __FUNCTION__.
1611 * @param string $filename
1612 * $file __FILE__.
1613 *
1614 * @return array
1615 * ids of created objects
1616 */
1617 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1618 $params = ['title' => $function];
1619 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1620 $params['extends'] = $entity ? $entity : 'Contact';
1621 $customGroup = $this->customGroupCreate($params);
1622 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1623 CRM_Core_PseudoConstant::flush();
1624 $options = [
1625 'defaultValue' => 'Default Value',
1626 'lowercasevalue' => 'Lowercase Value',
1627 1 => 'Integer Value',
1628 'NULL' => 'NULL',
1629 ];
1630 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1631 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1632 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1633 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1634 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1635 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1636 foreach ($options as $option_value => $option_label) {
1637 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1638 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1639 }
1640
1641 return [
1642 'custom_group_id' => $customGroup['id'],
1643 'custom_field_id' => $customField['id'],
1644 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1645 'custom_field_group_options' => $options,
1646 ];
1647 }
1648
1649 /**
1650 * Delete custom group.
1651 *
1652 * @param int $customGroupID
1653 *
1654 * @return array|int
1655 */
1656 public function customGroupDelete($customGroupID) {
1657 $params['id'] = $customGroupID;
1658 return $this->callAPISuccess('custom_group', 'delete', $params);
1659 }
1660
1661 /**
1662 * Create custom field.
1663 *
1664 * @param array $params
1665 * (custom_group_id) is required.
1666 *
1667 * @return array
1668 */
1669 public function customFieldCreate($params) {
1670 $params = array_merge([
1671 'label' => 'Custom Field',
1672 'data_type' => 'String',
1673 'html_type' => 'Text',
1674 'is_searchable' => 1,
1675 'is_active' => 1,
1676 'default_value' => 'defaultValue',
1677 ], $params);
1678
1679 $result = $this->callAPISuccess('custom_field', 'create', $params);
1680 // these 2 functions are called with force to flush static caches
1681 CRM_Core_BAO_CustomField::getTableColumnGroup($result['id'], 1);
1682 CRM_Core_Component::getEnabledComponents(1);
1683 return $result;
1684 }
1685
1686 /**
1687 * Delete custom field.
1688 *
1689 * @param int $customFieldID
1690 *
1691 * @return array|int
1692 */
1693 public function customFieldDelete($customFieldID) {
1694
1695 $params['id'] = $customFieldID;
1696 return $this->callAPISuccess('custom_field', 'delete', $params);
1697 }
1698
1699 /**
1700 * Create note.
1701 *
1702 * @param int $cId
1703 *
1704 * @return array
1705 */
1706 public function noteCreate($cId) {
1707 $params = [
1708 'entity_table' => 'civicrm_contact',
1709 'entity_id' => $cId,
1710 'note' => 'hello I am testing Note',
1711 'contact_id' => $cId,
1712 'modified_date' => date('Ymd'),
1713 'subject' => 'Test Note',
1714 ];
1715
1716 return $this->callAPISuccess('Note', 'create', $params);
1717 }
1718
1719 /**
1720 * Enable CiviCampaign Component.
1721 *
1722 * @param bool $reloadConfig
1723 * Force relaod config or not
1724 */
1725 public function enableCiviCampaign($reloadConfig = TRUE) {
1726 CRM_Core_BAO_ConfigSetting::enableComponent('CiviCampaign');
1727 if ($reloadConfig) {
1728 // force reload of config object
1729 $config = CRM_Core_Config::singleton(TRUE, TRUE);
1730 }
1731 //flush cache by calling with reset
1732 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1733 }
1734
1735 /**
1736 * Create custom field with Option Values.
1737 *
1738 * @param array $customGroup
1739 * @param string $name
1740 * Name of custom field.
1741 * @param array $extraParams
1742 * Additional parameters to pass through.
1743 *
1744 * @return array|int
1745 */
1746 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1747 $fieldParams = [
1748 'custom_group_id' => $customGroup['id'],
1749 'name' => 'test_custom_group',
1750 'label' => 'Country',
1751 'html_type' => 'Select',
1752 'data_type' => 'String',
1753 'weight' => 4,
1754 'is_required' => 1,
1755 'is_searchable' => 0,
1756 'is_active' => 1,
1757 ];
1758
1759 $optionGroup = [
1760 'domain_id' => 1,
1761 'name' => 'option_group1',
1762 'label' => 'option_group_label1',
1763 ];
1764
1765 $optionValue = [
1766 'option_label' => ['Label1', 'Label2'],
1767 'option_value' => ['value1', 'value2'],
1768 'option_name' => [$name . '_1', $name . '_2'],
1769 'option_weight' => [1, 2],
1770 'option_status' => [1, 1],
1771 ];
1772
1773 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1774
1775 return $this->callAPISuccess('custom_field', 'create', $params);
1776 }
1777
1778 /**
1779 * @param $entities
1780 *
1781 * @return bool
1782 */
1783 public function confirmEntitiesDeleted($entities) {
1784 foreach ($entities as $entity) {
1785
1786 $result = $this->callAPISuccess($entity, 'Get', []);
1787 if ($result['error'] == 1 || $result['count'] > 0) {
1788 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1789 return TRUE;
1790 }
1791 }
1792 return FALSE;
1793 }
1794
1795 /**
1796 * Quick clean by emptying tables created for the test.
1797 *
1798 * @param array $tablesToTruncate
1799 * @param bool $dropCustomValueTables
1800 *
1801 * @throws \CRM_Core_Exception
1802 */
1803 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1804 if ($this->tx) {
1805 throw new \CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1806 }
1807 if ($dropCustomValueTables) {
1808 $optionGroupResult = CRM_Core_DAO::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1809 while ($optionGroupResult->fetch()) {
1810 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1811 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1812 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1813 // generic cleanup routine.
1814 if (!empty($optionGroupResult->option_group_id) && $optionGroupResult->option_group_id > 50) {
1815 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id);
1816 }
1817 }
1818 $tablesToTruncate[] = 'civicrm_custom_group';
1819 $tablesToTruncate[] = 'civicrm_custom_field';
1820 }
1821
1822 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate, $tablesToTruncate));
1823
1824 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1825 foreach ($tablesToTruncate as $table) {
1826 $sql = "TRUNCATE TABLE $table";
1827 CRM_Core_DAO::executeQuery($sql);
1828 }
1829 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1830
1831 if ($dropCustomValueTables) {
1832 $dbName = self::getDBName();
1833 $query = "
1834 SELECT TABLE_NAME as tableName
1835 FROM INFORMATION_SCHEMA.TABLES
1836 WHERE TABLE_SCHEMA = '{$dbName}'
1837 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1838 ";
1839
1840 $tableDAO = CRM_Core_DAO::executeQuery($query);
1841 while ($tableDAO->fetch()) {
1842 $sql = "DROP TABLE {$tableDAO->tableName}";
1843 CRM_Core_DAO::executeQuery($sql);
1844 }
1845 }
1846 }
1847
1848 /**
1849 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1850 *
1851 * @throws \CRM_Core_Exception
1852 */
1853 public function quickCleanUpFinancialEntities() {
1854 $tablesToTruncate = [
1855 'civicrm_activity',
1856 'civicrm_activity_contact',
1857 'civicrm_contribution',
1858 'civicrm_contribution_soft',
1859 'civicrm_contribution_product',
1860 'civicrm_financial_trxn',
1861 'civicrm_financial_item',
1862 'civicrm_contribution_recur',
1863 'civicrm_line_item',
1864 'civicrm_contribution_page',
1865 'civicrm_payment_processor',
1866 'civicrm_entity_financial_trxn',
1867 'civicrm_membership',
1868 'civicrm_membership_type',
1869 'civicrm_membership_payment',
1870 'civicrm_membership_log',
1871 'civicrm_membership_block',
1872 'civicrm_event',
1873 'civicrm_participant',
1874 'civicrm_participant_payment',
1875 'civicrm_pledge',
1876 'civicrm_pcp_block',
1877 'civicrm_pcp',
1878 'civicrm_pledge_block',
1879 'civicrm_pledge_payment',
1880 'civicrm_price_set_entity',
1881 'civicrm_price_field_value',
1882 'civicrm_price_field',
1883 ];
1884 $this->quickCleanup($tablesToTruncate);
1885 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1886 $this->restoreDefaultPriceSetConfig();
1887 $this->disableTaxAndInvoicing();
1888 $this->setCurrencySeparators(',');
1889 CRM_Core_PseudoConstant::flush('taxRates');
1890 System::singleton()->flushProcessors();
1891 // @fixme this parameter is leaking - it should not be defined as a class static
1892 // but for now we just handle in tear down.
1893 CRM_Contribute_BAO_Query::$_contribOrSoftCredit = 'only contribs';
1894 }
1895
1896 /**
1897 * Reset the price set config so results exist.
1898 */
1899 public function restoreDefaultPriceSetConfig() {
1900 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1901 CRM_Core_DAO::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1902 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)");
1903 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)");
1904 }
1905
1906 /**
1907 * Recreate default membership types.
1908 */
1909 public function restoreMembershipTypes() {
1910 CRM_Core_DAO::executeQuery(
1911 "REPLACE INTO civicrm_membership_type
1912 (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)
1913 VALUES
1914 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1915 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1916 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1917 ");
1918 }
1919
1920 /*
1921 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1922 * Default behaviour is to also delete the entity
1923 * @param array $params
1924 * Params array to check against.
1925 * @param int $id
1926 * Id of the entity concerned.
1927 * @param string $entity
1928 * Name of entity concerned (e.g. membership).
1929 * @param bool $delete
1930 * Should the entity be deleted as part of this check.
1931 * @param string $errorText
1932 * Text to print on error.
1933 */
1934
1935 /**
1936 * @param array $params
1937 * @param int $id
1938 * @param $entity
1939 * @param int $delete
1940 * @param string $errorText
1941 *
1942 * @throws CRM_Core_Exception
1943 */
1944 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1945
1946 $result = $this->callAPISuccessGetSingle($entity, [
1947 'id' => $id,
1948 ]);
1949
1950 if ($delete) {
1951 $this->callAPISuccess($entity, 'Delete', [
1952 'id' => $id,
1953 ]);
1954 }
1955 $dateFields = $keys = $dateTimeFields = [];
1956 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1957 foreach ($fields['values'] as $field => $settings) {
1958 if (array_key_exists($field, $result)) {
1959 $keys[CRM_Utils_Array::value('name', $settings, $field)] = $field;
1960 }
1961 else {
1962 $keys[CRM_Utils_Array::value('name', $settings, $field)] = CRM_Utils_Array::value('name', $settings, $field);
1963 }
1964 $type = $settings['type'] ?? NULL;
1965 if ($type == CRM_Utils_Type::T_DATE) {
1966 $dateFields[] = $settings['name'];
1967 // we should identify both real names & unique names as dates
1968 if ($field != $settings['name']) {
1969 $dateFields[] = $field;
1970 }
1971 }
1972 if ($type == CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME) {
1973 $dateTimeFields[] = $settings['name'];
1974 // we should identify both real names & unique names as dates
1975 if ($field != $settings['name']) {
1976 $dateTimeFields[] = $field;
1977 }
1978 }
1979 }
1980
1981 if (strtolower($entity) == 'contribution') {
1982 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1983 // this is not returned in id format
1984 unset($params['payment_instrument_id']);
1985 $params['contribution_source'] = $params['source'];
1986 unset($params['source']);
1987 }
1988
1989 foreach ($params as $key => $value) {
1990 if ($key == 'version' || substr($key, 0, 3) == 'api' || !array_key_exists($keys[$key], $result)) {
1991 continue;
1992 }
1993 if (in_array($key, $dateFields)) {
1994 $value = date('Y-m-d', strtotime($value));
1995 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1996 }
1997 if (in_array($key, $dateTimeFields)) {
1998 $value = date('Y-m-d H:i:s', strtotime($value));
1999 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array::value($keys[$key], $result, CRM_Utils_Array::value($key, $result))));
2000 }
2001 $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);
2002 }
2003 }
2004
2005 /**
2006 * Get formatted values in the actual and expected result.
2007 *
2008 * @param array $actual
2009 * Actual calculated values.
2010 * @param array $expected
2011 * Expected values.
2012 */
2013 public function checkArrayEquals(&$actual, &$expected) {
2014 self::unsetId($actual);
2015 self::unsetId($expected);
2016 $this->assertEquals($expected, $actual);
2017 }
2018
2019 /**
2020 * Unset the key 'id' from the array
2021 *
2022 * @param array $unformattedArray
2023 * The array from which the 'id' has to be unset.
2024 */
2025 public static function unsetId(&$unformattedArray) {
2026 $formattedArray = [];
2027 if (array_key_exists('id', $unformattedArray)) {
2028 unset($unformattedArray['id']);
2029 }
2030 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
2031 foreach ($unformattedArray['values'] as $key => $value) {
2032 if (is_array($value)) {
2033 foreach ($value as $k => $v) {
2034 if ($k == 'id') {
2035 unset($value[$k]);
2036 }
2037 }
2038 }
2039 elseif ($key == 'id') {
2040 $unformattedArray[$key];
2041 }
2042 $formattedArray = [$value];
2043 }
2044 $unformattedArray['values'] = $formattedArray;
2045 }
2046 }
2047
2048 /**
2049 * Helper to enable/disable custom directory support
2050 *
2051 * @param array $customDirs
2052 * With members:.
2053 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2054 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2055 */
2056 public function customDirectories($customDirs) {
2057 $config = CRM_Core_Config::singleton();
2058
2059 if (empty($customDirs['php_path']) || $customDirs['php_path'] === FALSE) {
2060 unset($config->customPHPPathDir);
2061 }
2062 elseif ($customDirs['php_path'] === TRUE) {
2063 $config->customPHPPathDir = dirname(dirname(__FILE__)) . '/custom_directories/php/';
2064 }
2065 else {
2066 $config->customPHPPathDir = $php_path;
2067 }
2068
2069 if (empty($customDirs['template_path']) || $customDirs['template_path'] === FALSE) {
2070 unset($config->customTemplateDir);
2071 }
2072 elseif ($customDirs['template_path'] === TRUE) {
2073 $config->customTemplateDir = dirname(dirname(__FILE__)) . '/custom_directories/templates/';
2074 }
2075 else {
2076 $config->customTemplateDir = $template_path;
2077 }
2078 }
2079
2080 /**
2081 * Generate a temporary folder.
2082 *
2083 * @param string $prefix
2084 *
2085 * @return string
2086 */
2087 public function createTempDir($prefix = 'test-') {
2088 $tempDir = CRM_Utils_File::tempdir($prefix);
2089 $this->tempDirs[] = $tempDir;
2090 return $tempDir;
2091 }
2092
2093 public function cleanTempDirs() {
2094 if (!is_array($this->tempDirs)) {
2095 // fix test errors where this is not set
2096 return;
2097 }
2098 foreach ($this->tempDirs as $tempDir) {
2099 if (is_dir($tempDir)) {
2100 CRM_Utils_File::cleanDir($tempDir, TRUE, FALSE);
2101 }
2102 }
2103 }
2104
2105 /**
2106 * Temporarily replace the singleton extension with a different one.
2107 *
2108 * @param \CRM_Extension_System $system
2109 */
2110 public function setExtensionSystem(CRM_Extension_System $system) {
2111 if ($this->origExtensionSystem == NULL) {
2112 $this->origExtensionSystem = CRM_Extension_System::singleton();
2113 }
2114 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2115 }
2116
2117 public function unsetExtensionSystem() {
2118 if ($this->origExtensionSystem !== NULL) {
2119 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2120 $this->origExtensionSystem = NULL;
2121 }
2122 }
2123
2124 /**
2125 * Temporarily alter the settings-metadata to add a mock setting.
2126 *
2127 * WARNING: The setting metadata will disappear on the next cache-clear.
2128 *
2129 * @param $extras
2130 *
2131 * @return void
2132 */
2133 public function setMockSettingsMetaData($extras) {
2134 CRM_Utils_Hook::singleton()
2135 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2136 $metadata = array_merge($metadata, $extras);
2137 });
2138
2139 Civi::service('settings_manager')->flush();
2140
2141 $fields = $this->callAPISuccess('setting', 'getfields', []);
2142 foreach ($extras as $key => $spec) {
2143 $this->assertNotEmpty($spec['title']);
2144 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2145 }
2146 }
2147
2148 /**
2149 * @param string $name
2150 */
2151 public function financialAccountDelete($name) {
2152 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2153 $financialAccount->name = $name;
2154 if ($financialAccount->find(TRUE)) {
2155 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2156 $entityFinancialType->financial_account_id = $financialAccount->id;
2157 $entityFinancialType->delete();
2158 $financialAccount->delete();
2159 }
2160 }
2161
2162 /**
2163 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2164 * (NB unclear if this is still required)
2165 */
2166 public function _sethtmlGlobals() {
2167 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2168 'required' => [
2169 'html_quickform_rule_required',
2170 'HTML/QuickForm/Rule/Required.php',
2171 ],
2172 'maxlength' => [
2173 'html_quickform_rule_range',
2174 'HTML/QuickForm/Rule/Range.php',
2175 ],
2176 'minlength' => [
2177 'html_quickform_rule_range',
2178 'HTML/QuickForm/Rule/Range.php',
2179 ],
2180 'rangelength' => [
2181 'html_quickform_rule_range',
2182 'HTML/QuickForm/Rule/Range.php',
2183 ],
2184 'email' => [
2185 'html_quickform_rule_email',
2186 'HTML/QuickForm/Rule/Email.php',
2187 ],
2188 'regex' => [
2189 'html_quickform_rule_regex',
2190 'HTML/QuickForm/Rule/Regex.php',
2191 ],
2192 'lettersonly' => [
2193 'html_quickform_rule_regex',
2194 'HTML/QuickForm/Rule/Regex.php',
2195 ],
2196 'alphanumeric' => [
2197 'html_quickform_rule_regex',
2198 'HTML/QuickForm/Rule/Regex.php',
2199 ],
2200 'numeric' => [
2201 'html_quickform_rule_regex',
2202 'HTML/QuickForm/Rule/Regex.php',
2203 ],
2204 'nopunctuation' => [
2205 'html_quickform_rule_regex',
2206 'HTML/QuickForm/Rule/Regex.php',
2207 ],
2208 'nonzero' => [
2209 'html_quickform_rule_regex',
2210 'HTML/QuickForm/Rule/Regex.php',
2211 ],
2212 'callback' => [
2213 'html_quickform_rule_callback',
2214 'HTML/QuickForm/Rule/Callback.php',
2215 ],
2216 'compare' => [
2217 'html_quickform_rule_compare',
2218 'HTML/QuickForm/Rule/Compare.php',
2219 ],
2220 ];
2221 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2222 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2223 'group' => [
2224 'HTML/QuickForm/group.php',
2225 'HTML_QuickForm_group',
2226 ],
2227 'hidden' => [
2228 'HTML/QuickForm/hidden.php',
2229 'HTML_QuickForm_hidden',
2230 ],
2231 'reset' => [
2232 'HTML/QuickForm/reset.php',
2233 'HTML_QuickForm_reset',
2234 ],
2235 'checkbox' => [
2236 'HTML/QuickForm/checkbox.php',
2237 'HTML_QuickForm_checkbox',
2238 ],
2239 'file' => [
2240 'HTML/QuickForm/file.php',
2241 'HTML_QuickForm_file',
2242 ],
2243 'image' => [
2244 'HTML/QuickForm/image.php',
2245 'HTML_QuickForm_image',
2246 ],
2247 'password' => [
2248 'HTML/QuickForm/password.php',
2249 'HTML_QuickForm_password',
2250 ],
2251 'radio' => [
2252 'HTML/QuickForm/radio.php',
2253 'HTML_QuickForm_radio',
2254 ],
2255 'button' => [
2256 'HTML/QuickForm/button.php',
2257 'HTML_QuickForm_button',
2258 ],
2259 'submit' => [
2260 'HTML/QuickForm/submit.php',
2261 'HTML_QuickForm_submit',
2262 ],
2263 'select' => [
2264 'HTML/QuickForm/select.php',
2265 'HTML_QuickForm_select',
2266 ],
2267 'hiddenselect' => [
2268 'HTML/QuickForm/hiddenselect.php',
2269 'HTML_QuickForm_hiddenselect',
2270 ],
2271 'text' => [
2272 'HTML/QuickForm/text.php',
2273 'HTML_QuickForm_text',
2274 ],
2275 'textarea' => [
2276 'HTML/QuickForm/textarea.php',
2277 'HTML_QuickForm_textarea',
2278 ],
2279 'fckeditor' => [
2280 'HTML/QuickForm/fckeditor.php',
2281 'HTML_QuickForm_FCKEditor',
2282 ],
2283 'tinymce' => [
2284 'HTML/QuickForm/tinymce.php',
2285 'HTML_QuickForm_TinyMCE',
2286 ],
2287 'dojoeditor' => [
2288 'HTML/QuickForm/dojoeditor.php',
2289 'HTML_QuickForm_dojoeditor',
2290 ],
2291 'link' => [
2292 'HTML/QuickForm/link.php',
2293 'HTML_QuickForm_link',
2294 ],
2295 'advcheckbox' => [
2296 'HTML/QuickForm/advcheckbox.php',
2297 'HTML_QuickForm_advcheckbox',
2298 ],
2299 'date' => [
2300 'HTML/QuickForm/date.php',
2301 'HTML_QuickForm_date',
2302 ],
2303 'static' => [
2304 'HTML/QuickForm/static.php',
2305 'HTML_QuickForm_static',
2306 ],
2307 'header' => [
2308 'HTML/QuickForm/header.php',
2309 'HTML_QuickForm_header',
2310 ],
2311 'html' => [
2312 'HTML/QuickForm/html.php',
2313 'HTML_QuickForm_html',
2314 ],
2315 'hierselect' => [
2316 'HTML/QuickForm/hierselect.php',
2317 'HTML_QuickForm_hierselect',
2318 ],
2319 'autocomplete' => [
2320 'HTML/QuickForm/autocomplete.php',
2321 'HTML_QuickForm_autocomplete',
2322 ],
2323 'xbutton' => [
2324 'HTML/QuickForm/xbutton.php',
2325 'HTML_QuickForm_xbutton',
2326 ],
2327 'advmultiselect' => [
2328 'HTML/QuickForm/advmultiselect.php',
2329 'HTML_QuickForm_advmultiselect',
2330 ],
2331 ];
2332 }
2333
2334 /**
2335 * Set up an acl allowing contact to see 2 specified groups
2336 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2337 *
2338 * You need to have pre-created these groups & created the user e.g
2339 * $this->createLoggedInUser();
2340 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2341 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2342 *
2343 * @param bool $isProfile
2344 */
2345 public function setupACL($isProfile = FALSE) {
2346 global $_REQUEST;
2347 $_REQUEST = $this->_params;
2348
2349 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
2350 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2351 $ov = new CRM_Core_DAO_OptionValue();
2352 $ov->option_group_id = $optionGroupID;
2353 $ov->value = 55;
2354 if ($ov->find(TRUE)) {
2355 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2356 }
2357 $optionValue = $this->callAPISuccess('option_value', 'create', [
2358 'option_group_id' => $optionGroupID,
2359 'label' => 'pick me',
2360 'value' => 55,
2361 ]);
2362
2363 CRM_Core_DAO::executeQuery("
2364 TRUNCATE civicrm_acl_cache
2365 ");
2366
2367 CRM_Core_DAO::executeQuery("
2368 TRUNCATE civicrm_acl_contact_cache
2369 ");
2370
2371 CRM_Core_DAO::executeQuery("
2372 INSERT INTO civicrm_acl_entity_role (
2373 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2374 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2375 ");
2376
2377 if ($isProfile) {
2378 CRM_Core_DAO::executeQuery("
2379 INSERT INTO civicrm_acl (
2380 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2381 )
2382 VALUES (
2383 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2384 );
2385 ");
2386 }
2387 else {
2388 CRM_Core_DAO::executeQuery("
2389 INSERT INTO civicrm_acl (
2390 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2391 )
2392 VALUES (
2393 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2394 );
2395 ");
2396
2397 CRM_Core_DAO::executeQuery("
2398 INSERT INTO civicrm_acl (
2399 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2400 )
2401 VALUES (
2402 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2403 );
2404 ");
2405 }
2406
2407 $this->_loggedInUser = CRM_Core_Session::singleton()->get('userID');
2408 $this->callAPISuccess('group_contact', 'create', [
2409 'group_id' => $this->_permissionedGroup,
2410 'contact_id' => $this->_loggedInUser,
2411 ]);
2412
2413 if (!$isProfile) {
2414 //flush cache
2415 CRM_ACL_BAO_Cache::resetCache();
2416 CRM_ACL_API::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2417 }
2418 }
2419
2420 /**
2421 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2422 */
2423 public function offsetDefaultPriceSet() {
2424 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2425 $firstID = $contributionPriceSet['id'];
2426 $this->callAPISuccess('price_set', 'create', [
2427 'id' => $contributionPriceSet['id'],
2428 'is_active' => 0,
2429 'name' => 'old',
2430 ]);
2431 unset($contributionPriceSet['id']);
2432 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2433 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2434 'price_set_id' => $firstID,
2435 'options' => ['limit' => 1],
2436 ]);
2437 unset($priceField['id']);
2438 $priceField['price_set_id'] = $newPriceSet['id'];
2439 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2440 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2441 'price_set_id' => $firstID,
2442 'sequential' => 1,
2443 'options' => ['limit' => 1],
2444 ]);
2445
2446 unset($priceFieldValue['id']);
2447 //create some padding to use up ids
2448 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2449 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2450 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2451 }
2452
2453 /**
2454 * Create an instance of the paypal processor.
2455 *
2456 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2457 * this parent class & we don't have a structure for that yet
2458 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2459 * & the best protection against that is the functions this class affords
2460 *
2461 * @param array $params
2462 *
2463 * @return int $result['id'] payment processor id
2464 */
2465 public function paymentProcessorCreate($params = []) {
2466 $params = array_merge([
2467 'name' => 'demo',
2468 'domain_id' => CRM_Core_Config::domainID(),
2469 'payment_processor_type_id' => 'PayPal',
2470 'is_active' => 1,
2471 'is_default' => 0,
2472 'is_test' => 1,
2473 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2474 'password' => '1183377788',
2475 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2476 'url_site' => 'https://www.sandbox.paypal.com/',
2477 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2478 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2479 'class_name' => 'Payment_PayPalImpl',
2480 'billing_mode' => 3,
2481 'financial_type_id' => 1,
2482 'financial_account_id' => 12,
2483 // Credit card = 1 so can pass 'by accident'.
2484 'payment_instrument_id' => 'Debit Card',
2485 ], $params);
2486 if (!is_numeric($params['payment_processor_type_id'])) {
2487 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2488 //here
2489 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2490 'name' => $params['payment_processor_type_id'],
2491 'return' => 'id',
2492 ], 'integer');
2493 }
2494 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2495 return $result['id'];
2496 }
2497
2498 /**
2499 * Set up initial recurring payment allowing subsequent IPN payments.
2500 *
2501 * @param array $recurParams (Optional)
2502 * @param array $contributionParams (Optional)
2503 *
2504 * @throws \CRM_Core_Exception
2505 */
2506 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2507 $this->ids['campaign'][0] = $this->callAPISuccess('Campaign', 'create', ['title' => 'get the money'])['id'];
2508 $contributionParams = array_merge([
2509 'total_amount' => '200',
2510 'invoice_id' => $this->_invoiceID,
2511 'financial_type_id' => 'Donation',
2512 'contribution_status_id' => 'Pending',
2513 'contact_id' => $this->_contactID,
2514 'contribution_page_id' => $this->_contributionPageID,
2515 'payment_processor_id' => $this->_paymentProcessorID,
2516 'is_test' => 0,
2517 'receive_date' => '2019-07-25 07:34:23',
2518 'skipCleanMoney' => TRUE,
2519 'amount_level' => 'expensive',
2520 'campaign_id' => $this->ids['campaign'][0],
2521 'source' => 'Online Contribution: Page name',
2522 ], $contributionParams);
2523 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2524 'contact_id' => $this->_contactID,
2525 'amount' => 1000,
2526 'sequential' => 1,
2527 'installments' => 5,
2528 'frequency_unit' => 'Month',
2529 'frequency_interval' => 1,
2530 'invoice_id' => $this->_invoiceID,
2531 'contribution_status_id' => 2,
2532 'payment_processor_id' => $this->_paymentProcessorID,
2533 // processor provided ID - use contact ID as proxy.
2534 'processor_id' => $this->_contactID,
2535 'api.Order.create' => $contributionParams,
2536 ], $recurParams))['values'][0];
2537 $this->_contributionRecurID = $contributionRecur['id'];
2538 $this->_contributionID = $contributionRecur['api.Order.create']['id'];
2539 $this->ids['Contribution'][0] = $this->_contributionID;
2540 }
2541
2542 /**
2543 * 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
2544 *
2545 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2546 *
2547 * @throws \CRM_Core_Exception
2548 */
2549 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2550 $membershipParams = $recurParams = [];
2551 if (!empty($params['duration_unit'])) {
2552 $membershipParams['duration_unit'] = $params['duration_unit'];
2553 }
2554 if (!empty($params['frequency_unit'])) {
2555 $recurParams['frequency_unit'] = $params['frequency_unit'];
2556 }
2557
2558 $this->ids['membership_type'] = $this->membershipTypeCreate($membershipParams);
2559 //create a contribution so our membership & contribution don't both have id = 1
2560 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2561 $this->contributionCreate([
2562 'contact_id' => $this->_contactID,
2563 'is_test' => 1,
2564 'financial_type_id' => 1,
2565 'invoice_id' => 'abcd',
2566 'trxn_id' => 345,
2567 'receive_date' => '2019-07-25 07:34:23',
2568 ]);
2569 }
2570
2571 $this->ids['membership'] = $this->callAPISuccess('Membership', 'create', [
2572 'contact_id' => $this->_contactID,
2573 'membership_type_id' => $this->ids['membership_type'],
2574 'format.only_id' => TRUE,
2575 'source' => 'Payment',
2576 'skipLineItem' => TRUE,
2577 ]);
2578 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2579 'line_items' => [
2580 [
2581 'line_item' => [
2582 [
2583 'entity_table' => 'civicrm_membership',
2584 'entity_id' => $this->ids['membership'],
2585 'label' => 'General',
2586 'qty' => 1,
2587 'unit_price' => 200,
2588 'line_total' => 200,
2589 'financial_type_id' => 1,
2590 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2591 'return' => 'id',
2592 'label' => 'Membership Amount',
2593 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2594 ]),
2595 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2596 'return' => 'id',
2597 'label' => 'General',
2598 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2599 ]),
2600 ],
2601 ],
2602 ],
2603 ],
2604 ]);
2605 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids['membership'], 'contribution_recur_id' => $this->_contributionRecurID]);
2606 }
2607
2608 /**
2609 * @param $message
2610 *
2611 * @throws Exception
2612 */
2613 public function CiviUnitTestCase_fatalErrorHandler($message) {
2614 throw new Exception("{$message['message']}: {$message['code']}");
2615 }
2616
2617 /**
2618 * Wrap the entire test case in a transaction.
2619 *
2620 * Only subsequent DB statements will be wrapped in TX -- this cannot
2621 * retroactively wrap old DB statements. Therefore, it makes sense to
2622 * call this at the beginning of setUp().
2623 *
2624 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2625 * this option does not work with, e.g., custom-data.
2626 *
2627 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2628 * if TRUNCATE or ALTER is called while using a transaction.
2629 *
2630 * @param bool $nest
2631 * Whether to use nesting or reference-counting.
2632 */
2633 public function useTransaction($nest = TRUE) {
2634 if (!$this->tx) {
2635 $this->tx = new CRM_Core_Transaction($nest);
2636 $this->tx->rollback();
2637 }
2638 }
2639
2640 /**
2641 * Assert the attachment exists.
2642 *
2643 * @param bool $exists
2644 * @param array $apiResult
2645 */
2646 protected function assertAttachmentExistence($exists, $apiResult) {
2647 $fileId = $apiResult['id'];
2648 $this->assertTrue(is_numeric($fileId));
2649 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2650 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2651 1 => [$fileId, 'Int'],
2652 ]);
2653 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2654 1 => [$fileId, 'Int'],
2655 ]);
2656 }
2657
2658 /**
2659 * Assert 2 sql strings are the same, ignoring double spaces.
2660 *
2661 * @param string $expectedSQL
2662 * @param string $actualSQL
2663 * @param string $message
2664 */
2665 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2666 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2667 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2668 $this->assertEquals($expected, $actual, $message);
2669 }
2670
2671 /**
2672 * Create a price set for an event.
2673 *
2674 * @param int $feeTotal
2675 * @param int $minAmt
2676 * @param string $type
2677 *
2678 * @param array $options
2679 *
2680 * @return int
2681 * Price Set ID.
2682 * @throws \CRM_Core_Exception
2683 */
2684 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2685 // creating price set, price field
2686 $paramsSet['title'] = 'Price Set';
2687 $paramsSet['name'] = CRM_Utils_String::titleToVar('Price Set');
2688 $paramsSet['is_active'] = FALSE;
2689 $paramsSet['extends'] = 1;
2690 $paramsSet['min_amount'] = $minAmt;
2691
2692 $priceSet = CRM_Price_BAO_PriceSet::create($paramsSet);
2693 $this->_ids['price_set'] = $priceSet->id;
2694
2695 $paramsField = [
2696 'label' => 'Price Field',
2697 'name' => CRM_Utils_String::titleToVar('Price Field'),
2698 'html_type' => $type,
2699 'price' => $feeTotal,
2700 'option_label' => ['1' => 'Price Field'],
2701 'option_value' => ['1' => $feeTotal],
2702 'option_name' => ['1' => $feeTotal],
2703 'option_weight' => ['1' => 1],
2704 'option_amount' => ['1' => 1],
2705 'is_display_amounts' => 1,
2706 'weight' => 1,
2707 'options_per_line' => 1,
2708 'is_active' => ['1' => 1],
2709 'price_set_id' => $this->_ids['price_set'],
2710 'is_enter_qty' => 1,
2711 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2712 ];
2713 if ($type === 'Radio') {
2714 foreach ($options as $index => $option) {
2715 $paramsField['is_enter_qty'] = 0;
2716 $optionID = $index + 2;
2717 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2718 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2719 }
2720
2721 }
2722 $this->callAPISuccess('PriceField', 'create', $paramsField);
2723 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids['price_set']]);
2724 $this->_ids['price_field'] = array_keys($fields['values']);
2725 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids['price_field'][0]]);
2726 $this->_ids['price_field_value'] = array_keys($fieldValues['values']);
2727
2728 return $this->_ids['price_set'];
2729 }
2730
2731 /**
2732 * Add a profile to a contribution page.
2733 *
2734 * @param string $name
2735 * @param int $contributionPageID
2736 * @param string $module
2737 */
2738 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2739 $params = [
2740 'uf_group_id' => $name,
2741 'module' => $module,
2742 'entity_table' => 'civicrm_contribution_page',
2743 'entity_id' => $contributionPageID,
2744 'weight' => 1,
2745 ];
2746 if ($module !== 'CiviContribute') {
2747 $params['module_data'] = [$module => []];
2748 }
2749 $this->callAPISuccess('UFJoin', 'create', $params);
2750 }
2751
2752 /**
2753 * Add participant with contribution
2754 *
2755 * @return array
2756 *
2757 * @throws \CRM_Core_Exception
2758 */
2759 protected function createPartiallyPaidParticipantOrder() {
2760 $orderParams = $this->getParticipantOrderParams();
2761 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2762 return $this->callAPISuccess('Order', 'create', $orderParams);
2763 }
2764
2765 /**
2766 * Create price set
2767 *
2768 * @param string $component
2769 * @param int $componentId
2770 * @param array $priceFieldOptions
2771 *
2772 * @return array
2773 */
2774 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2775 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2776 $paramsSet['name'] = CRM_Utils_String::titleToVar($paramsSet['title']);
2777 $paramsSet['is_active'] = TRUE;
2778 $paramsSet['financial_type_id'] = 'Event Fee';
2779 $paramsSet['extends'] = 1;
2780 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2781 $priceSetId = $priceSet['id'];
2782 //Checking for priceset added in the table.
2783 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2784 'id', $paramsSet['title'], 'Check DB for created priceset'
2785 );
2786 $paramsField = array_merge([
2787 'label' => 'Price Field',
2788 'name' => CRM_Utils_String::titleToVar('Price Field'),
2789 'html_type' => 'CheckBox',
2790 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2791 'option_value' => ['1' => 100, '2' => 200],
2792 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2793 'option_weight' => ['1' => 1, '2' => 2],
2794 'option_amount' => ['1' => 100, '2' => 200],
2795 'is_display_amounts' => 1,
2796 'weight' => 1,
2797 'options_per_line' => 1,
2798 'is_active' => ['1' => 1, '2' => 1],
2799 'price_set_id' => $priceSet['id'],
2800 'is_enter_qty' => 1,
2801 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2802 ], $priceFieldOptions);
2803
2804 $priceField = CRM_Price_BAO_PriceField::create($paramsField);
2805 if ($componentId) {
2806 CRM_Price_BAO_PriceSet::addTo('civicrm_' . $component, $componentId, $priceSetId);
2807 }
2808 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id]);
2809 }
2810
2811 /**
2812 * Replace the template with a test-oriented template designed to show all the variables.
2813 *
2814 * @param string $templateName
2815 * @param string $type
2816 */
2817 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2818 $testTemplate = file_get_contents(__DIR__ . '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2819 CRM_Core_DAO::executeQuery(
2820 "UPDATE civicrm_msg_template
2821 SET msg_{$type} = %1
2822 WHERE workflow_name = '{$templateName}'
2823 AND is_default = 1", [1 => [$testTemplate, 'String']]
2824 );
2825 }
2826
2827 /**
2828 * Reinstate the default template.
2829 *
2830 * @param string $templateName
2831 * @param string $type
2832 */
2833 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2834 CRM_Core_DAO::executeQuery(
2835 "UPDATE civicrm_option_group og
2836 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2837 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2838 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2839 SET m.msg_{$type} = m2.msg_{$type}
2840 WHERE og.name = 'msg_tpl_workflow_contribution'
2841 AND ov.name = '{$templateName}'
2842 AND m.is_default = 1"
2843 );
2844 }
2845
2846 /**
2847 * Flush statics relating to financial type.
2848 */
2849 protected function flushFinancialTypeStatics() {
2850 if (isset(\Civi::$statics['CRM_Financial_BAO_FinancialType'])) {
2851 unset(\Civi::$statics['CRM_Financial_BAO_FinancialType']);
2852 }
2853 if (isset(\Civi::$statics['CRM_Contribute_PseudoConstant'])) {
2854 unset(\Civi::$statics['CRM_Contribute_PseudoConstant']);
2855 }
2856 CRM_Contribute_PseudoConstant::flush('financialType');
2857 CRM_Contribute_PseudoConstant::flush('membershipType');
2858 // Pseudoconstants may be saved to the cache table.
2859 CRM_Core_DAO::executeQuery("TRUNCATE civicrm_cache");
2860 CRM_Financial_BAO_FinancialType::$_statusACLFt = [];
2861 CRM_Financial_BAO_FinancialType::$_availableFinancialTypes = NULL;
2862 }
2863
2864 /**
2865 * Set the permissions to the supplied array.
2866 *
2867 * @param array $permissions
2868 */
2869 protected function setPermissions($permissions) {
2870 CRM_Core_Config::singleton()->userPermissionClass->permissions = $permissions;
2871 $this->flushFinancialTypeStatics();
2872 }
2873
2874 /**
2875 * @param array $params
2876 * @param $context
2877 */
2878 public function _checkFinancialRecords($params, $context) {
2879 $entityParams = [
2880 'entity_id' => $params['id'],
2881 'entity_table' => 'civicrm_contribution',
2882 ];
2883 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2884 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2885 if ($context == 'pending') {
2886 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
2887 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2888 return;
2889 }
2890 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
2891 $trxnParams = [
2892 'id' => $trxn['financial_trxn_id'],
2893 ];
2894 if ($context != 'online' && $context != 'payLater') {
2895 $compareParams = [
2896 'to_financial_account_id' => 6,
2897 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2898 'status_id' => 1,
2899 ];
2900 }
2901 if ($context == 'feeAmount') {
2902 $compareParams['fee_amount'] = 50;
2903 }
2904 elseif ($context == 'online') {
2905 $compareParams = [
2906 'to_financial_account_id' => 12,
2907 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2908 'status_id' => 1,
2909 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, 1),
2910 ];
2911 }
2912 elseif ($context == 'payLater') {
2913 $compareParams = [
2914 'to_financial_account_id' => 7,
2915 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2916 'status_id' => 2,
2917 ];
2918 }
2919 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2920 $entityParams = [
2921 'financial_trxn_id' => $trxn['financial_trxn_id'],
2922 'entity_table' => 'civicrm_financial_item',
2923 ];
2924 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
2925 $fitemParams = [
2926 'id' => $entityTrxn['entity_id'],
2927 ];
2928 $compareParams = [
2929 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2930 'status_id' => 1,
2931 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
2932 ];
2933 if ($context == 'payLater') {
2934 $compareParams = [
2935 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2936 'status_id' => 3,
2937 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
2938 ];
2939 }
2940 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2941 if ($context == 'feeAmount') {
2942 $maxParams = [
2943 'entity_id' => $params['id'],
2944 'entity_table' => 'civicrm_contribution',
2945 ];
2946 $maxTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($maxParams, TRUE));
2947 $trxnParams = [
2948 'id' => $maxTrxn['financial_trxn_id'],
2949 ];
2950 $compareParams = [
2951 'to_financial_account_id' => 5,
2952 'from_financial_account_id' => 6,
2953 'total_amount' => 50,
2954 'status_id' => 1,
2955 ];
2956 $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['id'], 'DESC');
2957 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2958 $fitemParams = [
2959 'entity_id' => $trxnId['financialTrxnId'],
2960 'entity_table' => 'civicrm_financial_trxn',
2961 ];
2962 $compareParams = [
2963 'amount' => 50.00,
2964 'status_id' => 1,
2965 'financial_account_id' => 5,
2966 ];
2967 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2968 }
2969 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2970 // line should be copied into all the functions that call this function & evaluated there
2971 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2972 // when calling completeTransaction or repeatTransaction.
2973 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2974 }
2975
2976 /**
2977 * Return financial type id on basis of name
2978 *
2979 * @param string $name Financial type m/c name
2980 *
2981 * @return int
2982 */
2983 public function getFinancialTypeId($name) {
2984 return CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2985 }
2986
2987 /**
2988 * Cleanup function for contents of $this->ids.
2989 *
2990 * This is a best effort cleanup to use in tear downs etc.
2991 *
2992 * It will not fail if the data has already been removed (some tests may do
2993 * their own cleanup).
2994 */
2995 protected function cleanUpSetUpIDs() {
2996 foreach ($this->setupIDs as $entity => $id) {
2997 try {
2998 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
2999 }
3000 catch (CiviCRM_API3_Exception $e) {
3001 // This is a best-effort cleanup function, ignore.
3002 }
3003 }
3004 }
3005
3006 /**
3007 * Create Financial Type.
3008 *
3009 * @param array $params
3010 *
3011 * @return array
3012 */
3013 protected function createFinancialType($params = []) {
3014 $params = array_merge($params,
3015 [
3016 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
3017 'is_active' => 1,
3018 ]
3019 );
3020 return $this->callAPISuccess('FinancialType', 'create', $params);
3021 }
3022
3023 /**
3024 * Create Payment Instrument.
3025 *
3026 * @param array $params
3027 * @param string $financialAccountName
3028 *
3029 * @return int
3030 */
3031 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
3032 $params = array_merge([
3033 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3034 'option_group_id' => 'payment_instrument',
3035 'is_active' => 1,
3036 ], $params);
3037 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3038
3039 $relationTypeID = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3040
3041 $financialAccountParams = [
3042 'entity_table' => 'civicrm_option_value',
3043 'entity_id' => $newPaymentInstrument,
3044 'account_relationship' => $relationTypeID,
3045 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3046 ];
3047 CRM_Financial_BAO_FinancialTypeAccount::add($financialAccountParams);
3048
3049 return CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3050 }
3051
3052 /**
3053 * Enable Tax and Invoicing
3054 *
3055 * @param array $params
3056 *
3057 * @return \Civi\Core\SettingsBag
3058 */
3059 protected function enableTaxAndInvoicing($params = []) {
3060 // Enable component contribute setting
3061 $contributeSetting = array_merge($params,
3062 [
3063 'invoicing' => 1,
3064 'invoice_prefix' => 'INV_',
3065 'due_date' => 10,
3066 'due_date_period' => 'days',
3067 'notes' => '',
3068 'is_email_pdf' => 1,
3069 'tax_term' => 'Sales Tax',
3070 'tax_display_settings' => 'Inclusive',
3071 ]
3072 );
3073 return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
3074 }
3075
3076 /**
3077 * Enable Tax and Invoicing
3078 *
3079 * @throws \CRM_Core_Exception
3080 */
3081 protected function disableTaxAndInvoicing() {
3082 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3083 foreach ($accounts as $account) {
3084 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3085 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3086 }
3087
3088 if (!empty(\Civi::$statics['CRM_Core_PseudoConstant']) && isset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3089 unset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates']);
3090 }
3091 return Civi::settings()->set('invoicing', FALSE);
3092 }
3093
3094 /**
3095 * Add Sales Tax relation for financial type with financial account.
3096 *
3097 * @param int $financialTypeId
3098 *
3099 * @return obj
3100 */
3101 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
3102 $params = [
3103 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3104 'financial_account_type_id' => key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3105 'is_deductible' => 1,
3106 'is_tax' => 1,
3107 'tax_rate' => 10,
3108 'is_active' => 1,
3109 ];
3110 $account = CRM_Financial_BAO_FinancialAccount::add($params);
3111 $entityParams = [
3112 'entity_table' => 'civicrm_financial_type',
3113 'entity_id' => $financialTypeId,
3114 'account_relationship' => key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3115 ];
3116
3117 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3118 \Civi::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3119
3120 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3121 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3122 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3123 $dao->copyValues($entityParams);
3124 $dao->find();
3125 if ($dao->fetch()) {
3126 $entityParams['id'] = $dao->id;
3127 }
3128 $entityParams['financial_account_id'] = $account->id;
3129
3130 return CRM_Financial_BAO_FinancialTypeAccount::add($entityParams);
3131 }
3132
3133 /**
3134 * Create price set with contribution test for test setup.
3135 *
3136 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3137 * on parent class at some point (fn is not in 4.4).
3138 *
3139 * @param $entity
3140 * @param array $params
3141 */
3142 public function createPriceSetWithPage($entity = NULL, $params = []) {
3143 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3144 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3145 'title' => "Test Contribution Page",
3146 'financial_type_id' => 1,
3147 'currency' => 'NZD',
3148 'goal_amount' => 50,
3149 'is_pay_later' => 1,
3150 'is_monetary' => TRUE,
3151 'is_email_receipt' => FALSE,
3152 ]);
3153 $priceSet = $this->callAPISuccess('price_set', 'create', [
3154 'is_quick_config' => 0,
3155 'extends' => 'CiviMember',
3156 'financial_type_id' => 1,
3157 'title' => 'my Page',
3158 ]);
3159 $priceSetID = $priceSet['id'];
3160
3161 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3162 $priceField = $this->callAPISuccess('price_field', 'create', [
3163 'price_set_id' => $priceSetID,
3164 'label' => 'Goat Breed',
3165 'html_type' => 'Radio',
3166 ]);
3167 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3168 'price_set_id' => $priceSetID,
3169 'price_field_id' => $priceField['id'],
3170 'label' => 'Long Haired Goat',
3171 'amount' => 20,
3172 'financial_type_id' => 'Donation',
3173 'membership_type_id' => $membershipTypeID,
3174 'membership_num_terms' => 1,
3175 ]);
3176 $this->_ids['price_field_value'] = [$priceFieldValue['id']];
3177 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3178 'price_set_id' => $priceSetID,
3179 'price_field_id' => $priceField['id'],
3180 'label' => 'Shoe-eating Goat',
3181 'amount' => 10,
3182 'financial_type_id' => 'Donation',
3183 'membership_type_id' => $membershipTypeID,
3184 'membership_num_terms' => 2,
3185 ]);
3186 $this->_ids['price_field_value'][] = $priceFieldValue['id'];
3187
3188 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3189 'price_set_id' => $priceSetID,
3190 'price_field_id' => $priceField['id'],
3191 'label' => 'Shoe-eating Goat',
3192 'amount' => 10,
3193 'financial_type_id' => 'Donation',
3194 ]);
3195 $this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];
3196
3197 $this->_ids['price_set'] = $priceSetID;
3198 $this->_ids['contribution_page'] = $contributionPageResult['id'];
3199 $this->_ids['price_field'] = [$priceField['id']];
3200
3201 $this->_ids['membership_type'] = $membershipTypeID;
3202 }
3203
3204 /**
3205 * Only specified contact returned.
3206 *
3207 * @implements CRM_Utils_Hook::aclWhereClause
3208 *
3209 * @param $type
3210 * @param $tables
3211 * @param $whereTables
3212 * @param $contactID
3213 * @param $where
3214 */
3215 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3216 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts) . ")";
3217 }
3218
3219 /**
3220 * @implements CRM_Utils_Hook::selectWhereClause
3221 *
3222 * @param string $entity
3223 * @param array $clauses
3224 */
3225 public function selectWhereClauseHook($entity, &$clauses) {
3226 if ($entity == 'Event') {
3227 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3228 }
3229 }
3230
3231 /**
3232 * An implementation of hook_civicrm_post used with all our test cases.
3233 *
3234 * @param $op
3235 * @param string $objectName
3236 * @param int $objectId
3237 * @param $objectRef
3238 */
3239 public function onPost($op, $objectName, $objectId, &$objectRef) {
3240 if ($op == 'create' && $objectName == 'Individual') {
3241 CRM_Core_DAO::executeQuery(
3242 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3243 [
3244 1 => [$objectId, 'Integer'],
3245 ]
3246 );
3247 }
3248
3249 if ($op == 'edit' && $objectName == 'Participant') {
3250 $params = [
3251 1 => [$objectId, 'Integer'],
3252 ];
3253 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3254 CRM_Core_DAO::executeQuery($query, $params);
3255 }
3256 }
3257
3258 /**
3259 * Instantiate form object.
3260 *
3261 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3262 *
3263 * @param string $class
3264 * Name of form class.
3265 *
3266 * @param array $formValues
3267 *
3268 * @param string $pageName
3269 *
3270 * @return \CRM_Core_Form
3271 * @throws \CRM_Core_Exception
3272 */
3273 public function getFormObject($class, $formValues = [], $pageName = '') {
3274 $_POST = $formValues;
3275 /* @var CRM_Core_Form $form */
3276 $form = new $class();
3277 $_SERVER['REQUEST_METHOD'] = 'GET';
3278 switch ($class) {
3279 case 'CRM_Event_Cart_Form_Checkout_Payment':
3280 case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices':
3281 $form->controller = new CRM_Event_Cart_Controller_Checkout();
3282 break;
3283
3284 default:
3285 $form->controller = new CRM_Core_Controller();
3286 }
3287 if (!$pageName) {
3288 $pageName = $form->getName();
3289 }
3290 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3291 $_SESSION['_' . $form->controller->_name . '_container']['values'][$pageName] = $formValues;
3292 return $form;
3293 }
3294
3295 /**
3296 * Get possible thousand separators.
3297 *
3298 * @return array
3299 */
3300 public function getThousandSeparators() {
3301 return [['.'], [',']];
3302 }
3303
3304 /**
3305 * Get the boolean options as a provider.
3306 *
3307 * @return array
3308 */
3309 public function getBooleanDataProvider() {
3310 return [[TRUE], [FALSE]];
3311 }
3312
3313 /**
3314 * Set the separators for thousands and decimal points.
3315 *
3316 * Note that this only covers some common scenarios.
3317 *
3318 * It does not cater for a situation where the thousand separator is a [space]
3319 * Latter is the Norwegian localization. At least some tests need to
3320 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3321 * to provide broader coverage.
3322 *
3323 * @param string $thousandSeparator
3324 */
3325 protected function setCurrencySeparators($thousandSeparator) {
3326 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3327 Civi::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ? '.' : ','));
3328 }
3329
3330 /**
3331 * Sets the thousand separator.
3332 *
3333 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3334 *
3335 * @param $thousandSeparator
3336 */
3337 protected function setMonetaryThousandSeparator($thousandSeparator) {
3338 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3339 }
3340
3341 /**
3342 * Sets the decimal separator.
3343 *
3344 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3345 *
3346 * @param $decimalPoint
3347 */
3348 protected function setMonetaryDecimalPoint($decimalPoint) {
3349 Civi::settings()->set('monetaryDecimalPoint', $decimalPoint);
3350 }
3351
3352 /**
3353 * Sets the default currency.
3354 *
3355 * @param $currency
3356 */
3357 protected function setDefaultCurrency($currency) {
3358 Civi::settings()->set('defaultCurrency', $currency);
3359 }
3360
3361 /**
3362 * Format money as it would be input.
3363 *
3364 * @param string $amount
3365 *
3366 * @return string
3367 */
3368 protected function formatMoneyInput($amount) {
3369 return CRM_Utils_Money::format($amount, NULL, '%a');
3370 }
3371
3372 /**
3373 * Get the contribution object.
3374 *
3375 * @param int $contributionID
3376 *
3377 * @return \CRM_Contribute_BAO_Contribution
3378 */
3379 protected function getContributionObject($contributionID) {
3380 $contributionObj = new CRM_Contribute_BAO_Contribution();
3381 $contributionObj->id = $contributionID;
3382 $contributionObj->find(TRUE);
3383 return $contributionObj;
3384 }
3385
3386 /**
3387 * Enable multilingual.
3388 */
3389 public function enableMultilingual() {
3390 $this->callAPISuccess('Setting', 'create', [
3391 'lcMessages' => 'en_US',
3392 'languageLimit' => [
3393 'en_US' => 1,
3394 ],
3395 ]);
3396
3397 CRM_Core_I18n_Schema::makeMultilingual('en_US');
3398
3399 global $dbLocale;
3400 $dbLocale = '_en_US';
3401 }
3402
3403 /**
3404 * Setup or clean up SMS tests
3405 *
3406 * @param bool $teardown
3407 *
3408 * @throws \CiviCRM_API3_Exception
3409 */
3410 public function setupForSmsTests($teardown = FALSE) {
3411 require_once 'CiviTest/CiviTestSMSProvider.php';
3412
3413 // Option value params for CiviTestSMSProvider
3414 $groupID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3415 $params = [
3416 'option_group_id' => $groupID,
3417 'label' => 'unittestSMS',
3418 'value' => 'unit.test.sms',
3419 'name' => 'CiviTestSMSProvider',
3420 'is_default' => 1,
3421 'is_active' => 1,
3422 'version' => 3,
3423 ];
3424
3425 if ($teardown) {
3426 // Test completed, delete provider
3427 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3428 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3429 return;
3430 }
3431
3432 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3433 // in CRM/Sms/Provider.php even though it is not an extension.
3434 return civicrm_api3('option_value', 'create', $params);
3435 }
3436
3437 /**
3438 * Start capturing browser output.
3439 *
3440 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3441 */
3442 protected function startCapturingOutput() {
3443 ob_start();
3444 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3445 }
3446
3447 /**
3448 * Stop capturing browser output and return as a csv.
3449 *
3450 * @param bool $isFirstRowHeaders
3451 *
3452 * @return \League\Csv\Reader
3453 *
3454 * @throws \League\Csv\Exception
3455 */
3456 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3457 $output = ob_get_flush();
3458 $stream = fopen('php://memory', 'r+');
3459 fwrite($stream, $output);
3460 rewind($stream);
3461 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3462 $csv = Reader::createFromString($output);
3463 if ($isFirstRowHeaders) {
3464 $csv->setHeaderOffset(0);
3465 }
3466 ob_clean();
3467 return $csv;
3468 }
3469
3470 /**
3471 * Rename various labels to not match the names.
3472 *
3473 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3474 * code that expects it to.
3475 */
3476 protected function renameLabels() {
3477 $replacements = ['Pending', 'Refunded'];
3478 foreach ($replacements as $name) {
3479 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3480 }
3481 }
3482
3483 /**
3484 * Undo any label renaming.
3485 */
3486 protected function resetLabels() {
3487 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3488 }
3489
3490 /**
3491 * Get parameters to set up a multi-line participant order.
3492 *
3493 * @return array
3494 * @throws \CRM_Core_Exception
3495 */
3496 protected function getParticipantOrderParams(): array {
3497 $this->_contactId = $this->individualCreate();
3498 $event = $this->eventCreate();
3499 $this->_eventId = $event['id'];
3500 $eventParams = [
3501 'id' => $this->_eventId,
3502 'financial_type_id' => 4,
3503 'is_monetary' => 1,
3504 ];
3505 $this->callAPISuccess('event', 'create', $eventParams);
3506 $priceFields = $this->createPriceSet('event', $this->_eventId);
3507 $participantParams = [
3508 'financial_type_id' => 4,
3509 'event_id' => $this->_eventId,
3510 'role_id' => 1,
3511 'status_id' => 14,
3512 'fee_currency' => 'USD',
3513 'contact_id' => $this->_contactId,
3514 ];
3515 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3516 $orderParams = [
3517 'total_amount' => 300,
3518 'currency' => 'USD',
3519 'contact_id' => $this->_contactId,
3520 'financial_type_id' => 4,
3521 'contribution_status_id' => 'Pending',
3522 'contribution_mode' => 'participant',
3523 'participant_id' => $participant['id'],
3524 ];
3525 foreach ($priceFields['values'] as $key => $priceField) {
3526 $orderParams['line_items'][] = [
3527 'line_item' => [
3528 [
3529 'price_field_id' => $priceField['price_field_id'],
3530 'price_field_value_id' => $priceField['id'],
3531 'label' => $priceField['label'],
3532 'field_title' => $priceField['label'],
3533 'qty' => 1,
3534 'unit_price' => $priceField['amount'],
3535 'line_total' => $priceField['amount'],
3536 'financial_type_id' => $priceField['financial_type_id'],
3537 'entity_table' => 'civicrm_participant',
3538 ],
3539 ],
3540 'params' => $participant,
3541 ];
3542 }
3543 return $orderParams;
3544 }
3545
3546 /**
3547 * @param $payments
3548 *
3549 * @throws \CRM_Core_Exception
3550 */
3551 protected function validatePayments($payments) {
3552 foreach ($payments as $payment) {
3553 $balance = CRM_Contribute_BAO_Contribution::getContributionBalance($payment['contribution_id']);
3554 if ($balance < 0 && $balance + $payment['total_amount'] === 0.0) {
3555 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3556 // This is a pretty rough way at guessing which payment is the overpayment - but
3557 // for the test suite it should be enough.
3558 continue;
3559 }
3560 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3561 'financial_trxn_id' => $payment['id'],
3562 'entity_table' => 'civicrm_financial_item',
3563 'return' => ['amount'],
3564 ])['values'];
3565 $itemTotal = 0;
3566 foreach ($items as $item) {
3567 $itemTotal += $item['amount'];
3568 }
3569 $this->assertEquals($payment['total_amount'], $itemTotal);
3570 }
3571 }
3572
3573 /**
3574 * Validate all created payments.
3575 *
3576 * @throws \CRM_Core_Exception
3577 */
3578 protected function validateAllPayments() {
3579 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3580 $this->validatePayments($payments);
3581 }
3582
3583 /**
3584 * Validate all created contributions.
3585 *
3586 * @throws \CRM_Core_Exception
3587 */
3588 protected function validateAllContributions() {
3589 $contributions = $this->callAPISuccess('Contribution', 'get', ['return' => ['tax_amount', 'total_amount']])['values'];
3590 foreach ($contributions as $contribution) {
3591 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3592 $total = 0;
3593 $taxTotal = 0;
3594 foreach ($lineItems as $lineItem) {
3595 $total += $lineItem['line_total'];
3596 $taxTotal += (float) ($lineItem['tax_amount'] ?? 0);
3597 }
3598 $this->assertEquals($taxTotal, (float) ($contribution['tax_amount'] ?? 0));
3599 $this->assertEquals($total, $contribution['total_amount']);
3600 }
3601 }
3602
3603 /**
3604 * @return array|int
3605 * @throws \CRM_Core_Exception
3606 */
3607 protected function createRuleGroup() {
3608 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3609 'contact_type' => 'Individual',
3610 'threshold' => 8,
3611 'used' => 'General',
3612 'name' => 'TestRule',
3613 'title' => 'TestRule',
3614 'is_reserved' => 0,
3615 ]);
3616 return $ruleGroup;
3617 }
3618
3619 /**
3620 * Generic create test.
3621 *
3622 * @param int $version
3623 *
3624 * @throws \CRM_Core_Exception
3625 */
3626 protected function basicCreateTest(int $version) {
3627 $this->_apiversion = $version;
3628 $result = $this->callAPIAndDocument($this->_entity, 'create', $this->params, __FUNCTION__, __FILE__);
3629 $this->assertEquals(1, $result['count']);
3630 $this->assertNotNull($result['values'][$result['id']]['id']);
3631 $this->getAndCheck($this->params, $result['id'], $this->_entity);
3632 }
3633
3634 /**
3635 * Generic delete test.
3636 *
3637 * @param int $version
3638 *
3639 * @throws \CRM_Core_Exception
3640 */
3641 protected function basicDeleteTest($version) {
3642 $this->_apiversion = $version;
3643 $result = $this->callAPISuccess($this->_entity, 'create', $this->params);
3644 $deleteParams = ['id' => $result['id']];
3645 $this->callAPIAndDocument($this->_entity, 'delete', $deleteParams, __FUNCTION__, __FILE__);
3646 $checkDeleted = $this->callAPISuccess($this->_entity, 'get', []);
3647 $this->assertEquals(0, $checkDeleted['count']);
3648 }
3649
3650 /**
3651 * Create and return a case object for the given Client ID.
3652 *
3653 * @param int $clientId
3654 * @param int $loggedInUser
3655 * Omit or pass NULL to use the same as clientId
3656 * @param array $extra
3657 * Optional specific parameters such as start_date
3658 *
3659 * @return CRM_Case_BAO_Case
3660 */
3661 public function createCase($clientId, $loggedInUser = NULL, $extra = NULL) {
3662 if (empty($loggedInUser)) {
3663 // backwards compatibility - but it's more typical that the creator is a different person than the client
3664 $loggedInUser = $clientId;
3665 }
3666 $caseParams = [
3667 'activity_subject' => 'Case Subject',
3668 'client_id' => $clientId,
3669 'case_type_id' => 1,
3670 'status_id' => 1,
3671 'case_type' => 'housing_support',
3672 'subject' => 'Case Subject',
3673 'start_date' => ($extra['start_date'] ?? date("Y-m-d")),
3674 'start_date_time' => ($extra['start_date_time'] ?? date("YmdHis")),
3675 'medium_id' => 2,
3676 'activity_details' => '',
3677 ];
3678 $form = new CRM_Case_Form_Case();
3679 return $form->testSubmit($caseParams, 'OpenCase', $loggedInUser, 'standalone');
3680 }
3681
3682 /**
3683 * Validate that all location entities have exactly one primary.
3684 *
3685 * This query takes about 2 minutes on a DB with 10s of millions of contacts.
3686 */
3687 public function assertLocationValidity() {
3688 $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT COUNT(*) FROM
3689
3690 (SELECT a1.contact_id
3691 FROM civicrm_address a1
3692 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3693 AND a1.contact_id = a2.contact_id
3694 WHERE
3695 a1.is_primary = 1
3696 AND a2.id IS NOT NULL
3697 AND a1.contact_id IS NOT NULL
3698 UNION
3699 SELECT a1.contact_id
3700 FROM civicrm_address a1
3701 LEFT JOIN civicrm_address a2 ON a1.id <> a2.id AND a2.is_primary = 1
3702 AND a1.contact_id = a2.contact_id
3703 WHERE a1.is_primary = 0
3704 AND a2.id IS NULL
3705 AND a1.contact_id IS NOT NULL
3706
3707 UNION
3708
3709 SELECT a1.contact_id
3710 FROM civicrm_email a1
3711 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3712 AND a1.contact_id = a2.contact_id
3713 WHERE
3714 a1.is_primary = 1
3715 AND a2.id IS NOT NULL
3716 AND a1.contact_id IS NOT NULL
3717 UNION
3718 SELECT a1.contact_id
3719 FROM civicrm_email a1
3720 LEFT JOIN civicrm_email a2 ON a1.id <> a2.id AND a2.is_primary = 1
3721 AND a1.contact_id = a2.contact_id
3722 WHERE a1.is_primary = 0
3723 AND a2.id IS NULL
3724 AND a1.contact_id IS NOT NULL
3725
3726 UNION
3727
3728 SELECT a1.contact_id
3729 FROM civicrm_phone a1
3730 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3731 AND a1.contact_id = a2.contact_id
3732 WHERE
3733 a1.is_primary = 1
3734 AND a2.id IS NOT NULL
3735 AND a1.contact_id IS NOT NULL
3736 UNION
3737 SELECT a1.contact_id
3738 FROM civicrm_phone a1
3739 LEFT JOIN civicrm_phone a2 ON a1.id <> a2.id AND a2.is_primary = 1
3740 AND a1.contact_id = a2.contact_id
3741 WHERE a1.is_primary = 0
3742 AND a2.id IS NULL
3743 AND a1.contact_id IS NOT NULL
3744
3745 UNION
3746
3747 SELECT a1.contact_id
3748 FROM civicrm_im a1
3749 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3750 AND a1.contact_id = a2.contact_id
3751 WHERE
3752 a1.is_primary = 1
3753 AND a2.id IS NOT NULL
3754 AND a1.contact_id IS NOT NULL
3755 UNION
3756 SELECT a1.contact_id
3757 FROM civicrm_im a1
3758 LEFT JOIN civicrm_im a2 ON a1.id <> a2.id AND a2.is_primary = 1
3759 AND a1.contact_id = a2.contact_id
3760 WHERE a1.is_primary = 0
3761 AND a2.id IS NULL
3762 AND a1.contact_id IS NOT NULL
3763
3764 UNION
3765
3766 SELECT a1.contact_id
3767 FROM civicrm_openid a1
3768 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3769 AND a1.contact_id = a2.contact_id
3770 WHERE (a1.is_primary = 1 AND a2.id IS NOT NULL)
3771 UNION
3772
3773 SELECT a1.contact_id
3774 FROM civicrm_openid a1
3775 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3776 AND a1.contact_id = a2.contact_id
3777 WHERE
3778 a1.is_primary = 1
3779 AND a2.id IS NOT NULL
3780 AND a1.contact_id IS NOT NULL
3781 UNION
3782 SELECT a1.contact_id
3783 FROM civicrm_openid a1
3784 LEFT JOIN civicrm_openid a2 ON a1.id <> a2.id AND a2.is_primary = 1
3785 AND a1.contact_id = a2.contact_id
3786 WHERE a1.is_primary = 0
3787 AND a2.id IS NULL
3788 AND a1.contact_id IS NOT NULL) as primary_descrepancies
3789 '));
3790 }
3791
3792 }