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