Merge pull request #17719 from civicrm/5.27
[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 * Constructor.
170 *
171 * Because we are overriding the parent class constructor, we
172 * need to show the same arguments as exist in the constructor of
173 * PHPUnit_Framework_TestCase, since
174 * PHPUnit_Framework_TestSuite::createTest() creates a
175 * ReflectionClass of the Test class and checks the constructor
176 * of that class to decide how to set up the test.
177 *
178 * @param string $name
179 * @param array $data
180 * @param string $dataName
181 */
182 public function __construct($name = NULL, array $data = [], $dataName = '') {
183 parent::__construct($name, $data, $dataName);
184
185 // we need full error reporting
186 error_reporting(E_ALL & ~E_NOTICE);
187
188 self::$_dbName = self::getDBName();
189
190 // also load the class loader
191 require_once 'CRM/Core/ClassLoader.php';
192 CRM_Core_ClassLoader::singleton()->register();
193 if (function_exists('_civix_phpunit_setUp')) {
194 // FIXME: loosen coupling
195 _civix_phpunit_setUp();
196 }
197 }
198
199 /**
200 * Override to run the test and assert its state.
201 *
202 * @return mixed
203 * @throws \Exception
204 * @throws \PHPUnit_Framework_IncompleteTest
205 * @throws \PHPUnit_Framework_SkippedTest
206 */
207 protected function runTest() {
208 try {
209 return parent::runTest();
210 }
211 catch (PEAR_Exception $e) {
212 // PEAR_Exception has metadata in funny places, and PHPUnit won't log it nicely
213 throw new Exception(\CRM_Core_Error::formatTextException($e), $e->getCode());
214 }
215 }
216
217 /**
218 * @return bool
219 */
220 public function requireDBReset() {
221 return $this->DBResetRequired;
222 }
223
224 /**
225 * @return string
226 */
227 public static function getDBName() {
228 static $dbName = NULL;
229 if ($dbName === NULL) {
230 require_once "DB.php";
231 $dsninfo = DB::parseDSN(CIVICRM_DSN);
232 $dbName = $dsninfo['database'];
233 }
234 return $dbName;
235 }
236
237 /**
238 * Create database connection for this instance.
239 *
240 * Initialize the test database if it hasn't been initialized
241 *
242 */
243 protected function getConnection() {
244 if (!self::$dbInit) {
245 $dbName = self::getDBName();
246
247 // install test database
248 echo PHP_EOL . "Installing {$dbName} database" . PHP_EOL;
249
250 static::_populateDB(FALSE, $this);
251
252 self::$dbInit = TRUE;
253 }
254
255 }
256
257 /**
258 * Required implementation of abstract method.
259 */
260 protected function getDataSet() {
261 }
262
263 /**
264 * @param bool $perClass
265 * @param null $object
266 *
267 * @return bool
268 * TRUE if the populate logic runs; FALSE if it is skipped
269 */
270 protected static function _populateDB($perClass = FALSE, &$object = NULL) {
271 if (CIVICRM_UF !== 'UnitTests') {
272 throw new \RuntimeException("_populateDB requires CIVICRM_UF=UnitTests");
273 }
274
275 if ($perClass || $object == NULL) {
276 $dbreset = TRUE;
277 }
278 else {
279 $dbreset = $object->requireDBReset();
280 }
281
282 if (self::$populateOnce || !$dbreset) {
283 return FALSE;
284 }
285 self::$populateOnce = NULL;
286
287 Civi\Test::data()->populate();
288
289 return TRUE;
290 }
291
292 public static function setUpBeforeClass() {
293 static::_populateDB(TRUE);
294
295 // also set this global hack
296 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
297 }
298
299 /**
300 * Common setup functions for all unit tests.
301 */
302 protected function setUp() {
303 $session = CRM_Core_Session::singleton();
304 $session->set('userID', NULL);
305
306 $this->_apiversion = 3;
307
308 // REVERT
309 $this->errorScope = CRM_Core_TemporaryErrorScope::useException();
310 // Use a temporary file for STDIN
311 $GLOBALS['stdin'] = tmpfile();
312 if ($GLOBALS['stdin'] === FALSE) {
313 echo "Couldn't open temporary file\n";
314 exit(1);
315 }
316
317 // Get and save a connection to the database
318 $this->_dbconn = $this->getConnection();
319
320 // reload database before each test
321 // $this->_populateDB();
322
323 // "initialize" CiviCRM to avoid problems when running single tests
324 // FIXME: look at it closer in second stage
325
326 $GLOBALS['civicrm_setting']['domain']['fatalErrorHandler'] = 'CiviUnitTestCase_fatalErrorHandler';
327 $GLOBALS['civicrm_setting']['domain']['backtrace'] = 1;
328
329 // disable any left-over test extensions
330 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
331
332 // reset all the caches
333 CRM_Utils_System::flushCache();
334
335 // initialize the object once db is loaded
336 \Civi::$statics = [];
337 // ugh, performance
338 $config = CRM_Core_Config::singleton(TRUE, TRUE);
339
340 // when running unit tests, use mockup user framework
341 $this->hookClass = CRM_Utils_Hook::singleton();
342
343 // Make sure the DB connection is setup properly
344 $config->userSystem->setMySQLTimeZone();
345 $env = new CRM_Utils_Check_Component_Env();
346 CRM_Utils_Check::singleton()->assertValid($env->checkMysqlTime());
347
348 // clear permissions stub to not check permissions
349 $config->userPermissionClass->permissions = NULL;
350
351 //flush component settings
352 CRM_Core_Component::getEnabledComponents(TRUE);
353
354 $_REQUEST = $_GET = $_POST = [];
355 error_reporting(E_ALL);
356
357 $this->renameLabels();
358 $this->_sethtmlGlobals();
359 }
360
361 /**
362 * Read everything from the datasets directory and insert into the db.
363 */
364 public function loadAllFixtures() {
365 $fixturesDir = __DIR__ . '/../../fixtures';
366
367 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
368
369 $jsonFiles = glob($fixturesDir . '/*.json');
370 foreach ($jsonFiles as $jsonFixture) {
371 $json = json_decode(file_get_contents($jsonFixture));
372 foreach ($json as $tableName => $vars) {
373 if ($tableName === 'civicrm_contact') {
374 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');
375 }
376 else {
377 CRM_Core_DAO::executeQuery("TRUNCATE $tableName");
378 }
379 foreach ($vars as $entity) {
380 $keys = $values = [];
381 foreach ($entity as $key => $value) {
382 $keys[] = $key;
383 $values[] = is_numeric($value) ? $value : "'{$value}'";
384 }
385 CRM_Core_DAO::executeQuery("
386 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
387 );
388 }
389
390 }
391 }
392
393 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
394 }
395
396 /**
397 * Load the data that used to be handled by the discontinued dbunit class.
398 *
399 * This could do with further tidy up - the initial priority is simply to get rid of
400 * the dbunity package which is no longer supported.
401 *
402 * @param string $fileName
403 */
404 protected function loadXMLDataSet($fileName) {
405 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 0');
406 $xml = json_decode(json_encode(simplexml_load_file($fileName)), TRUE);
407 foreach ($xml as $tableName => $element) {
408 if (!empty($element)) {
409 foreach ($element as $row) {
410 $keys = $values = [];
411 if (isset($row['@attributes'])) {
412 foreach ($row['@attributes'] as $key => $value) {
413 $keys[] = $key;
414 $values[] = is_numeric($value) ? $value : "'{$value}'";
415 }
416 }
417 elseif (!empty($row)) {
418 // cos we copied it & it is inconsistent....
419 foreach ($row as $key => $value) {
420 $keys[] = $key;
421 $values[] = is_numeric($value) ? $value : "'{$value}'";
422 }
423 }
424
425 if (!empty($values)) {
426 CRM_Core_DAO::executeQuery("
427 INSERT INTO $tableName (" . implode(',', $keys) . ') VALUES(' . implode(',', $values) . ')'
428 );
429 }
430 }
431 }
432 }
433 CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 1');
434 }
435
436 /**
437 * Create default domain contacts for the two domains added during test class.
438 * database population.
439 */
440 public function createDomainContacts() {
441 $this->organizationCreate();
442 $this->organizationCreate(['organization_name' => 'Second Domain']);
443 }
444
445 /**
446 * Common teardown functions for all unit tests.
447 */
448 protected function tearDown() {
449 $this->_apiversion = 3;
450 $this->resetLabels();
451
452 error_reporting(E_ALL & ~E_NOTICE);
453 CRM_Utils_Hook::singleton()->reset();
454 if ($this->hookClass) {
455 $this->hookClass->reset();
456 }
457 CRM_Core_Session::singleton()->reset(1);
458
459 if ($this->tx) {
460 $this->tx->rollback()->commit();
461 $this->tx = NULL;
462
463 CRM_Core_Transaction::forceRollbackIfEnabled();
464 \Civi\Core\Transaction\Manager::singleton(TRUE);
465 }
466 else {
467 CRM_Core_Transaction::forceRollbackIfEnabled();
468 \Civi\Core\Transaction\Manager::singleton(TRUE);
469
470 $tablesToTruncate = ['civicrm_contact', 'civicrm_uf_match'];
471 $this->quickCleanup($tablesToTruncate);
472 $this->createDomainContacts();
473 }
474
475 $this->cleanTempDirs();
476 $this->unsetExtensionSystem();
477 $this->assertEquals([], CRM_Core_DAO::$_nullArray);
478 $this->assertEquals(NULL, CRM_Core_DAO::$_nullObject);
479 }
480
481 /**
482 * Create a batch of external API calls which can
483 * be executed concurrently.
484 *
485 * ```
486 * $calls = $this->createExternalAPI()
487 * ->addCall('Contact', 'get', ...)
488 * ->addCall('Contact', 'get', ...)
489 * ...
490 * ->run()
491 * ->getResults();
492 * ```
493 *
494 * @return \Civi\API\ExternalBatch
495 * @throws PHPUnit_Framework_SkippedTestError
496 */
497 public function createExternalAPI() {
498 global $civicrm_root;
499 $defaultParams = [
500 'version' => $this->_apiversion,
501 'debug' => 1,
502 ];
503
504 $calls = new \Civi\API\ExternalBatch($defaultParams);
505
506 if (!$calls->isSupported()) {
507 $this->markTestSkipped('The test relies on Civi\API\ExternalBatch. This is unsupported in the local environment.');
508 }
509
510 return $calls;
511 }
512
513 /**
514 * Create required data based on $this->entity & $this->params
515 * This is just a way to set up the test data for delete & get functions
516 * so the distinction between set
517 * up & tested functions is clearer
518 *
519 * @return array
520 * api Result
521 */
522 public function createTestEntity() {
523 return $entity = $this->callAPISuccess($this->entity, 'create', $this->params);
524 }
525
526 /**
527 * @param int $contactTypeId
528 *
529 * @throws Exception
530 */
531 public function contactTypeDelete($contactTypeId) {
532 $result = CRM_Contact_BAO_ContactType::del($contactTypeId);
533 if (!$result) {
534 throw new Exception('Could not delete contact type');
535 }
536 }
537
538 /**
539 * @param array $params
540 *
541 * @return mixed
542 */
543 public function membershipTypeCreate($params = []) {
544 CRM_Member_PseudoConstant::flush('membershipType');
545 CRM_Core_Config::clearDBCache();
546 $this->setupIDs['contact'] = $memberOfOrganization = $this->organizationCreate();
547 $params = array_merge([
548 'name' => 'General',
549 'duration_unit' => 'year',
550 'duration_interval' => 1,
551 'period_type' => 'rolling',
552 'member_of_contact_id' => $memberOfOrganization,
553 'domain_id' => 1,
554 'financial_type_id' => 2,
555 'is_active' => 1,
556 'sequential' => 1,
557 'visibility' => 'Public',
558 ], $params);
559
560 $result = $this->callAPISuccess('MembershipType', 'Create', $params);
561
562 CRM_Member_PseudoConstant::flush('membershipType');
563 CRM_Utils_Cache::singleton()->flush();
564
565 return $result['id'];
566 }
567
568 /**
569 * Create membership.
570 *
571 * @param array $params
572 *
573 * @return int
574 * @throws \CRM_Core_Exception
575 */
576 public function contactMembershipCreate($params) {
577 $params = array_merge([
578 'join_date' => '2007-01-21',
579 'start_date' => '2007-01-21',
580 'end_date' => '2007-12-21',
581 'source' => 'Payment',
582 'membership_type_id' => 'General',
583 ], $params);
584 if (!is_numeric($params['membership_type_id'])) {
585 $membershipTypes = $this->callAPISuccess('Membership', 'getoptions', ['action' => 'create', 'field' => 'membership_type_id']);
586 if (!in_array($params['membership_type_id'], $membershipTypes['values'], TRUE)) {
587 $this->membershipTypeCreate(['name' => $params['membership_type_id']]);
588 }
589 }
590
591 $result = $this->callAPISuccess('Membership', 'create', $params);
592 return $result['id'];
593 }
594
595 /**
596 * Delete Membership Type.
597 *
598 * @param array $params
599 */
600 public function membershipTypeDelete($params) {
601 $this->callAPISuccess('MembershipType', 'Delete', $params);
602 }
603
604 /**
605 * @param int $membershipID
606 */
607 public function membershipDelete($membershipID) {
608 $deleteParams = ['id' => $membershipID];
609 $result = $this->callAPISuccess('Membership', 'Delete', $deleteParams);
610 }
611
612 /**
613 * @param string $name
614 *
615 * @return mixed
616 *
617 * @throws \CRM_Core_Exception
618 */
619 public function membershipStatusCreate($name = 'test member status') {
620 $params['name'] = $name;
621 $params['start_event'] = 'start_date';
622 $params['end_event'] = 'end_date';
623 $params['is_current_member'] = 1;
624 $params['is_active'] = 1;
625
626 $result = $this->callAPISuccess('MembershipStatus', 'Create', $params);
627 CRM_Member_PseudoConstant::flush('membershipStatus');
628 return (int) $result['id'];
629 }
630
631 /**
632 * Delete the given membership status, deleting any memberships of the status first.
633 *
634 * @param int $membershipStatusID
635 *
636 * @throws \CRM_Core_Exception
637 */
638 public function membershipStatusDelete(int $membershipStatusID) {
639 $this->callAPISuccess('Membership', 'get', ['status_id' => $membershipStatusID, 'api.Membership.delete' => 1]);
640 $this->callAPISuccess('MembershipStatus', 'Delete', ['id' => $membershipStatusID]);
641 }
642
643 public function membershipRenewalDate($durationUnit, $membershipEndDate) {
644 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
645 $renewedMembershipEndDate = new DateTime($membershipEndDate);
646 switch ($durationUnit) {
647 case 'year':
648 $renewedMembershipEndDate->add(new DateInterval('P1Y'));
649 break;
650
651 case 'month':
652 // We have to add 1 day first in case it's the end of the month, then subtract afterwards
653 // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28
654 $renewedMembershipEndDate->add(new DateInterval('P1D'));
655 $renewedMembershipEndDate->add(new DateInterval('P1M'));
656 $renewedMembershipEndDate->sub(new DateInterval('P1D'));
657 break;
658 }
659 return $renewedMembershipEndDate->format('Y-m-d');
660 }
661
662 /**
663 * Create a relationship type.
664 *
665 * @param array $params
666 *
667 * @return int
668 *
669 * @throws \CRM_Core_Exception
670 */
671 public function relationshipTypeCreate($params = []) {
672 $params = array_merge([
673 'name_a_b' => 'Relation 1 for relationship type create',
674 'name_b_a' => 'Relation 2 for relationship type create',
675 'contact_type_a' => 'Individual',
676 'contact_type_b' => 'Organization',
677 'is_reserved' => 1,
678 'is_active' => 1,
679 ], $params);
680
681 $result = $this->callAPISuccess('relationship_type', 'create', $params);
682 CRM_Core_PseudoConstant::flush('relationshipType');
683
684 return $result['id'];
685 }
686
687 /**
688 * Delete Relatinship Type.
689 *
690 * @param int $relationshipTypeID
691 */
692 public function relationshipTypeDelete($relationshipTypeID) {
693 $params['id'] = $relationshipTypeID;
694 $check = $this->callAPISuccess('relationship_type', 'get', $params);
695 if (!empty($check['count'])) {
696 $this->callAPISuccess('relationship_type', 'delete', $params);
697 }
698 }
699
700 /**
701 * @param array $params
702 *
703 * @return mixed
704 * @throws \CRM_Core_Exception
705 */
706 public function paymentProcessorTypeCreate($params = NULL) {
707 if (is_null($params)) {
708 $params = [
709 'name' => 'API_Test_PP',
710 'title' => 'API Test Payment Processor',
711 'class_name' => 'CRM_Core_Payment_APITest',
712 'billing_mode' => 'form',
713 'is_recur' => 0,
714 'is_reserved' => 1,
715 'is_active' => 1,
716 ];
717 }
718 $result = $this->callAPISuccess('payment_processor_type', 'create', $params);
719
720 CRM_Core_PseudoConstant::flush('paymentProcessorType');
721
722 return $result['id'];
723 }
724
725 /**
726 * Create test Authorize.net instance.
727 *
728 * @param array $params
729 *
730 * @return mixed
731 */
732 public function paymentProcessorAuthorizeNetCreate($params = []) {
733 $params = array_merge([
734 'name' => 'Authorize',
735 'domain_id' => CRM_Core_Config::domainID(),
736 'payment_processor_type_id' => 'AuthNet',
737 'title' => 'AuthNet',
738 'is_active' => 1,
739 'is_default' => 0,
740 'is_test' => 1,
741 'is_recur' => 1,
742 'user_name' => '4y5BfuW7jm',
743 'password' => '4cAmW927n8uLf5J8',
744 'url_site' => 'https://test.authorize.net/gateway/transact.dll',
745 'url_recur' => 'https://apitest.authorize.net/xml/v1/request.api',
746 'class_name' => 'Payment_AuthorizeNet',
747 'billing_mode' => 1,
748 ], $params);
749
750 $result = $this->callAPISuccess('PaymentProcessor', 'create', $params);
751 return $result['id'];
752 }
753
754 /**
755 * Create Participant.
756 *
757 * @param array $params
758 * Array of contact id and event id values.
759 *
760 * @return int
761 * $id of participant created
762 */
763 public function participantCreate($params = []) {
764 if (empty($params['contact_id'])) {
765 $params['contact_id'] = $this->individualCreate();
766 }
767 if (empty($params['event_id'])) {
768 $event = $this->eventCreate();
769 $params['event_id'] = $event['id'];
770 }
771 $defaults = [
772 'status_id' => 2,
773 'role_id' => 1,
774 'register_date' => 20070219,
775 'source' => 'Wimbeldon',
776 'event_level' => 'Payment',
777 'debug' => 1,
778 ];
779
780 $params = array_merge($defaults, $params);
781 $result = $this->callAPISuccess('Participant', 'create', $params);
782 return $result['id'];
783 }
784
785 /**
786 * Create Payment Processor.
787 *
788 * @return int
789 * Id Payment Processor
790 */
791 public function processorCreate($params = []) {
792 $processorParams = [
793 'domain_id' => 1,
794 'name' => 'Dummy',
795 'payment_processor_type_id' => 'Dummy',
796 'financial_account_id' => 12,
797 'is_test' => TRUE,
798 'is_active' => 1,
799 'user_name' => '',
800 'url_site' => 'http://dummy.com',
801 'url_recur' => 'http://dummy.com',
802 'billing_mode' => 1,
803 'sequential' => 1,
804 'payment_instrument_id' => 'Debit Card',
805 ];
806 $processorParams = array_merge($processorParams, $params);
807 $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
808 return $processor['id'];
809 }
810
811 /**
812 * Create Payment Processor.
813 *
814 * @param array $processorParams
815 *
816 * @return \CRM_Core_Payment_Dummy
817 * Instance of Dummy Payment Processor
818 *
819 * @throws \CiviCRM_API3_Exception
820 */
821 public function dummyProcessorCreate($processorParams = []) {
822 $paymentProcessorID = $this->processorCreate($processorParams);
823 // 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
824 // Otherwise we are testing a scenario that only exists in tests (and some tests fail because the live processor has not been defined).
825 $processorParams['is_test'] = FALSE;
826 $this->processorCreate($processorParams);
827 return System::singleton()->getById($paymentProcessorID);
828 }
829
830 /**
831 * Create contribution page.
832 *
833 * @param array $params
834 *
835 * @return array
836 * Array of contribution page
837 */
838 public function contributionPageCreate($params = []) {
839 $this->_pageParams = array_merge([
840 'title' => 'Test Contribution Page',
841 'financial_type_id' => 1,
842 'currency' => 'USD',
843 'financial_account_id' => 1,
844 'is_active' => 1,
845 'is_allow_other_amount' => 1,
846 'min_amount' => 10,
847 'max_amount' => 1000,
848 ], $params);
849 return $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
850 }
851
852 /**
853 * Create a sample batch.
854 */
855 public function batchCreate() {
856 $params = $this->_params;
857 $params['name'] = $params['title'] = 'Batch_433397';
858 $params['status_id'] = 1;
859 $result = $this->callAPISuccess('batch', 'create', $params);
860 return $result['id'];
861 }
862
863 /**
864 * Create Tag.
865 *
866 * @param array $params
867 *
868 * @return array
869 * result of created tag
870 */
871 public function tagCreate($params = []) {
872 $defaults = [
873 'name' => 'New Tag3',
874 'description' => 'This is description for Our New Tag ',
875 'domain_id' => '1',
876 ];
877 $params = array_merge($defaults, $params);
878 $result = $this->callAPISuccess('Tag', 'create', $params);
879 return $result['values'][$result['id']];
880 }
881
882 /**
883 * Delete Tag.
884 *
885 * @param int $tagId
886 * Id of the tag to be deleted.
887 *
888 * @return int
889 */
890 public function tagDelete($tagId) {
891 require_once 'api/api.php';
892 $params = [
893 'tag_id' => $tagId,
894 ];
895 $result = $this->callAPISuccess('Tag', 'delete', $params);
896 return $result['id'];
897 }
898
899 /**
900 * Add entity(s) to the tag
901 *
902 * @param array $params
903 *
904 * @return bool
905 */
906 public function entityTagAdd($params) {
907 $result = $this->callAPISuccess('entity_tag', 'create', $params);
908 return TRUE;
909 }
910
911 /**
912 * Create pledge.
913 *
914 * @param array $params
915 * Parameters.
916 *
917 * @return int
918 * id of created pledge
919 *
920 * @throws \CRM_Core_Exception
921 */
922 public function pledgeCreate($params) {
923 $params = array_merge([
924 'pledge_create_date' => date('Ymd'),
925 'start_date' => date('Ymd'),
926 'scheduled_date' => date('Ymd'),
927 'amount' => 100.00,
928 'pledge_status_id' => '2',
929 'financial_type_id' => '1',
930 'pledge_original_installment_amount' => 20,
931 'frequency_interval' => 5,
932 'frequency_unit' => 'year',
933 'frequency_day' => 15,
934 'installments' => 5,
935 ],
936 $params);
937
938 $result = $this->callAPISuccess('Pledge', 'create', $params);
939 return $result['id'];
940 }
941
942 /**
943 * Delete contribution.
944 *
945 * @param int $pledgeId
946 *
947 * @throws \CRM_Core_Exception
948 */
949 public function pledgeDelete($pledgeId) {
950 $params = [
951 'pledge_id' => $pledgeId,
952 ];
953 $this->callAPISuccess('Pledge', 'delete', $params);
954 }
955
956 /**
957 * Create contribution.
958 *
959 * @param array $params
960 * Array of parameters.
961 *
962 * @return int
963 * id of created contribution
964 * @throws \CRM_Core_Exception
965 */
966 public function contributionCreate($params) {
967
968 $params = array_merge([
969 'domain_id' => 1,
970 'receive_date' => date('Ymd'),
971 'total_amount' => 100.00,
972 'fee_amount' => 5.00,
973 'financial_type_id' => 1,
974 'payment_instrument_id' => 1,
975 'non_deductible_amount' => 10.00,
976 'source' => 'SSF',
977 'contribution_status_id' => 1,
978 ], $params);
979
980 $result = $this->callAPISuccess('contribution', 'create', $params);
981 return $result['id'];
982 }
983
984 /**
985 * Delete contribution.
986 *
987 * @param int $contributionId
988 *
989 * @return array|int
990 * @throws \CRM_Core_Exception
991 */
992 public function contributionDelete($contributionId) {
993 $params = [
994 'contribution_id' => $contributionId,
995 ];
996 $result = $this->callAPISuccess('contribution', 'delete', $params);
997 return $result;
998 }
999
1000 /**
1001 * Create an Event.
1002 *
1003 * @param array $params
1004 * Name-value pair for an event.
1005 *
1006 * @return array
1007 * @throws \CRM_Core_Exception
1008 */
1009 public function eventCreate($params = []) {
1010 // if no contact was passed, make up a dummy event creator
1011 if (!isset($params['contact_id'])) {
1012 $params['contact_id'] = $this->_contactCreate([
1013 'contact_type' => 'Individual',
1014 'first_name' => 'Event',
1015 'last_name' => 'Creator',
1016 ]);
1017 }
1018
1019 // set defaults for missing params
1020 $params = array_merge([
1021 'title' => 'Annual CiviCRM meet',
1022 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
1023 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
1024 'event_type_id' => 1,
1025 'is_public' => 1,
1026 'start_date' => 20081021,
1027 'end_date' => 20081023,
1028 'is_online_registration' => 1,
1029 'registration_start_date' => 20080601,
1030 'registration_end_date' => 20081015,
1031 'max_participants' => 100,
1032 'event_full_text' => 'Sorry! We are already full',
1033 'is_monetary' => 0,
1034 'is_active' => 1,
1035 'is_show_location' => 0,
1036 'is_email_confirm' => 1,
1037 ], $params);
1038
1039 return $this->callAPISuccess('Event', 'create', $params);
1040 }
1041
1042 /**
1043 * Create a paid event.
1044 *
1045 * @param array $params
1046 *
1047 * @param array $options
1048 *
1049 * @param string $key
1050 * Index for storing event ID in ids array.
1051 *
1052 * @return array
1053 *
1054 * @throws \CRM_Core_Exception
1055 */
1056 protected function eventCreatePaid($params, $options = [['name' => 'hundy', 'amount' => 100]], $key = 'event') {
1057 $event = $this->eventCreate($params);
1058 $this->ids['event'][$key] = (int) $event['id'];
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 = $priceSet[$this->priceSetID] ?? NULL;
1063 $this->eventFeeBlock = $priceSet['fields'] ?? NULL;
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(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1288 $savedSearch = CRM_Contact_BAO_SavedSearch::create($smartGroupParams);
1289
1290 $groupParams['saved_search_id'] = $savedSearch->id;
1291 return $this->groupCreate($groupParams);
1292 }
1293
1294 /**
1295 * Create a UFField.
1296 *
1297 * @param array $params
1298 */
1299 public function uFFieldCreate($params = []) {
1300 $params = array_merge([
1301 'uf_group_id' => 1,
1302 'field_name' => 'first_name',
1303 'is_active' => 1,
1304 'is_required' => 1,
1305 'visibility' => 'Public Pages and Listings',
1306 'is_searchable' => '1',
1307 'label' => 'first_name',
1308 'field_type' => 'Individual',
1309 'weight' => 1,
1310 ], $params);
1311 $this->callAPISuccess('uf_field', 'create', $params);
1312 }
1313
1314 /**
1315 * Add a UF Join Entry.
1316 *
1317 * @param array $params
1318 *
1319 * @return int
1320 * $id of created UF Join
1321 */
1322 public function ufjoinCreate($params = NULL) {
1323 if ($params === NULL) {
1324 $params = [
1325 'is_active' => 1,
1326 'module' => 'CiviEvent',
1327 'entity_table' => 'civicrm_event',
1328 'entity_id' => 3,
1329 'weight' => 1,
1330 'uf_group_id' => 1,
1331 ];
1332 }
1333 $result = $this->callAPISuccess('uf_join', 'create', $params);
1334 return $result;
1335 }
1336
1337 /**
1338 * @param array $params
1339 * Optional parameters.
1340 * @param bool $reloadConfig
1341 * While enabling CiviCampaign component, we shouldn't always forcibly
1342 * reload config as this hinder hook call in test environment
1343 *
1344 * @return int
1345 * Campaign ID.
1346 */
1347 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1348 $this->enableCiviCampaign($reloadConfig);
1349 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1350 'name' => 'big_campaign',
1351 'title' => 'Campaign',
1352 ], $params));
1353 return $campaign['id'];
1354 }
1355
1356 /**
1357 * Create Group for a contact.
1358 *
1359 * @param int $contactId
1360 */
1361 public function contactGroupCreate($contactId) {
1362 $params = [
1363 'contact_id.1' => $contactId,
1364 'group_id' => 1,
1365 ];
1366
1367 $this->callAPISuccess('GroupContact', 'Create', $params);
1368 }
1369
1370 /**
1371 * Delete Group for a contact.
1372 *
1373 * @param int $contactId
1374 */
1375 public function contactGroupDelete($contactId) {
1376 $params = [
1377 'contact_id.1' => $contactId,
1378 'group_id' => 1,
1379 ];
1380 $this->civicrm_api('GroupContact', 'Delete', $params);
1381 }
1382
1383 /**
1384 * Create Activity.
1385 *
1386 * @param array $params
1387 *
1388 * @return array|int
1389 *
1390 * @throws \CRM_Core_Exception
1391 * @throws \CiviCRM_API3_Exception
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_pcp_block',
1843 'civicrm_pcp',
1844 'civicrm_pledge_block',
1845 'civicrm_pledge_payment',
1846 'civicrm_price_set_entity',
1847 'civicrm_price_field_value',
1848 'civicrm_price_field',
1849 ];
1850 $this->quickCleanup($tablesToTruncate);
1851 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1852 $this->restoreDefaultPriceSetConfig();
1853 $this->disableTaxAndInvoicing();
1854 $this->setCurrencySeparators(',');
1855 CRM_Core_PseudoConstant::flush('taxRates');
1856 System::singleton()->flushProcessors();
1857 // @fixme this parameter is leaking - it should not be defined as a class static
1858 // but for now we just handle in tear down.
1859 CRM_Contribute_BAO_Query::$_contribOrSoftCredit = 'only contribs';
1860 }
1861
1862 /**
1863 * Reset the price set config so results exist.
1864 */
1865 public function restoreDefaultPriceSetConfig() {
1866 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1867 CRM_Core_DAO::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1868 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)");
1869 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)");
1870 }
1871
1872 /**
1873 * Recreate default membership types.
1874 */
1875 public function restoreMembershipTypes() {
1876 CRM_Core_DAO::executeQuery(
1877 "REPLACE INTO civicrm_membership_type
1878 (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)
1879 VALUES
1880 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1881 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1882 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1883 ");
1884 }
1885
1886 /*
1887 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1888 * Default behaviour is to also delete the entity
1889 * @param array $params
1890 * Params array to check against.
1891 * @param int $id
1892 * Id of the entity concerned.
1893 * @param string $entity
1894 * Name of entity concerned (e.g. membership).
1895 * @param bool $delete
1896 * Should the entity be deleted as part of this check.
1897 * @param string $errorText
1898 * Text to print on error.
1899 */
1900
1901 /**
1902 * @param array $params
1903 * @param int $id
1904 * @param $entity
1905 * @param int $delete
1906 * @param string $errorText
1907 *
1908 * @throws CRM_Core_Exception
1909 */
1910 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1911
1912 $result = $this->callAPISuccessGetSingle($entity, [
1913 'id' => $id,
1914 ]);
1915
1916 if ($delete) {
1917 $this->callAPISuccess($entity, 'Delete', [
1918 'id' => $id,
1919 ]);
1920 }
1921 $dateFields = $keys = $dateTimeFields = [];
1922 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1923 foreach ($fields['values'] as $field => $settings) {
1924 if (array_key_exists($field, $result)) {
1925 $keys[CRM_Utils_Array::value('name', $settings, $field)] = $field;
1926 }
1927 else {
1928 $keys[CRM_Utils_Array::value('name', $settings, $field)] = CRM_Utils_Array::value('name', $settings, $field);
1929 }
1930 $type = $settings['type'] ?? NULL;
1931 if ($type == CRM_Utils_Type::T_DATE) {
1932 $dateFields[] = $settings['name'];
1933 // we should identify both real names & unique names as dates
1934 if ($field != $settings['name']) {
1935 $dateFields[] = $field;
1936 }
1937 }
1938 if ($type == CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME) {
1939 $dateTimeFields[] = $settings['name'];
1940 // we should identify both real names & unique names as dates
1941 if ($field != $settings['name']) {
1942 $dateTimeFields[] = $field;
1943 }
1944 }
1945 }
1946
1947 if (strtolower($entity) == 'contribution') {
1948 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1949 // this is not returned in id format
1950 unset($params['payment_instrument_id']);
1951 $params['contribution_source'] = $params['source'];
1952 unset($params['source']);
1953 }
1954
1955 foreach ($params as $key => $value) {
1956 if ($key == 'version' || substr($key, 0, 3) == 'api' || !array_key_exists($keys[$key], $result)) {
1957 continue;
1958 }
1959 if (in_array($key, $dateFields)) {
1960 $value = date('Y-m-d', strtotime($value));
1961 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1962 }
1963 if (in_array($key, $dateTimeFields)) {
1964 $value = date('Y-m-d H:i:s', strtotime($value));
1965 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array::value($keys[$key], $result, CRM_Utils_Array::value($key, $result))));
1966 }
1967 $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);
1968 }
1969 }
1970
1971 /**
1972 * Get formatted values in the actual and expected result.
1973 *
1974 * @param array $actual
1975 * Actual calculated values.
1976 * @param array $expected
1977 * Expected values.
1978 */
1979 public function checkArrayEquals(&$actual, &$expected) {
1980 self::unsetId($actual);
1981 self::unsetId($expected);
1982 $this->assertEquals($expected, $actual);
1983 }
1984
1985 /**
1986 * Unset the key 'id' from the array
1987 *
1988 * @param array $unformattedArray
1989 * The array from which the 'id' has to be unset.
1990 */
1991 public static function unsetId(&$unformattedArray) {
1992 $formattedArray = [];
1993 if (array_key_exists('id', $unformattedArray)) {
1994 unset($unformattedArray['id']);
1995 }
1996 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
1997 foreach ($unformattedArray['values'] as $key => $value) {
1998 if (is_array($value)) {
1999 foreach ($value as $k => $v) {
2000 if ($k == 'id') {
2001 unset($value[$k]);
2002 }
2003 }
2004 }
2005 elseif ($key == 'id') {
2006 $unformattedArray[$key];
2007 }
2008 $formattedArray = [$value];
2009 }
2010 $unformattedArray['values'] = $formattedArray;
2011 }
2012 }
2013
2014 /**
2015 * Helper to enable/disable custom directory support
2016 *
2017 * @param array $customDirs
2018 * With members:.
2019 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2020 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2021 */
2022 public function customDirectories($customDirs) {
2023 $config = CRM_Core_Config::singleton();
2024
2025 if (empty($customDirs['php_path']) || $customDirs['php_path'] === FALSE) {
2026 unset($config->customPHPPathDir);
2027 }
2028 elseif ($customDirs['php_path'] === TRUE) {
2029 $config->customPHPPathDir = dirname(dirname(__FILE__)) . '/custom_directories/php/';
2030 }
2031 else {
2032 $config->customPHPPathDir = $php_path;
2033 }
2034
2035 if (empty($customDirs['template_path']) || $customDirs['template_path'] === FALSE) {
2036 unset($config->customTemplateDir);
2037 }
2038 elseif ($customDirs['template_path'] === TRUE) {
2039 $config->customTemplateDir = dirname(dirname(__FILE__)) . '/custom_directories/templates/';
2040 }
2041 else {
2042 $config->customTemplateDir = $template_path;
2043 }
2044 }
2045
2046 /**
2047 * Generate a temporary folder.
2048 *
2049 * @param string $prefix
2050 *
2051 * @return string
2052 */
2053 public function createTempDir($prefix = 'test-') {
2054 $tempDir = CRM_Utils_File::tempdir($prefix);
2055 $this->tempDirs[] = $tempDir;
2056 return $tempDir;
2057 }
2058
2059 public function cleanTempDirs() {
2060 if (!is_array($this->tempDirs)) {
2061 // fix test errors where this is not set
2062 return;
2063 }
2064 foreach ($this->tempDirs as $tempDir) {
2065 if (is_dir($tempDir)) {
2066 CRM_Utils_File::cleanDir($tempDir, TRUE, FALSE);
2067 }
2068 }
2069 }
2070
2071 /**
2072 * Temporarily replace the singleton extension with a different one.
2073 *
2074 * @param \CRM_Extension_System $system
2075 */
2076 public function setExtensionSystem(CRM_Extension_System $system) {
2077 if ($this->origExtensionSystem == NULL) {
2078 $this->origExtensionSystem = CRM_Extension_System::singleton();
2079 }
2080 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2081 }
2082
2083 public function unsetExtensionSystem() {
2084 if ($this->origExtensionSystem !== NULL) {
2085 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2086 $this->origExtensionSystem = NULL;
2087 }
2088 }
2089
2090 /**
2091 * Temporarily alter the settings-metadata to add a mock setting.
2092 *
2093 * WARNING: The setting metadata will disappear on the next cache-clear.
2094 *
2095 * @param $extras
2096 *
2097 * @return void
2098 */
2099 public function setMockSettingsMetaData($extras) {
2100 CRM_Utils_Hook::singleton()
2101 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2102 $metadata = array_merge($metadata, $extras);
2103 });
2104
2105 Civi::service('settings_manager')->flush();
2106
2107 $fields = $this->callAPISuccess('setting', 'getfields', []);
2108 foreach ($extras as $key => $spec) {
2109 $this->assertNotEmpty($spec['title']);
2110 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2111 }
2112 }
2113
2114 /**
2115 * @param string $name
2116 */
2117 public function financialAccountDelete($name) {
2118 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2119 $financialAccount->name = $name;
2120 if ($financialAccount->find(TRUE)) {
2121 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2122 $entityFinancialType->financial_account_id = $financialAccount->id;
2123 $entityFinancialType->delete();
2124 $financialAccount->delete();
2125 }
2126 }
2127
2128 /**
2129 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2130 * (NB unclear if this is still required)
2131 */
2132 public function _sethtmlGlobals() {
2133 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2134 'required' => [
2135 'html_quickform_rule_required',
2136 'HTML/QuickForm/Rule/Required.php',
2137 ],
2138 'maxlength' => [
2139 'html_quickform_rule_range',
2140 'HTML/QuickForm/Rule/Range.php',
2141 ],
2142 'minlength' => [
2143 'html_quickform_rule_range',
2144 'HTML/QuickForm/Rule/Range.php',
2145 ],
2146 'rangelength' => [
2147 'html_quickform_rule_range',
2148 'HTML/QuickForm/Rule/Range.php',
2149 ],
2150 'email' => [
2151 'html_quickform_rule_email',
2152 'HTML/QuickForm/Rule/Email.php',
2153 ],
2154 'regex' => [
2155 'html_quickform_rule_regex',
2156 'HTML/QuickForm/Rule/Regex.php',
2157 ],
2158 'lettersonly' => [
2159 'html_quickform_rule_regex',
2160 'HTML/QuickForm/Rule/Regex.php',
2161 ],
2162 'alphanumeric' => [
2163 'html_quickform_rule_regex',
2164 'HTML/QuickForm/Rule/Regex.php',
2165 ],
2166 'numeric' => [
2167 'html_quickform_rule_regex',
2168 'HTML/QuickForm/Rule/Regex.php',
2169 ],
2170 'nopunctuation' => [
2171 'html_quickform_rule_regex',
2172 'HTML/QuickForm/Rule/Regex.php',
2173 ],
2174 'nonzero' => [
2175 'html_quickform_rule_regex',
2176 'HTML/QuickForm/Rule/Regex.php',
2177 ],
2178 'callback' => [
2179 'html_quickform_rule_callback',
2180 'HTML/QuickForm/Rule/Callback.php',
2181 ],
2182 'compare' => [
2183 'html_quickform_rule_compare',
2184 'HTML/QuickForm/Rule/Compare.php',
2185 ],
2186 ];
2187 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2188 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2189 'group' => [
2190 'HTML/QuickForm/group.php',
2191 'HTML_QuickForm_group',
2192 ],
2193 'hidden' => [
2194 'HTML/QuickForm/hidden.php',
2195 'HTML_QuickForm_hidden',
2196 ],
2197 'reset' => [
2198 'HTML/QuickForm/reset.php',
2199 'HTML_QuickForm_reset',
2200 ],
2201 'checkbox' => [
2202 'HTML/QuickForm/checkbox.php',
2203 'HTML_QuickForm_checkbox',
2204 ],
2205 'file' => [
2206 'HTML/QuickForm/file.php',
2207 'HTML_QuickForm_file',
2208 ],
2209 'image' => [
2210 'HTML/QuickForm/image.php',
2211 'HTML_QuickForm_image',
2212 ],
2213 'password' => [
2214 'HTML/QuickForm/password.php',
2215 'HTML_QuickForm_password',
2216 ],
2217 'radio' => [
2218 'HTML/QuickForm/radio.php',
2219 'HTML_QuickForm_radio',
2220 ],
2221 'button' => [
2222 'HTML/QuickForm/button.php',
2223 'HTML_QuickForm_button',
2224 ],
2225 'submit' => [
2226 'HTML/QuickForm/submit.php',
2227 'HTML_QuickForm_submit',
2228 ],
2229 'select' => [
2230 'HTML/QuickForm/select.php',
2231 'HTML_QuickForm_select',
2232 ],
2233 'hiddenselect' => [
2234 'HTML/QuickForm/hiddenselect.php',
2235 'HTML_QuickForm_hiddenselect',
2236 ],
2237 'text' => [
2238 'HTML/QuickForm/text.php',
2239 'HTML_QuickForm_text',
2240 ],
2241 'textarea' => [
2242 'HTML/QuickForm/textarea.php',
2243 'HTML_QuickForm_textarea',
2244 ],
2245 'fckeditor' => [
2246 'HTML/QuickForm/fckeditor.php',
2247 'HTML_QuickForm_FCKEditor',
2248 ],
2249 'tinymce' => [
2250 'HTML/QuickForm/tinymce.php',
2251 'HTML_QuickForm_TinyMCE',
2252 ],
2253 'dojoeditor' => [
2254 'HTML/QuickForm/dojoeditor.php',
2255 'HTML_QuickForm_dojoeditor',
2256 ],
2257 'link' => [
2258 'HTML/QuickForm/link.php',
2259 'HTML_QuickForm_link',
2260 ],
2261 'advcheckbox' => [
2262 'HTML/QuickForm/advcheckbox.php',
2263 'HTML_QuickForm_advcheckbox',
2264 ],
2265 'date' => [
2266 'HTML/QuickForm/date.php',
2267 'HTML_QuickForm_date',
2268 ],
2269 'static' => [
2270 'HTML/QuickForm/static.php',
2271 'HTML_QuickForm_static',
2272 ],
2273 'header' => [
2274 'HTML/QuickForm/header.php',
2275 'HTML_QuickForm_header',
2276 ],
2277 'html' => [
2278 'HTML/QuickForm/html.php',
2279 'HTML_QuickForm_html',
2280 ],
2281 'hierselect' => [
2282 'HTML/QuickForm/hierselect.php',
2283 'HTML_QuickForm_hierselect',
2284 ],
2285 'autocomplete' => [
2286 'HTML/QuickForm/autocomplete.php',
2287 'HTML_QuickForm_autocomplete',
2288 ],
2289 'xbutton' => [
2290 'HTML/QuickForm/xbutton.php',
2291 'HTML_QuickForm_xbutton',
2292 ],
2293 'advmultiselect' => [
2294 'HTML/QuickForm/advmultiselect.php',
2295 'HTML_QuickForm_advmultiselect',
2296 ],
2297 ];
2298 }
2299
2300 /**
2301 * Set up an acl allowing contact to see 2 specified groups
2302 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2303 *
2304 * You need to have pre-created these groups & created the user e.g
2305 * $this->createLoggedInUser();
2306 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2307 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2308 *
2309 * @param bool $isProfile
2310 */
2311 public function setupACL($isProfile = FALSE) {
2312 global $_REQUEST;
2313 $_REQUEST = $this->_params;
2314
2315 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
2316 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2317 $ov = new CRM_Core_DAO_OptionValue();
2318 $ov->option_group_id = $optionGroupID;
2319 $ov->value = 55;
2320 if ($ov->find(TRUE)) {
2321 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2322 }
2323 $optionValue = $this->callAPISuccess('option_value', 'create', [
2324 'option_group_id' => $optionGroupID,
2325 'label' => 'pick me',
2326 'value' => 55,
2327 ]);
2328
2329 CRM_Core_DAO::executeQuery("
2330 TRUNCATE civicrm_acl_cache
2331 ");
2332
2333 CRM_Core_DAO::executeQuery("
2334 TRUNCATE civicrm_acl_contact_cache
2335 ");
2336
2337 CRM_Core_DAO::executeQuery("
2338 INSERT INTO civicrm_acl_entity_role (
2339 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2340 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2341 ");
2342
2343 if ($isProfile) {
2344 CRM_Core_DAO::executeQuery("
2345 INSERT INTO civicrm_acl (
2346 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2347 )
2348 VALUES (
2349 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2350 );
2351 ");
2352 }
2353 else {
2354 CRM_Core_DAO::executeQuery("
2355 INSERT INTO civicrm_acl (
2356 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2357 )
2358 VALUES (
2359 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2360 );
2361 ");
2362
2363 CRM_Core_DAO::executeQuery("
2364 INSERT INTO civicrm_acl (
2365 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2366 )
2367 VALUES (
2368 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2369 );
2370 ");
2371 }
2372
2373 $this->_loggedInUser = CRM_Core_Session::singleton()->get('userID');
2374 $this->callAPISuccess('group_contact', 'create', [
2375 'group_id' => $this->_permissionedGroup,
2376 'contact_id' => $this->_loggedInUser,
2377 ]);
2378
2379 if (!$isProfile) {
2380 //flush cache
2381 CRM_ACL_BAO_Cache::resetCache();
2382 CRM_ACL_API::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2383 }
2384 }
2385
2386 /**
2387 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2388 */
2389 public function offsetDefaultPriceSet() {
2390 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2391 $firstID = $contributionPriceSet['id'];
2392 $this->callAPISuccess('price_set', 'create', [
2393 'id' => $contributionPriceSet['id'],
2394 'is_active' => 0,
2395 'name' => 'old',
2396 ]);
2397 unset($contributionPriceSet['id']);
2398 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2399 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2400 'price_set_id' => $firstID,
2401 'options' => ['limit' => 1],
2402 ]);
2403 unset($priceField['id']);
2404 $priceField['price_set_id'] = $newPriceSet['id'];
2405 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2406 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2407 'price_set_id' => $firstID,
2408 'sequential' => 1,
2409 'options' => ['limit' => 1],
2410 ]);
2411
2412 unset($priceFieldValue['id']);
2413 //create some padding to use up ids
2414 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2415 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2416 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2417 }
2418
2419 /**
2420 * Create an instance of the paypal processor.
2421 *
2422 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2423 * this parent class & we don't have a structure for that yet
2424 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2425 * & the best protection against that is the functions this class affords
2426 *
2427 * @param array $params
2428 *
2429 * @return int $result['id'] payment processor id
2430 */
2431 public function paymentProcessorCreate($params = []) {
2432 $params = array_merge([
2433 'name' => 'demo',
2434 'domain_id' => CRM_Core_Config::domainID(),
2435 'payment_processor_type_id' => 'PayPal',
2436 'is_active' => 1,
2437 'is_default' => 0,
2438 'is_test' => 1,
2439 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2440 'password' => '1183377788',
2441 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2442 'url_site' => 'https://www.sandbox.paypal.com/',
2443 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2444 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2445 'class_name' => 'Payment_PayPalImpl',
2446 'billing_mode' => 3,
2447 'financial_type_id' => 1,
2448 'financial_account_id' => 12,
2449 // Credit card = 1 so can pass 'by accident'.
2450 'payment_instrument_id' => 'Debit Card',
2451 ], $params);
2452 if (!is_numeric($params['payment_processor_type_id'])) {
2453 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2454 //here
2455 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2456 'name' => $params['payment_processor_type_id'],
2457 'return' => 'id',
2458 ], 'integer');
2459 }
2460 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2461 return $result['id'];
2462 }
2463
2464 /**
2465 * Set up initial recurring payment allowing subsequent IPN payments.
2466 *
2467 * @param array $recurParams (Optional)
2468 * @param array $contributionParams (Optional)
2469 *
2470 * @throws \CRM_Core_Exception
2471 */
2472 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2473 $contributionParams = array_merge([
2474 'total_amount' => '200',
2475 'invoice_id' => $this->_invoiceID,
2476 'financial_type_id' => 'Donation',
2477 'contribution_status_id' => 'Pending',
2478 'contact_id' => $this->_contactID,
2479 'contribution_page_id' => $this->_contributionPageID,
2480 'payment_processor_id' => $this->_paymentProcessorID,
2481 'is_test' => 0,
2482 'receive_date' => '2019-07-25 07:34:23',
2483 'skipCleanMoney' => TRUE,
2484 ], $contributionParams);
2485 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2486 'contact_id' => $this->_contactID,
2487 'amount' => 1000,
2488 'sequential' => 1,
2489 'installments' => 5,
2490 'frequency_unit' => 'Month',
2491 'frequency_interval' => 1,
2492 'invoice_id' => $this->_invoiceID,
2493 'contribution_status_id' => 2,
2494 'payment_processor_id' => $this->_paymentProcessorID,
2495 // processor provided ID - use contact ID as proxy.
2496 'processor_id' => $this->_contactID,
2497 'api.Order.create' => $contributionParams,
2498 ], $recurParams))['values'][0];
2499 $this->_contributionRecurID = $contributionRecur['id'];
2500 $this->_contributionID = $contributionRecur['api.Order.create']['id'];
2501 $this->ids['Contribution'][0] = $this->_contributionID;
2502 }
2503
2504 /**
2505 * 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
2506 *
2507 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2508 *
2509 * @throws \CRM_Core_Exception
2510 */
2511 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2512 $membershipParams = $recurParams = [];
2513 if (!empty($params['duration_unit'])) {
2514 $membershipParams['duration_unit'] = $params['duration_unit'];
2515 }
2516 if (!empty($params['frequency_unit'])) {
2517 $recurParams['frequency_unit'] = $params['frequency_unit'];
2518 }
2519
2520 $this->ids['membership_type'] = $this->membershipTypeCreate($membershipParams);
2521 //create a contribution so our membership & contribution don't both have id = 1
2522 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2523 $this->contributionCreate([
2524 'contact_id' => $this->_contactID,
2525 'is_test' => 1,
2526 'financial_type_id' => 1,
2527 'invoice_id' => 'abcd',
2528 'trxn_id' => 345,
2529 'receive_date' => '2019-07-25 07:34:23',
2530 ]);
2531 }
2532
2533 $this->ids['membership'] = $this->callAPISuccess('Membership', 'create', [
2534 'contact_id' => $this->_contactID,
2535 'membership_type_id' => $this->ids['membership_type'],
2536 'format.only_id' => TRUE,
2537 'source' => 'Payment',
2538 'skipLineItem' => TRUE,
2539 ]);
2540 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2541 'line_items' => [
2542 [
2543 'line_item' => [
2544 [
2545 'entity_table' => 'civicrm_membership',
2546 'entity_id' => $this->ids['membership'],
2547 'label' => 'General',
2548 'qty' => 1,
2549 'unit_price' => 200,
2550 'line_total' => 200,
2551 'financial_type_id' => 1,
2552 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2553 'return' => 'id',
2554 'label' => 'Membership Amount',
2555 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2556 ]),
2557 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2558 'return' => 'id',
2559 'label' => 'General',
2560 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2561 ]),
2562 ],
2563 ],
2564 ],
2565 ],
2566 ]);
2567 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids['membership'], 'contribution_recur_id' => $this->_contributionRecurID]);
2568 }
2569
2570 /**
2571 * @param $message
2572 *
2573 * @throws Exception
2574 */
2575 public function CiviUnitTestCase_fatalErrorHandler($message) {
2576 throw new Exception("{$message['message']}: {$message['code']}");
2577 }
2578
2579 /**
2580 * Wrap the entire test case in a transaction.
2581 *
2582 * Only subsequent DB statements will be wrapped in TX -- this cannot
2583 * retroactively wrap old DB statements. Therefore, it makes sense to
2584 * call this at the beginning of setUp().
2585 *
2586 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2587 * this option does not work with, e.g., custom-data.
2588 *
2589 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2590 * if TRUNCATE or ALTER is called while using a transaction.
2591 *
2592 * @param bool $nest
2593 * Whether to use nesting or reference-counting.
2594 */
2595 public function useTransaction($nest = TRUE) {
2596 if (!$this->tx) {
2597 $this->tx = new CRM_Core_Transaction($nest);
2598 $this->tx->rollback();
2599 }
2600 }
2601
2602 /**
2603 * Assert the attachment exists.
2604 *
2605 * @param bool $exists
2606 * @param array $apiResult
2607 */
2608 protected function assertAttachmentExistence($exists, $apiResult) {
2609 $fileId = $apiResult['id'];
2610 $this->assertTrue(is_numeric($fileId));
2611 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2612 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2613 1 => [$fileId, 'Int'],
2614 ]);
2615 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2616 1 => [$fileId, 'Int'],
2617 ]);
2618 }
2619
2620 /**
2621 * Assert 2 sql strings are the same, ignoring double spaces.
2622 *
2623 * @param string $expectedSQL
2624 * @param string $actualSQL
2625 * @param string $message
2626 */
2627 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2628 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2629 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2630 $this->assertEquals($expected, $actual, $message);
2631 }
2632
2633 /**
2634 * Create a price set for an event.
2635 *
2636 * @param int $feeTotal
2637 * @param int $minAmt
2638 * @param string $type
2639 *
2640 * @param array $options
2641 *
2642 * @return int
2643 * Price Set ID.
2644 * @throws \CRM_Core_Exception
2645 */
2646 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2647 // creating price set, price field
2648 $paramsSet['title'] = 'Price Set';
2649 $paramsSet['name'] = CRM_Utils_String::titleToVar('Price Set');
2650 $paramsSet['is_active'] = FALSE;
2651 $paramsSet['extends'] = 1;
2652 $paramsSet['min_amount'] = $minAmt;
2653
2654 $priceSet = CRM_Price_BAO_PriceSet::create($paramsSet);
2655 $this->_ids['price_set'] = $priceSet->id;
2656
2657 $paramsField = [
2658 'label' => 'Price Field',
2659 'name' => CRM_Utils_String::titleToVar('Price Field'),
2660 'html_type' => $type,
2661 'price' => $feeTotal,
2662 'option_label' => ['1' => 'Price Field'],
2663 'option_value' => ['1' => $feeTotal],
2664 'option_name' => ['1' => $feeTotal],
2665 'option_weight' => ['1' => 1],
2666 'option_amount' => ['1' => 1],
2667 'is_display_amounts' => 1,
2668 'weight' => 1,
2669 'options_per_line' => 1,
2670 'is_active' => ['1' => 1],
2671 'price_set_id' => $this->_ids['price_set'],
2672 'is_enter_qty' => 1,
2673 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2674 ];
2675 if ($type === 'Radio') {
2676 foreach ($options as $index => $option) {
2677 $paramsField['is_enter_qty'] = 0;
2678 $optionID = $index + 2;
2679 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2680 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2681 }
2682
2683 }
2684 $this->callAPISuccess('PriceField', 'create', $paramsField);
2685 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids['price_set']]);
2686 $this->_ids['price_field'] = array_keys($fields['values']);
2687 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids['price_field'][0]]);
2688 $this->_ids['price_field_value'] = array_keys($fieldValues['values']);
2689
2690 return $this->_ids['price_set'];
2691 }
2692
2693 /**
2694 * Add a profile to a contribution page.
2695 *
2696 * @param string $name
2697 * @param int $contributionPageID
2698 * @param string $module
2699 */
2700 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2701 $params = [
2702 'uf_group_id' => $name,
2703 'module' => $module,
2704 'entity_table' => 'civicrm_contribution_page',
2705 'entity_id' => $contributionPageID,
2706 'weight' => 1,
2707 ];
2708 if ($module !== 'CiviContribute') {
2709 $params['module_data'] = [$module => []];
2710 }
2711 $this->callAPISuccess('UFJoin', 'create', $params);
2712 }
2713
2714 /**
2715 * Add participant with contribution
2716 *
2717 * @return array
2718 *
2719 * @throws \CRM_Core_Exception
2720 */
2721 protected function createPartiallyPaidParticipantOrder() {
2722 $orderParams = $this->getParticipantOrderParams();
2723 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2724 return $this->callAPISuccess('Order', 'create', $orderParams);
2725 }
2726
2727 /**
2728 * Create price set
2729 *
2730 * @param string $component
2731 * @param int $componentId
2732 * @param array $priceFieldOptions
2733 *
2734 * @return array
2735 */
2736 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2737 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2738 $paramsSet['name'] = CRM_Utils_String::titleToVar($paramsSet['title']);
2739 $paramsSet['is_active'] = TRUE;
2740 $paramsSet['financial_type_id'] = 'Event Fee';
2741 $paramsSet['extends'] = 1;
2742 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2743 $priceSetId = $priceSet['id'];
2744 //Checking for priceset added in the table.
2745 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2746 'id', $paramsSet['title'], 'Check DB for created priceset'
2747 );
2748 $paramsField = array_merge([
2749 'label' => 'Price Field',
2750 'name' => CRM_Utils_String::titleToVar('Price Field'),
2751 'html_type' => 'CheckBox',
2752 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2753 'option_value' => ['1' => 100, '2' => 200],
2754 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2755 'option_weight' => ['1' => 1, '2' => 2],
2756 'option_amount' => ['1' => 100, '2' => 200],
2757 'is_display_amounts' => 1,
2758 'weight' => 1,
2759 'options_per_line' => 1,
2760 'is_active' => ['1' => 1, '2' => 1],
2761 'price_set_id' => $priceSet['id'],
2762 'is_enter_qty' => 1,
2763 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2764 ], $priceFieldOptions);
2765
2766 $priceField = CRM_Price_BAO_PriceField::create($paramsField);
2767 if ($componentId) {
2768 CRM_Price_BAO_PriceSet::addTo('civicrm_' . $component, $componentId, $priceSetId);
2769 }
2770 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id]);
2771 }
2772
2773 /**
2774 * Replace the template with a test-oriented template designed to show all the variables.
2775 *
2776 * @param string $templateName
2777 * @param string $type
2778 */
2779 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2780 $testTemplate = file_get_contents(__DIR__ . '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2781 CRM_Core_DAO::executeQuery(
2782 "UPDATE civicrm_option_group og
2783 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2784 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2785 SET m.msg_{$type} = %1
2786 WHERE og.name LIKE 'msg_tpl_workflow_%'
2787 AND ov.name = '{$templateName}'
2788 AND m.is_default = 1", [1 => [$testTemplate, 'String']]
2789 );
2790 }
2791
2792 /**
2793 * Reinstate the default template.
2794 *
2795 * @param string $templateName
2796 * @param string $type
2797 */
2798 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2799 CRM_Core_DAO::executeQuery(
2800 "UPDATE civicrm_option_group og
2801 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2802 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2803 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2804 SET m.msg_{$type} = m2.msg_{$type}
2805 WHERE og.name = 'msg_tpl_workflow_contribution'
2806 AND ov.name = '{$templateName}'
2807 AND m.is_default = 1"
2808 );
2809 }
2810
2811 /**
2812 * Flush statics relating to financial type.
2813 */
2814 protected function flushFinancialTypeStatics() {
2815 if (isset(\Civi::$statics['CRM_Financial_BAO_FinancialType'])) {
2816 unset(\Civi::$statics['CRM_Financial_BAO_FinancialType']);
2817 }
2818 if (isset(\Civi::$statics['CRM_Contribute_PseudoConstant'])) {
2819 unset(\Civi::$statics['CRM_Contribute_PseudoConstant']);
2820 }
2821 CRM_Contribute_PseudoConstant::flush('financialType');
2822 CRM_Contribute_PseudoConstant::flush('membershipType');
2823 // Pseudoconstants may be saved to the cache table.
2824 CRM_Core_DAO::executeQuery("TRUNCATE civicrm_cache");
2825 CRM_Financial_BAO_FinancialType::$_statusACLFt = [];
2826 CRM_Financial_BAO_FinancialType::$_availableFinancialTypes = NULL;
2827 }
2828
2829 /**
2830 * Set the permissions to the supplied array.
2831 *
2832 * @param array $permissions
2833 */
2834 protected function setPermissions($permissions) {
2835 CRM_Core_Config::singleton()->userPermissionClass->permissions = $permissions;
2836 $this->flushFinancialTypeStatics();
2837 }
2838
2839 /**
2840 * @param array $params
2841 * @param $context
2842 */
2843 public function _checkFinancialRecords($params, $context) {
2844 $entityParams = [
2845 'entity_id' => $params['id'],
2846 'entity_table' => 'civicrm_contribution',
2847 ];
2848 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2849 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2850 if ($context == 'pending') {
2851 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
2852 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2853 return;
2854 }
2855 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
2856 $trxnParams = [
2857 'id' => $trxn['financial_trxn_id'],
2858 ];
2859 if ($context != 'online' && $context != 'payLater') {
2860 $compareParams = [
2861 'to_financial_account_id' => 6,
2862 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2863 'status_id' => 1,
2864 ];
2865 }
2866 if ($context == 'feeAmount') {
2867 $compareParams['fee_amount'] = 50;
2868 }
2869 elseif ($context == 'online') {
2870 $compareParams = [
2871 'to_financial_account_id' => 12,
2872 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2873 'status_id' => 1,
2874 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, 1),
2875 ];
2876 }
2877 elseif ($context == 'payLater') {
2878 $compareParams = [
2879 'to_financial_account_id' => 7,
2880 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2881 'status_id' => 2,
2882 ];
2883 }
2884 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2885 $entityParams = [
2886 'financial_trxn_id' => $trxn['financial_trxn_id'],
2887 'entity_table' => 'civicrm_financial_item',
2888 ];
2889 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
2890 $fitemParams = [
2891 'id' => $entityTrxn['entity_id'],
2892 ];
2893 $compareParams = [
2894 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2895 'status_id' => 1,
2896 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
2897 ];
2898 if ($context == 'payLater') {
2899 $compareParams = [
2900 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2901 'status_id' => 3,
2902 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
2903 ];
2904 }
2905 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2906 if ($context == 'feeAmount') {
2907 $maxParams = [
2908 'entity_id' => $params['id'],
2909 'entity_table' => 'civicrm_contribution',
2910 ];
2911 $maxTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($maxParams, TRUE));
2912 $trxnParams = [
2913 'id' => $maxTrxn['financial_trxn_id'],
2914 ];
2915 $compareParams = [
2916 'to_financial_account_id' => 5,
2917 'from_financial_account_id' => 6,
2918 'total_amount' => 50,
2919 'status_id' => 1,
2920 ];
2921 $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['id'], 'DESC');
2922 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2923 $fitemParams = [
2924 'entity_id' => $trxnId['financialTrxnId'],
2925 'entity_table' => 'civicrm_financial_trxn',
2926 ];
2927 $compareParams = [
2928 'amount' => 50.00,
2929 'status_id' => 1,
2930 'financial_account_id' => 5,
2931 ];
2932 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2933 }
2934 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2935 // line should be copied into all the functions that call this function & evaluated there
2936 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2937 // when calling completeTransaction or repeatTransaction.
2938 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2939 }
2940
2941 /**
2942 * Return financial type id on basis of name
2943 *
2944 * @param string $name Financial type m/c name
2945 *
2946 * @return int
2947 */
2948 public function getFinancialTypeId($name) {
2949 return CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2950 }
2951
2952 /**
2953 * Cleanup function for contents of $this->ids.
2954 *
2955 * This is a best effort cleanup to use in tear downs etc.
2956 *
2957 * It will not fail if the data has already been removed (some tests may do
2958 * their own cleanup).
2959 */
2960 protected function cleanUpSetUpIDs() {
2961 foreach ($this->setupIDs as $entity => $id) {
2962 try {
2963 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
2964 }
2965 catch (CiviCRM_API3_Exception $e) {
2966 // This is a best-effort cleanup function, ignore.
2967 }
2968 }
2969 }
2970
2971 /**
2972 * Create Financial Type.
2973 *
2974 * @param array $params
2975 *
2976 * @return array
2977 */
2978 protected function createFinancialType($params = []) {
2979 $params = array_merge($params,
2980 [
2981 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
2982 'is_active' => 1,
2983 ]
2984 );
2985 return $this->callAPISuccess('FinancialType', 'create', $params);
2986 }
2987
2988 /**
2989 * Create Payment Instrument.
2990 *
2991 * @param array $params
2992 * @param string $financialAccountName
2993 *
2994 * @return int
2995 */
2996 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
2997 $params = array_merge([
2998 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
2999 'option_group_id' => 'payment_instrument',
3000 'is_active' => 1,
3001 ], $params);
3002 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3003
3004 $relationTypeID = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3005
3006 $financialAccountParams = [
3007 'entity_table' => 'civicrm_option_value',
3008 'entity_id' => $newPaymentInstrument,
3009 'account_relationship' => $relationTypeID,
3010 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3011 ];
3012 CRM_Financial_BAO_FinancialTypeAccount::add($financialAccountParams);
3013
3014 return CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3015 }
3016
3017 /**
3018 * Enable Tax and Invoicing
3019 *
3020 * @param array $params
3021 *
3022 * @return \Civi\Core\SettingsBag
3023 */
3024 protected function enableTaxAndInvoicing($params = []) {
3025 // Enable component contribute setting
3026 $contributeSetting = array_merge($params,
3027 [
3028 'invoicing' => 1,
3029 'invoice_prefix' => 'INV_',
3030 'due_date' => 10,
3031 'due_date_period' => 'days',
3032 'notes' => '',
3033 'is_email_pdf' => 1,
3034 'tax_term' => 'Sales Tax',
3035 'tax_display_settings' => 'Inclusive',
3036 ]
3037 );
3038 return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
3039 }
3040
3041 /**
3042 * Enable Tax and Invoicing
3043 *
3044 * @throws \CRM_Core_Exception
3045 */
3046 protected function disableTaxAndInvoicing() {
3047 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3048 foreach ($accounts as $account) {
3049 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3050 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3051 }
3052
3053 if (!empty(\Civi::$statics['CRM_Core_PseudoConstant']) && isset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3054 unset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates']);
3055 }
3056 return Civi::settings()->set('invoicing', FALSE);
3057 }
3058
3059 /**
3060 * Add Sales Tax relation for financial type with financial account.
3061 *
3062 * @param int $financialTypeId
3063 *
3064 * @return obj
3065 */
3066 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
3067 $params = [
3068 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3069 'financial_account_type_id' => key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3070 'is_deductible' => 1,
3071 'is_tax' => 1,
3072 'tax_rate' => 10,
3073 'is_active' => 1,
3074 ];
3075 $account = CRM_Financial_BAO_FinancialAccount::add($params);
3076 $entityParams = [
3077 'entity_table' => 'civicrm_financial_type',
3078 'entity_id' => $financialTypeId,
3079 'account_relationship' => key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3080 ];
3081
3082 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3083 \Civi::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3084
3085 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3086 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3087 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3088 $dao->copyValues($entityParams);
3089 $dao->find();
3090 if ($dao->fetch()) {
3091 $entityParams['id'] = $dao->id;
3092 }
3093 $entityParams['financial_account_id'] = $account->id;
3094
3095 return CRM_Financial_BAO_FinancialTypeAccount::add($entityParams);
3096 }
3097
3098 /**
3099 * Create price set with contribution test for test setup.
3100 *
3101 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3102 * on parent class at some point (fn is not in 4.4).
3103 *
3104 * @param $entity
3105 * @param array $params
3106 */
3107 public function createPriceSetWithPage($entity = NULL, $params = []) {
3108 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3109 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3110 'title' => "Test Contribution Page",
3111 'financial_type_id' => 1,
3112 'currency' => 'NZD',
3113 'goal_amount' => 50,
3114 'is_pay_later' => 1,
3115 'is_monetary' => TRUE,
3116 'is_email_receipt' => FALSE,
3117 ]);
3118 $priceSet = $this->callAPISuccess('price_set', 'create', [
3119 'is_quick_config' => 0,
3120 'extends' => 'CiviMember',
3121 'financial_type_id' => 1,
3122 'title' => 'my Page',
3123 ]);
3124 $priceSetID = $priceSet['id'];
3125
3126 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3127 $priceField = $this->callAPISuccess('price_field', 'create', [
3128 'price_set_id' => $priceSetID,
3129 'label' => 'Goat Breed',
3130 'html_type' => 'Radio',
3131 ]);
3132 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3133 'price_set_id' => $priceSetID,
3134 'price_field_id' => $priceField['id'],
3135 'label' => 'Long Haired Goat',
3136 'amount' => 20,
3137 'financial_type_id' => 'Donation',
3138 'membership_type_id' => $membershipTypeID,
3139 'membership_num_terms' => 1,
3140 ]);
3141 $this->_ids['price_field_value'] = [$priceFieldValue['id']];
3142 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3143 'price_set_id' => $priceSetID,
3144 'price_field_id' => $priceField['id'],
3145 'label' => 'Shoe-eating Goat',
3146 'amount' => 10,
3147 'financial_type_id' => 'Donation',
3148 'membership_type_id' => $membershipTypeID,
3149 'membership_num_terms' => 2,
3150 ]);
3151 $this->_ids['price_field_value'][] = $priceFieldValue['id'];
3152
3153 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3154 'price_set_id' => $priceSetID,
3155 'price_field_id' => $priceField['id'],
3156 'label' => 'Shoe-eating Goat',
3157 'amount' => 10,
3158 'financial_type_id' => 'Donation',
3159 ]);
3160 $this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];
3161
3162 $this->_ids['price_set'] = $priceSetID;
3163 $this->_ids['contribution_page'] = $contributionPageResult['id'];
3164 $this->_ids['price_field'] = [$priceField['id']];
3165
3166 $this->_ids['membership_type'] = $membershipTypeID;
3167 }
3168
3169 /**
3170 * Only specified contact returned.
3171 *
3172 * @implements CRM_Utils_Hook::aclWhereClause
3173 *
3174 * @param $type
3175 * @param $tables
3176 * @param $whereTables
3177 * @param $contactID
3178 * @param $where
3179 */
3180 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3181 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts) . ")";
3182 }
3183
3184 /**
3185 * @implements CRM_Utils_Hook::selectWhereClause
3186 *
3187 * @param string $entity
3188 * @param array $clauses
3189 */
3190 public function selectWhereClauseHook($entity, &$clauses) {
3191 if ($entity == 'Event') {
3192 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3193 }
3194 }
3195
3196 /**
3197 * An implementation of hook_civicrm_post used with all our test cases.
3198 *
3199 * @param $op
3200 * @param string $objectName
3201 * @param int $objectId
3202 * @param $objectRef
3203 */
3204 public function onPost($op, $objectName, $objectId, &$objectRef) {
3205 if ($op == 'create' && $objectName == 'Individual') {
3206 CRM_Core_DAO::executeQuery(
3207 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3208 [
3209 1 => [$objectId, 'Integer'],
3210 ]
3211 );
3212 }
3213
3214 if ($op == 'edit' && $objectName == 'Participant') {
3215 $params = [
3216 1 => [$objectId, 'Integer'],
3217 ];
3218 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3219 CRM_Core_DAO::executeQuery($query, $params);
3220 }
3221 }
3222
3223 /**
3224 * Instantiate form object.
3225 *
3226 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3227 *
3228 * @param string $class
3229 * Name of form class.
3230 *
3231 * @param array $formValues
3232 *
3233 * @param string $pageName
3234 *
3235 * @return \CRM_Core_Form
3236 * @throws \CRM_Core_Exception
3237 */
3238 public function getFormObject($class, $formValues = [], $pageName = '') {
3239 $form = new $class();
3240 $_SERVER['REQUEST_METHOD'] = 'GET';
3241 $form->controller = new CRM_Core_Controller();
3242 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3243 $_SESSION['_' . $form->controller->_name . '_container']['values'][$pageName] = $formValues;
3244 return $form;
3245 }
3246
3247 /**
3248 * Get possible thousand separators.
3249 *
3250 * @return array
3251 */
3252 public function getThousandSeparators() {
3253 return [['.'], [',']];
3254 }
3255
3256 /**
3257 * Get the boolean options as a provider.
3258 *
3259 * @return array
3260 */
3261 public function getBooleanDataProvider() {
3262 return [[TRUE], [FALSE]];
3263 }
3264
3265 /**
3266 * Set the separators for thousands and decimal points.
3267 *
3268 * Note that this only covers some common scenarios.
3269 *
3270 * It does not cater for a situation where the thousand separator is a [space]
3271 * Latter is the Norwegian localization. At least some tests need to
3272 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3273 * to provide broader coverage.
3274 *
3275 * @param string $thousandSeparator
3276 */
3277 protected function setCurrencySeparators($thousandSeparator) {
3278 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3279 Civi::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ? '.' : ','));
3280 }
3281
3282 /**
3283 * Sets the thousand separator.
3284 *
3285 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3286 *
3287 * @param $thousandSeparator
3288 */
3289 protected function setMonetaryThousandSeparator($thousandSeparator) {
3290 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3291 }
3292
3293 /**
3294 * Sets the decimal separator.
3295 *
3296 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3297 *
3298 * @param $decimalPoint
3299 */
3300 protected function setMonetaryDecimalPoint($decimalPoint) {
3301 Civi::settings()->set('monetaryDecimalPoint', $decimalPoint);
3302 }
3303
3304 /**
3305 * Sets the default currency.
3306 *
3307 * @param $currency
3308 */
3309 protected function setDefaultCurrency($currency) {
3310 Civi::settings()->set('defaultCurrency', $currency);
3311 }
3312
3313 /**
3314 * Format money as it would be input.
3315 *
3316 * @param string $amount
3317 *
3318 * @return string
3319 */
3320 protected function formatMoneyInput($amount) {
3321 return CRM_Utils_Money::format($amount, NULL, '%a');
3322 }
3323
3324 /**
3325 * Get the contribution object.
3326 *
3327 * @param int $contributionID
3328 *
3329 * @return \CRM_Contribute_BAO_Contribution
3330 */
3331 protected function getContributionObject($contributionID) {
3332 $contributionObj = new CRM_Contribute_BAO_Contribution();
3333 $contributionObj->id = $contributionID;
3334 $contributionObj->find(TRUE);
3335 return $contributionObj;
3336 }
3337
3338 /**
3339 * Enable multilingual.
3340 */
3341 public function enableMultilingual() {
3342 $this->callAPISuccess('Setting', 'create', [
3343 'lcMessages' => 'en_US',
3344 'languageLimit' => [
3345 'en_US' => 1,
3346 ],
3347 ]);
3348
3349 CRM_Core_I18n_Schema::makeMultilingual('en_US');
3350
3351 global $dbLocale;
3352 $dbLocale = '_en_US';
3353 }
3354
3355 /**
3356 * Setup or clean up SMS tests
3357 *
3358 * @param bool $teardown
3359 *
3360 * @throws \CiviCRM_API3_Exception
3361 */
3362 public function setupForSmsTests($teardown = FALSE) {
3363 require_once 'CiviTest/CiviTestSMSProvider.php';
3364
3365 // Option value params for CiviTestSMSProvider
3366 $groupID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3367 $params = [
3368 'option_group_id' => $groupID,
3369 'label' => 'unittestSMS',
3370 'value' => 'unit.test.sms',
3371 'name' => 'CiviTestSMSProvider',
3372 'is_default' => 1,
3373 'is_active' => 1,
3374 'version' => 3,
3375 ];
3376
3377 if ($teardown) {
3378 // Test completed, delete provider
3379 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3380 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3381 return;
3382 }
3383
3384 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3385 // in CRM/Sms/Provider.php even though it is not an extension.
3386 return civicrm_api3('option_value', 'create', $params);
3387 }
3388
3389 /**
3390 * Start capturing browser output.
3391 *
3392 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3393 */
3394 protected function startCapturingOutput() {
3395 ob_start();
3396 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3397 }
3398
3399 /**
3400 * Stop capturing browser output and return as a csv.
3401 *
3402 * @param bool $isFirstRowHeaders
3403 *
3404 * @return \League\Csv\Reader
3405 *
3406 * @throws \League\Csv\Exception
3407 */
3408 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3409 $output = ob_get_flush();
3410 $stream = fopen('php://memory', 'r+');
3411 fwrite($stream, $output);
3412 rewind($stream);
3413 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3414 $csv = Reader::createFromString($output);
3415 if ($isFirstRowHeaders) {
3416 $csv->setHeaderOffset(0);
3417 }
3418 ob_clean();
3419 return $csv;
3420 }
3421
3422 /**
3423 * Rename various labels to not match the names.
3424 *
3425 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3426 * code that expects it to.
3427 */
3428 protected function renameLabels() {
3429 $replacements = ['Pending', 'Refunded'];
3430 foreach ($replacements as $name) {
3431 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3432 }
3433 }
3434
3435 /**
3436 * Undo any label renaming.
3437 */
3438 protected function resetLabels() {
3439 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3440 }
3441
3442 /**
3443 * Get parameters to set up a multi-line participant order.
3444 *
3445 * @return array
3446 * @throws \CRM_Core_Exception
3447 */
3448 protected function getParticipantOrderParams(): array {
3449 $this->_contactId = $this->individualCreate();
3450 $event = $this->eventCreate();
3451 $this->_eventId = $event['id'];
3452 $eventParams = [
3453 'id' => $this->_eventId,
3454 'financial_type_id' => 4,
3455 'is_monetary' => 1,
3456 ];
3457 $this->callAPISuccess('event', 'create', $eventParams);
3458 $priceFields = $this->createPriceSet('event', $this->_eventId);
3459 $participantParams = [
3460 'financial_type_id' => 4,
3461 'event_id' => $this->_eventId,
3462 'role_id' => 1,
3463 'status_id' => 14,
3464 'fee_currency' => 'USD',
3465 'contact_id' => $this->_contactId,
3466 ];
3467 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3468 $orderParams = [
3469 'total_amount' => 300,
3470 'currency' => 'USD',
3471 'contact_id' => $this->_contactId,
3472 'financial_type_id' => 4,
3473 'contribution_status_id' => 'Pending',
3474 'contribution_mode' => 'participant',
3475 'participant_id' => $participant['id'],
3476 ];
3477 foreach ($priceFields['values'] as $key => $priceField) {
3478 $orderParams['line_items'][] = [
3479 'line_item' => [
3480 [
3481 'price_field_id' => $priceField['price_field_id'],
3482 'price_field_value_id' => $priceField['id'],
3483 'label' => $priceField['label'],
3484 'field_title' => $priceField['label'],
3485 'qty' => 1,
3486 'unit_price' => $priceField['amount'],
3487 'line_total' => $priceField['amount'],
3488 'financial_type_id' => $priceField['financial_type_id'],
3489 'entity_table' => 'civicrm_participant',
3490 ],
3491 ],
3492 'params' => $participant,
3493 ];
3494 }
3495 return $orderParams;
3496 }
3497
3498 /**
3499 * @param $payments
3500 *
3501 * @throws \CRM_Core_Exception
3502 */
3503 protected function validatePayments($payments) {
3504 foreach ($payments as $payment) {
3505 $balance = CRM_Contribute_BAO_Contribution::getContributionBalance($payment['contribution_id']);
3506 if ($balance < 0 && $balance + $payment['total_amount'] === 0.0) {
3507 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3508 // This is a pretty rough way at guessing which payment is the overpayment - but
3509 // for the test suite it should be enough.
3510 continue;
3511 }
3512 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3513 'financial_trxn_id' => $payment['id'],
3514 'entity_table' => 'civicrm_financial_item',
3515 'return' => ['amount'],
3516 ])['values'];
3517 $itemTotal = 0;
3518 foreach ($items as $item) {
3519 $itemTotal += $item['amount'];
3520 }
3521 $this->assertEquals($payment['total_amount'], $itemTotal);
3522 }
3523 }
3524
3525 /**
3526 * Validate all created payments.
3527 *
3528 * @throws \CRM_Core_Exception
3529 */
3530 protected function validateAllPayments() {
3531 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3532 $this->validatePayments($payments);
3533 }
3534
3535 /**
3536 * Validate all created contributions.
3537 *
3538 * @throws \CRM_Core_Exception
3539 */
3540 protected function validateAllContributions() {
3541 $contributions = $this->callAPISuccess('Contribution', 'get', [])['values'];
3542 foreach ($contributions as $contribution) {
3543 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3544 $total = 0;
3545 foreach ($lineItems as $lineItem) {
3546 $total += $lineItem['line_total'];
3547 }
3548 $this->assertEquals($total, $contribution['total_amount']);
3549 }
3550 }
3551
3552 /**
3553 * Generic create test.
3554 *
3555 * @param int $version
3556 *
3557 * @throws \CRM_Core_Exception
3558 */
3559 protected function basicCreateTest(int $version) {
3560 $this->_apiversion = $version;
3561 $result = $this->callAPIAndDocument($this->_entity, 'create', $this->params, __FUNCTION__, __FILE__);
3562 $this->assertEquals(1, $result['count']);
3563 $this->assertNotNull($result['values'][$result['id']]['id']);
3564 $this->getAndCheck($this->params, $result['id'], $this->_entity);
3565 }
3566
3567 /**
3568 * Generic delete test.
3569 *
3570 * @param int $version
3571 *
3572 * @throws \CRM_Core_Exception
3573 */
3574 protected function basicDeleteTest($version) {
3575 $this->_apiversion = $version;
3576 $result = $this->callAPISuccess($this->_entity, 'create', $this->params);
3577 $deleteParams = ['id' => $result['id']];
3578 $this->callAPIAndDocument($this->_entity, 'delete', $deleteParams, __FUNCTION__, __FILE__);
3579 $checkDeleted = $this->callAPISuccess($this->_entity, 'get', []);
3580 $this->assertEquals(0, $checkDeleted['count']);
3581 }
3582
3583 }