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