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