Merge pull request #18038 from eileenmcnaughton/perm_test
[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 $params['is_monetary'] = TRUE;
1058 $event = $this->eventCreate($params);
1059 $this->ids['Event'][$key] = (int) $event['id'];
1060 $this->ids['PriceSet'][$key] = $this->eventPriceSetCreate(55, 0, 'Radio', $options);
1061 CRM_Price_BAO_PriceSet::addTo('civicrm_event', $event['id'], $this->ids['PriceSet'][$key]);
1062 $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($this->ids['PriceSet'][$key], TRUE, FALSE);
1063 $priceSet = $priceSet[$this->ids['PriceSet'][$key]] ?? NULL;
1064 $this->eventFeeBlock = $priceSet['fields'] ?? NULL;
1065 return $event;
1066 }
1067
1068 /**
1069 * Delete event.
1070 *
1071 * @param int $id
1072 * ID of the event.
1073 *
1074 * @return array|int
1075 */
1076 public function eventDelete($id) {
1077 $params = [
1078 'event_id' => $id,
1079 ];
1080 return $this->callAPISuccess('event', 'delete', $params);
1081 }
1082
1083 /**
1084 * Delete participant.
1085 *
1086 * @param int $participantID
1087 *
1088 * @return array|int
1089 */
1090 public function participantDelete($participantID) {
1091 $params = [
1092 'id' => $participantID,
1093 ];
1094 $check = $this->callAPISuccess('Participant', 'get', $params);
1095 if ($check['count'] > 0) {
1096 return $this->callAPISuccess('Participant', 'delete', $params);
1097 }
1098 }
1099
1100 /**
1101 * Create participant payment.
1102 *
1103 * @param int $participantID
1104 * @param int $contributionID
1105 *
1106 * @return int
1107 * $id of created payment
1108 */
1109 public function participantPaymentCreate($participantID, $contributionID = NULL) {
1110 //Create Participant Payment record With Values
1111 $params = [
1112 'participant_id' => $participantID,
1113 'contribution_id' => $contributionID,
1114 ];
1115
1116 $result = $this->callAPISuccess('participant_payment', 'create', $params);
1117 return $result['id'];
1118 }
1119
1120 /**
1121 * Delete participant payment.
1122 *
1123 * @param int $paymentID
1124 */
1125 public function participantPaymentDelete($paymentID) {
1126 $params = [
1127 'id' => $paymentID,
1128 ];
1129 $result = $this->callAPISuccess('participant_payment', 'delete', $params);
1130 }
1131
1132 /**
1133 * Add a Location.
1134 *
1135 * @param int $contactID
1136 *
1137 * @return int
1138 * location id of created location
1139 */
1140 public function locationAdd($contactID) {
1141 $address = [
1142 1 => [
1143 'location_type' => 'New Location Type',
1144 'is_primary' => 1,
1145 'name' => 'Saint Helier St',
1146 'county' => 'Marin',
1147 'country' => 'UNITED STATES',
1148 'state_province' => 'Michigan',
1149 'supplemental_address_1' => 'Hallmark Ct',
1150 'supplemental_address_2' => 'Jersey Village',
1151 'supplemental_address_3' => 'My Town',
1152 ],
1153 ];
1154
1155 $params = [
1156 'contact_id' => $contactID,
1157 'address' => $address,
1158 'location_format' => '2.0',
1159 'location_type' => 'New Location Type',
1160 ];
1161
1162 $result = $this->callAPISuccess('Location', 'create', $params);
1163 return $result;
1164 }
1165
1166 /**
1167 * Delete Locations of contact.
1168 *
1169 * @param array $params
1170 * Parameters.
1171 */
1172 public function locationDelete($params) {
1173 $this->callAPISuccess('Location', 'delete', $params);
1174 }
1175
1176 /**
1177 * Add a Location Type.
1178 *
1179 * @param array $params
1180 *
1181 * @return CRM_Core_DAO_LocationType
1182 * location id of created location
1183 */
1184 public function locationTypeCreate($params = NULL) {
1185 if ($params === NULL) {
1186 $params = [
1187 'name' => 'New Location Type',
1188 'vcard_name' => 'New Location Type',
1189 'description' => 'Location Type for Delete',
1190 'is_active' => 1,
1191 ];
1192 }
1193
1194 $locationType = new CRM_Core_DAO_LocationType();
1195 $locationType->copyValues($params);
1196 $locationType->save();
1197 // clear getfields cache
1198 CRM_Core_PseudoConstant::flush();
1199 $this->callAPISuccess('phone', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1200 return $locationType;
1201 }
1202
1203 /**
1204 * Delete a Location Type.
1205 *
1206 * @param int $locationTypeId
1207 */
1208 public function locationTypeDelete($locationTypeId) {
1209 $locationType = new CRM_Core_DAO_LocationType();
1210 $locationType->id = $locationTypeId;
1211 $locationType->delete();
1212 }
1213
1214 /**
1215 * Add a Mapping.
1216 *
1217 * @param array $params
1218 *
1219 * @return CRM_Core_DAO_Mapping
1220 * Mapping id of created mapping
1221 */
1222 public function mappingCreate($params = NULL) {
1223 if ($params === NULL) {
1224 $params = [
1225 'name' => 'Mapping name',
1226 'description' => 'Mapping description',
1227 // 'Export Contact' mapping.
1228 'mapping_type_id' => 7,
1229 ];
1230 }
1231
1232 $mapping = new CRM_Core_DAO_Mapping();
1233 $mapping->copyValues($params);
1234 $mapping->save();
1235 // clear getfields cache
1236 CRM_Core_PseudoConstant::flush();
1237 $this->callAPISuccess('mapping', 'getfields', ['version' => 3, 'cache_clear' => 1]);
1238 return $mapping;
1239 }
1240
1241 /**
1242 * Delete a Mapping
1243 *
1244 * @param int $mappingId
1245 */
1246 public function mappingDelete($mappingId) {
1247 $mapping = new CRM_Core_DAO_Mapping();
1248 $mapping->id = $mappingId;
1249 $mapping->delete();
1250 }
1251
1252 /**
1253 * Prepare class for ACLs.
1254 */
1255 protected function prepareForACLs() {
1256 $config = CRM_Core_Config::singleton();
1257 $config->userPermissionClass->permissions = [];
1258 }
1259
1260 /**
1261 * Reset after ACLs.
1262 */
1263 protected function cleanUpAfterACLs() {
1264 CRM_Utils_Hook::singleton()->reset();
1265 $tablesToTruncate = [
1266 'civicrm_acl',
1267 'civicrm_acl_cache',
1268 'civicrm_acl_entity_role',
1269 'civicrm_acl_contact_cache',
1270 ];
1271 $this->quickCleanup($tablesToTruncate);
1272 $config = CRM_Core_Config::singleton();
1273 unset($config->userPermissionClass->permissions);
1274 }
1275
1276 /**
1277 * Create a smart group.
1278 *
1279 * By default it will be a group of households.
1280 *
1281 * @param array $smartGroupParams
1282 * @param array $groupParams
1283 * @param string $contactType
1284 *
1285 * @return int
1286 */
1287 public function smartGroupCreate($smartGroupParams = [], $groupParams = [], $contactType = 'Household') {
1288 $smartGroupParams = array_merge(['form_values' => ['contact_type' => ['IN' => [$contactType]]]], $smartGroupParams);
1289 $savedSearch = CRM_Contact_BAO_SavedSearch::create($smartGroupParams);
1290
1291 $groupParams['saved_search_id'] = $savedSearch->id;
1292 return $this->groupCreate($groupParams);
1293 }
1294
1295 /**
1296 * Create a UFField.
1297 *
1298 * @param array $params
1299 */
1300 public function uFFieldCreate($params = []) {
1301 $params = array_merge([
1302 'uf_group_id' => 1,
1303 'field_name' => 'first_name',
1304 'is_active' => 1,
1305 'is_required' => 1,
1306 'visibility' => 'Public Pages and Listings',
1307 'is_searchable' => '1',
1308 'label' => 'first_name',
1309 'field_type' => 'Individual',
1310 'weight' => 1,
1311 ], $params);
1312 $this->callAPISuccess('uf_field', 'create', $params);
1313 }
1314
1315 /**
1316 * Add a UF Join Entry.
1317 *
1318 * @param array $params
1319 *
1320 * @return int
1321 * $id of created UF Join
1322 */
1323 public function ufjoinCreate($params = NULL) {
1324 if ($params === NULL) {
1325 $params = [
1326 'is_active' => 1,
1327 'module' => 'CiviEvent',
1328 'entity_table' => 'civicrm_event',
1329 'entity_id' => 3,
1330 'weight' => 1,
1331 'uf_group_id' => 1,
1332 ];
1333 }
1334 $result = $this->callAPISuccess('uf_join', 'create', $params);
1335 return $result;
1336 }
1337
1338 /**
1339 * @param array $params
1340 * Optional parameters.
1341 * @param bool $reloadConfig
1342 * While enabling CiviCampaign component, we shouldn't always forcibly
1343 * reload config as this hinder hook call in test environment
1344 *
1345 * @return int
1346 * Campaign ID.
1347 */
1348 public function campaignCreate($params = [], $reloadConfig = TRUE) {
1349 $this->enableCiviCampaign($reloadConfig);
1350 $campaign = $this->callAPISuccess('campaign', 'create', array_merge([
1351 'name' => 'big_campaign',
1352 'title' => 'Campaign',
1353 ], $params));
1354 return $campaign['id'];
1355 }
1356
1357 /**
1358 * Create Group for a contact.
1359 *
1360 * @param int $contactId
1361 */
1362 public function contactGroupCreate($contactId) {
1363 $params = [
1364 'contact_id.1' => $contactId,
1365 'group_id' => 1,
1366 ];
1367
1368 $this->callAPISuccess('GroupContact', 'Create', $params);
1369 }
1370
1371 /**
1372 * Delete Group for a contact.
1373 *
1374 * @param int $contactId
1375 */
1376 public function contactGroupDelete($contactId) {
1377 $params = [
1378 'contact_id.1' => $contactId,
1379 'group_id' => 1,
1380 ];
1381 $this->civicrm_api('GroupContact', 'Delete', $params);
1382 }
1383
1384 /**
1385 * Create Activity.
1386 *
1387 * @param array $params
1388 *
1389 * @return array|int
1390 *
1391 * @throws \CRM_Core_Exception
1392 * @throws \CiviCRM_API3_Exception
1393 */
1394 public function activityCreate($params = []) {
1395 $params = array_merge([
1396 'subject' => 'Discussion on warm beer',
1397 'activity_date_time' => date('Ymd'),
1398 'duration' => 90,
1399 'location' => 'Baker Street',
1400 'details' => 'Lets schedule a meeting',
1401 'status_id' => 1,
1402 'activity_type_id' => 'Meeting',
1403 ], $params);
1404 if (!isset($params['source_contact_id'])) {
1405 $params['source_contact_id'] = $this->individualCreate();
1406 }
1407 if (!isset($params['target_contact_id'])) {
1408 $params['target_contact_id'] = $this->individualCreate([
1409 'first_name' => 'Julia',
1410 'last_name' => 'Anderson',
1411 'prefix' => 'Ms.',
1412 'email' => 'julia_anderson@civicrm.org',
1413 'contact_type' => 'Individual',
1414 ]);
1415 }
1416 if (!isset($params['assignee_contact_id'])) {
1417 $params['assignee_contact_id'] = $params['target_contact_id'];
1418 }
1419
1420 $result = civicrm_api3('Activity', 'create', $params);
1421
1422 $result['target_contact_id'] = $params['target_contact_id'];
1423 $result['assignee_contact_id'] = $params['assignee_contact_id'];
1424 return $result;
1425 }
1426
1427 /**
1428 * Create an activity type.
1429 *
1430 * @param array $params
1431 * Parameters.
1432 *
1433 * @return array
1434 */
1435 public function activityTypeCreate($params) {
1436 return $this->callAPISuccess('ActivityType', 'create', $params);
1437 }
1438
1439 /**
1440 * Delete activity type.
1441 *
1442 * @param int $activityTypeId
1443 * Id of the activity type.
1444 *
1445 * @return array
1446 */
1447 public function activityTypeDelete($activityTypeId) {
1448 $params['activity_type_id'] = $activityTypeId;
1449 return $this->callAPISuccess('ActivityType', 'delete', $params);
1450 }
1451
1452 /**
1453 * Create custom group.
1454 *
1455 * @param array $params
1456 *
1457 * @return array
1458 */
1459 public function customGroupCreate($params = []) {
1460 $defaults = [
1461 'title' => 'new custom group',
1462 'extends' => 'Contact',
1463 'domain_id' => 1,
1464 'style' => 'Inline',
1465 'is_active' => 1,
1466 ];
1467
1468 $params = array_merge($defaults, $params);
1469
1470 return $this->callAPISuccess('custom_group', 'create', $params);
1471 }
1472
1473 /**
1474 * Existing function doesn't allow params to be over-ridden so need a new one
1475 * this one allows you to only pass in the params you want to change
1476 *
1477 * @param array $params
1478 *
1479 * @return array|int
1480 */
1481 public function CustomGroupCreateByParams($params = []) {
1482 $defaults = [
1483 'title' => "API Custom Group",
1484 'extends' => 'Contact',
1485 'domain_id' => 1,
1486 'style' => 'Inline',
1487 'is_active' => 1,
1488 ];
1489 $params = array_merge($defaults, $params);
1490 return $this->callAPISuccess('custom_group', 'create', $params);
1491 }
1492
1493 /**
1494 * Create custom group with multi fields.
1495 *
1496 * @param array $params
1497 *
1498 * @return array|int
1499 */
1500 public function CustomGroupMultipleCreateByParams($params = []) {
1501 $defaults = [
1502 'style' => 'Tab',
1503 'is_multiple' => 1,
1504 ];
1505 $params = array_merge($defaults, $params);
1506 return $this->CustomGroupCreateByParams($params);
1507 }
1508
1509 /**
1510 * Create custom group with multi fields.
1511 *
1512 * @param array $params
1513 *
1514 * @return array
1515 */
1516 public function CustomGroupMultipleCreateWithFields($params = []) {
1517 // also need to pass on $params['custom_field'] if not set but not in place yet
1518 $ids = [];
1519 $customGroup = $this->CustomGroupMultipleCreateByParams($params);
1520 $ids['custom_group_id'] = $customGroup['id'];
1521
1522 $customField = $this->customFieldCreate([
1523 'custom_group_id' => $ids['custom_group_id'],
1524 'label' => 'field_1' . $ids['custom_group_id'],
1525 'in_selector' => 1,
1526 ]);
1527
1528 $ids['custom_field_id'][] = $customField['id'];
1529
1530 $customField = $this->customFieldCreate([
1531 'custom_group_id' => $ids['custom_group_id'],
1532 'default_value' => '',
1533 'label' => 'field_2' . $ids['custom_group_id'],
1534 'in_selector' => 1,
1535 ]);
1536 $ids['custom_field_id'][] = $customField['id'];
1537
1538 $customField = $this->customFieldCreate([
1539 'custom_group_id' => $ids['custom_group_id'],
1540 'default_value' => '',
1541 'label' => 'field_3' . $ids['custom_group_id'],
1542 'in_selector' => 1,
1543 ]);
1544 $ids['custom_field_id'][] = $customField['id'];
1545
1546 return $ids;
1547 }
1548
1549 /**
1550 * Create a custom group with a single text custom field. See
1551 * participant:testCreateWithCustom for how to use this
1552 *
1553 * @param string $function
1554 * __FUNCTION__.
1555 * @param string $filename
1556 * $file __FILE__.
1557 *
1558 * @return array
1559 * ids of created objects
1560 */
1561 public function entityCustomGroupWithSingleFieldCreate($function, $filename) {
1562 $params = ['title' => $function];
1563 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1564 $params['extends'] = $entity ? $entity : 'Contact';
1565 $customGroup = $this->customGroupCreate($params);
1566 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function]);
1567 CRM_Core_PseudoConstant::flush();
1568
1569 return ['custom_group_id' => $customGroup['id'], 'custom_field_id' => $customField['id']];
1570 }
1571
1572 /**
1573 * Create a custom group with a single text custom field, multi-select widget, with a variety of option values including upper and lower case.
1574 * See api_v3_SyntaxConformanceTest:testCustomDataGet for how to use this
1575 *
1576 * @param string $function
1577 * __FUNCTION__.
1578 * @param string $filename
1579 * $file __FILE__.
1580 *
1581 * @return array
1582 * ids of created objects
1583 */
1584 public function entityCustomGroupWithSingleStringMultiSelectFieldCreate($function, $filename) {
1585 $params = ['title' => $function];
1586 $entity = substr(basename($filename), 0, strlen(basename($filename)) - 8);
1587 $params['extends'] = $entity ? $entity : 'Contact';
1588 $customGroup = $this->customGroupCreate($params);
1589 $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id'], 'label' => $function, 'html_type' => 'Multi-Select', 'default_value' => 1]);
1590 CRM_Core_PseudoConstant::flush();
1591 $options = [
1592 'defaultValue' => 'Default Value',
1593 'lowercasevalue' => 'Lowercase Value',
1594 1 => 'Integer Value',
1595 'NULL' => 'NULL',
1596 ];
1597 $custom_field_params = ['sequential' => 1, 'id' => $customField['id']];
1598 $custom_field_api_result = $this->callAPISuccess('custom_field', 'get', $custom_field_params);
1599 $this->assertNotEmpty($custom_field_api_result['values'][0]['option_group_id']);
1600 $option_group_params = ['sequential' => 1, 'id' => $custom_field_api_result['values'][0]['option_group_id']];
1601 $option_group_result = $this->callAPISuccess('OptionGroup', 'get', $option_group_params);
1602 $this->assertNotEmpty($option_group_result['values'][0]['name']);
1603 foreach ($options as $option_value => $option_label) {
1604 $option_group_params = ['option_group_id' => $option_group_result['values'][0]['name'], 'value' => $option_value, 'label' => $option_label];
1605 $option_value_result = $this->callAPISuccess('OptionValue', 'create', $option_group_params);
1606 }
1607
1608 return [
1609 'custom_group_id' => $customGroup['id'],
1610 'custom_field_id' => $customField['id'],
1611 'custom_field_option_group_id' => $custom_field_api_result['values'][0]['option_group_id'],
1612 'custom_field_group_options' => $options,
1613 ];
1614 }
1615
1616 /**
1617 * Delete custom group.
1618 *
1619 * @param int $customGroupID
1620 *
1621 * @return array|int
1622 */
1623 public function customGroupDelete($customGroupID) {
1624 $params['id'] = $customGroupID;
1625 return $this->callAPISuccess('custom_group', 'delete', $params);
1626 }
1627
1628 /**
1629 * Create custom field.
1630 *
1631 * @param array $params
1632 * (custom_group_id) is required.
1633 *
1634 * @return array
1635 */
1636 public function customFieldCreate($params) {
1637 $params = array_merge([
1638 'label' => 'Custom Field',
1639 'data_type' => 'String',
1640 'html_type' => 'Text',
1641 'is_searchable' => 1,
1642 'is_active' => 1,
1643 'default_value' => 'defaultValue',
1644 ], $params);
1645
1646 $result = $this->callAPISuccess('custom_field', 'create', $params);
1647 // these 2 functions are called with force to flush static caches
1648 CRM_Core_BAO_CustomField::getTableColumnGroup($result['id'], 1);
1649 CRM_Core_Component::getEnabledComponents(1);
1650 return $result;
1651 }
1652
1653 /**
1654 * Delete custom field.
1655 *
1656 * @param int $customFieldID
1657 *
1658 * @return array|int
1659 */
1660 public function customFieldDelete($customFieldID) {
1661
1662 $params['id'] = $customFieldID;
1663 return $this->callAPISuccess('custom_field', 'delete', $params);
1664 }
1665
1666 /**
1667 * Create note.
1668 *
1669 * @param int $cId
1670 *
1671 * @return array
1672 */
1673 public function noteCreate($cId) {
1674 $params = [
1675 'entity_table' => 'civicrm_contact',
1676 'entity_id' => $cId,
1677 'note' => 'hello I am testing Note',
1678 'contact_id' => $cId,
1679 'modified_date' => date('Ymd'),
1680 'subject' => 'Test Note',
1681 ];
1682
1683 return $this->callAPISuccess('Note', 'create', $params);
1684 }
1685
1686 /**
1687 * Enable CiviCampaign Component.
1688 *
1689 * @param bool $reloadConfig
1690 * Force relaod config or not
1691 */
1692 public function enableCiviCampaign($reloadConfig = TRUE) {
1693 CRM_Core_BAO_ConfigSetting::enableComponent('CiviCampaign');
1694 if ($reloadConfig) {
1695 // force reload of config object
1696 $config = CRM_Core_Config::singleton(TRUE, TRUE);
1697 }
1698 //flush cache by calling with reset
1699 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, TRUE, 'name', TRUE);
1700 }
1701
1702 /**
1703 * Create custom field with Option Values.
1704 *
1705 * @param array $customGroup
1706 * @param string $name
1707 * Name of custom field.
1708 * @param array $extraParams
1709 * Additional parameters to pass through.
1710 *
1711 * @return array|int
1712 */
1713 public function customFieldOptionValueCreate($customGroup, $name, $extraParams = []) {
1714 $fieldParams = [
1715 'custom_group_id' => $customGroup['id'],
1716 'name' => 'test_custom_group',
1717 'label' => 'Country',
1718 'html_type' => 'Select',
1719 'data_type' => 'String',
1720 'weight' => 4,
1721 'is_required' => 1,
1722 'is_searchable' => 0,
1723 'is_active' => 1,
1724 ];
1725
1726 $optionGroup = [
1727 'domain_id' => 1,
1728 'name' => 'option_group1',
1729 'label' => 'option_group_label1',
1730 ];
1731
1732 $optionValue = [
1733 'option_label' => ['Label1', 'Label2'],
1734 'option_value' => ['value1', 'value2'],
1735 'option_name' => [$name . '_1', $name . '_2'],
1736 'option_weight' => [1, 2],
1737 'option_status' => [1, 1],
1738 ];
1739
1740 $params = array_merge($fieldParams, $optionGroup, $optionValue, $extraParams);
1741
1742 return $this->callAPISuccess('custom_field', 'create', $params);
1743 }
1744
1745 /**
1746 * @param $entities
1747 *
1748 * @return bool
1749 */
1750 public function confirmEntitiesDeleted($entities) {
1751 foreach ($entities as $entity) {
1752
1753 $result = $this->callAPISuccess($entity, 'Get', []);
1754 if ($result['error'] == 1 || $result['count'] > 0) {
1755 // > than $entity[0] to allow a value to be passed in? e.g. domain?
1756 return TRUE;
1757 }
1758 }
1759 return FALSE;
1760 }
1761
1762 /**
1763 * Quick clean by emptying tables created for the test.
1764 *
1765 * @param array $tablesToTruncate
1766 * @param bool $dropCustomValueTables
1767 *
1768 * @throws \CRM_Core_Exception
1769 */
1770 public function quickCleanup($tablesToTruncate, $dropCustomValueTables = FALSE) {
1771 if ($this->tx) {
1772 throw new \CRM_Core_Exception("CiviUnitTestCase: quickCleanup() is not compatible with useTransaction()");
1773 }
1774 if ($dropCustomValueTables) {
1775 $optionGroupResult = CRM_Core_DAO::executeQuery('SELECT option_group_id FROM civicrm_custom_field');
1776 while ($optionGroupResult->fetch()) {
1777 // We have a test that sets the option_group_id for a custom group to that of 'activity_type'.
1778 // Then test tearDown deletes it. This is all mildly terrifying but for the context here we can be pretty
1779 // sure the low-numbered (50 is arbitrary) option groups are not ones to 'just delete' in a
1780 // generic cleanup routine.
1781 if (!empty($optionGroupResult->option_group_id) && $optionGroupResult->option_group_id > 50) {
1782 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_option_group WHERE id = ' . $optionGroupResult->option_group_id);
1783 }
1784 }
1785 $tablesToTruncate[] = 'civicrm_custom_group';
1786 $tablesToTruncate[] = 'civicrm_custom_field';
1787 }
1788
1789 $tablesToTruncate = array_unique(array_merge($this->_tablesToTruncate, $tablesToTruncate));
1790
1791 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
1792 foreach ($tablesToTruncate as $table) {
1793 $sql = "TRUNCATE TABLE $table";
1794 CRM_Core_DAO::executeQuery($sql);
1795 }
1796 CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
1797
1798 if ($dropCustomValueTables) {
1799 $dbName = self::getDBName();
1800 $query = "
1801 SELECT TABLE_NAME as tableName
1802 FROM INFORMATION_SCHEMA.TABLES
1803 WHERE TABLE_SCHEMA = '{$dbName}'
1804 AND ( TABLE_NAME LIKE 'civicrm_value_%' )
1805 ";
1806
1807 $tableDAO = CRM_Core_DAO::executeQuery($query);
1808 while ($tableDAO->fetch()) {
1809 $sql = "DROP TABLE {$tableDAO->tableName}";
1810 CRM_Core_DAO::executeQuery($sql);
1811 }
1812 }
1813 }
1814
1815 /**
1816 * Clean up financial entities after financial tests (so we remember to get all the tables :-))
1817 *
1818 * @throws \CRM_Core_Exception
1819 */
1820 public function quickCleanUpFinancialEntities() {
1821 $tablesToTruncate = [
1822 'civicrm_activity',
1823 'civicrm_activity_contact',
1824 'civicrm_contribution',
1825 'civicrm_contribution_soft',
1826 'civicrm_contribution_product',
1827 'civicrm_financial_trxn',
1828 'civicrm_financial_item',
1829 'civicrm_contribution_recur',
1830 'civicrm_line_item',
1831 'civicrm_contribution_page',
1832 'civicrm_payment_processor',
1833 'civicrm_entity_financial_trxn',
1834 'civicrm_membership',
1835 'civicrm_membership_type',
1836 'civicrm_membership_payment',
1837 'civicrm_membership_log',
1838 'civicrm_membership_block',
1839 'civicrm_event',
1840 'civicrm_participant',
1841 'civicrm_participant_payment',
1842 'civicrm_pledge',
1843 'civicrm_pcp_block',
1844 'civicrm_pcp',
1845 'civicrm_pledge_block',
1846 'civicrm_pledge_payment',
1847 'civicrm_price_set_entity',
1848 'civicrm_price_field_value',
1849 'civicrm_price_field',
1850 ];
1851 $this->quickCleanup($tablesToTruncate);
1852 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
1853 $this->restoreDefaultPriceSetConfig();
1854 $this->disableTaxAndInvoicing();
1855 $this->setCurrencySeparators(',');
1856 CRM_Core_PseudoConstant::flush('taxRates');
1857 System::singleton()->flushProcessors();
1858 // @fixme this parameter is leaking - it should not be defined as a class static
1859 // but for now we just handle in tear down.
1860 CRM_Contribute_BAO_Query::$_contribOrSoftCredit = 'only contribs';
1861 }
1862
1863 /**
1864 * Reset the price set config so results exist.
1865 */
1866 public function restoreDefaultPriceSetConfig() {
1867 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_price_set WHERE name NOT IN('default_contribution_amount', 'default_membership_type_amount')");
1868 CRM_Core_DAO::executeQuery("UPDATE civicrm_price_set SET id = 1 WHERE name ='default_contribution_amount'");
1869 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)");
1870 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)");
1871 }
1872
1873 /**
1874 * Recreate default membership types.
1875 */
1876 public function restoreMembershipTypes() {
1877 CRM_Core_DAO::executeQuery(
1878 "REPLACE INTO civicrm_membership_type
1879 (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)
1880 VALUES
1881 (1, 1, 'General', 'Regular annual membership.', 1, 2, 100.00, 'year', 2, 'rolling', NULL, NULL, 7, 'b_a', 'Public', 1, 1),
1882 (2, 1, 'Student', 'Discount membership for full-time students.', 1, 2, 50.00, 'year', 1, 'rolling', NULL, NULL, NULL, NULL, 'Public', 2, 1),
1883 (3, 1, 'Lifetime', 'Lifetime membership.', 1, 2, 1200.00, 'lifetime', 1, 'rolling', NULL, NULL, 7, 'b_a', 'Admin', 3, 1);
1884 ");
1885 }
1886
1887 /*
1888 * Function does a 'Get' on the entity & compares the fields in the Params with those returned
1889 * Default behaviour is to also delete the entity
1890 * @param array $params
1891 * Params array to check against.
1892 * @param int $id
1893 * Id of the entity concerned.
1894 * @param string $entity
1895 * Name of entity concerned (e.g. membership).
1896 * @param bool $delete
1897 * Should the entity be deleted as part of this check.
1898 * @param string $errorText
1899 * Text to print on error.
1900 */
1901
1902 /**
1903 * @param array $params
1904 * @param int $id
1905 * @param $entity
1906 * @param int $delete
1907 * @param string $errorText
1908 *
1909 * @throws CRM_Core_Exception
1910 */
1911 public function getAndCheck($params, $id, $entity, $delete = 1, $errorText = '') {
1912
1913 $result = $this->callAPISuccessGetSingle($entity, [
1914 'id' => $id,
1915 ]);
1916
1917 if ($delete) {
1918 $this->callAPISuccess($entity, 'Delete', [
1919 'id' => $id,
1920 ]);
1921 }
1922 $dateFields = $keys = $dateTimeFields = [];
1923 $fields = $this->callAPISuccess($entity, 'getfields', ['version' => 3, 'action' => 'get']);
1924 foreach ($fields['values'] as $field => $settings) {
1925 if (array_key_exists($field, $result)) {
1926 $keys[CRM_Utils_Array::value('name', $settings, $field)] = $field;
1927 }
1928 else {
1929 $keys[CRM_Utils_Array::value('name', $settings, $field)] = CRM_Utils_Array::value('name', $settings, $field);
1930 }
1931 $type = $settings['type'] ?? NULL;
1932 if ($type == CRM_Utils_Type::T_DATE) {
1933 $dateFields[] = $settings['name'];
1934 // we should identify both real names & unique names as dates
1935 if ($field != $settings['name']) {
1936 $dateFields[] = $field;
1937 }
1938 }
1939 if ($type == CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME) {
1940 $dateTimeFields[] = $settings['name'];
1941 // we should identify both real names & unique names as dates
1942 if ($field != $settings['name']) {
1943 $dateTimeFields[] = $field;
1944 }
1945 }
1946 }
1947
1948 if (strtolower($entity) == 'contribution') {
1949 $params['receive_date'] = date('Y-m-d', strtotime($params['receive_date']));
1950 // this is not returned in id format
1951 unset($params['payment_instrument_id']);
1952 $params['contribution_source'] = $params['source'];
1953 unset($params['source']);
1954 }
1955
1956 foreach ($params as $key => $value) {
1957 if ($key == 'version' || substr($key, 0, 3) == 'api' || !array_key_exists($keys[$key], $result)) {
1958 continue;
1959 }
1960 if (in_array($key, $dateFields)) {
1961 $value = date('Y-m-d', strtotime($value));
1962 $result[$key] = date('Y-m-d', strtotime($result[$key]));
1963 }
1964 if (in_array($key, $dateTimeFields)) {
1965 $value = date('Y-m-d H:i:s', strtotime($value));
1966 $result[$keys[$key]] = date('Y-m-d H:i:s', strtotime(CRM_Utils_Array::value($keys[$key], $result, CRM_Utils_Array::value($key, $result))));
1967 }
1968 $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);
1969 }
1970 }
1971
1972 /**
1973 * Get formatted values in the actual and expected result.
1974 *
1975 * @param array $actual
1976 * Actual calculated values.
1977 * @param array $expected
1978 * Expected values.
1979 */
1980 public function checkArrayEquals(&$actual, &$expected) {
1981 self::unsetId($actual);
1982 self::unsetId($expected);
1983 $this->assertEquals($expected, $actual);
1984 }
1985
1986 /**
1987 * Unset the key 'id' from the array
1988 *
1989 * @param array $unformattedArray
1990 * The array from which the 'id' has to be unset.
1991 */
1992 public static function unsetId(&$unformattedArray) {
1993 $formattedArray = [];
1994 if (array_key_exists('id', $unformattedArray)) {
1995 unset($unformattedArray['id']);
1996 }
1997 if (!empty($unformattedArray['values']) && is_array($unformattedArray['values'])) {
1998 foreach ($unformattedArray['values'] as $key => $value) {
1999 if (is_array($value)) {
2000 foreach ($value as $k => $v) {
2001 if ($k == 'id') {
2002 unset($value[$k]);
2003 }
2004 }
2005 }
2006 elseif ($key == 'id') {
2007 $unformattedArray[$key];
2008 }
2009 $formattedArray = [$value];
2010 }
2011 $unformattedArray['values'] = $formattedArray;
2012 }
2013 }
2014
2015 /**
2016 * Helper to enable/disable custom directory support
2017 *
2018 * @param array $customDirs
2019 * With members:.
2020 * 'php_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2021 * 'template_path' Set to TRUE to use the default, FALSE or "" to disable support, or a string path to use another path
2022 */
2023 public function customDirectories($customDirs) {
2024 $config = CRM_Core_Config::singleton();
2025
2026 if (empty($customDirs['php_path']) || $customDirs['php_path'] === FALSE) {
2027 unset($config->customPHPPathDir);
2028 }
2029 elseif ($customDirs['php_path'] === TRUE) {
2030 $config->customPHPPathDir = dirname(dirname(__FILE__)) . '/custom_directories/php/';
2031 }
2032 else {
2033 $config->customPHPPathDir = $php_path;
2034 }
2035
2036 if (empty($customDirs['template_path']) || $customDirs['template_path'] === FALSE) {
2037 unset($config->customTemplateDir);
2038 }
2039 elseif ($customDirs['template_path'] === TRUE) {
2040 $config->customTemplateDir = dirname(dirname(__FILE__)) . '/custom_directories/templates/';
2041 }
2042 else {
2043 $config->customTemplateDir = $template_path;
2044 }
2045 }
2046
2047 /**
2048 * Generate a temporary folder.
2049 *
2050 * @param string $prefix
2051 *
2052 * @return string
2053 */
2054 public function createTempDir($prefix = 'test-') {
2055 $tempDir = CRM_Utils_File::tempdir($prefix);
2056 $this->tempDirs[] = $tempDir;
2057 return $tempDir;
2058 }
2059
2060 public function cleanTempDirs() {
2061 if (!is_array($this->tempDirs)) {
2062 // fix test errors where this is not set
2063 return;
2064 }
2065 foreach ($this->tempDirs as $tempDir) {
2066 if (is_dir($tempDir)) {
2067 CRM_Utils_File::cleanDir($tempDir, TRUE, FALSE);
2068 }
2069 }
2070 }
2071
2072 /**
2073 * Temporarily replace the singleton extension with a different one.
2074 *
2075 * @param \CRM_Extension_System $system
2076 */
2077 public function setExtensionSystem(CRM_Extension_System $system) {
2078 if ($this->origExtensionSystem == NULL) {
2079 $this->origExtensionSystem = CRM_Extension_System::singleton();
2080 }
2081 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2082 }
2083
2084 public function unsetExtensionSystem() {
2085 if ($this->origExtensionSystem !== NULL) {
2086 CRM_Extension_System::setSingleton($this->origExtensionSystem);
2087 $this->origExtensionSystem = NULL;
2088 }
2089 }
2090
2091 /**
2092 * Temporarily alter the settings-metadata to add a mock setting.
2093 *
2094 * WARNING: The setting metadata will disappear on the next cache-clear.
2095 *
2096 * @param $extras
2097 *
2098 * @return void
2099 */
2100 public function setMockSettingsMetaData($extras) {
2101 CRM_Utils_Hook::singleton()
2102 ->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
2103 $metadata = array_merge($metadata, $extras);
2104 });
2105
2106 Civi::service('settings_manager')->flush();
2107
2108 $fields = $this->callAPISuccess('setting', 'getfields', []);
2109 foreach ($extras as $key => $spec) {
2110 $this->assertNotEmpty($spec['title']);
2111 $this->assertEquals($spec['title'], $fields['values'][$key]['title']);
2112 }
2113 }
2114
2115 /**
2116 * @param string $name
2117 */
2118 public function financialAccountDelete($name) {
2119 $financialAccount = new CRM_Financial_DAO_FinancialAccount();
2120 $financialAccount->name = $name;
2121 if ($financialAccount->find(TRUE)) {
2122 $entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount();
2123 $entityFinancialType->financial_account_id = $financialAccount->id;
2124 $entityFinancialType->delete();
2125 $financialAccount->delete();
2126 }
2127 }
2128
2129 /**
2130 * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together
2131 * (NB unclear if this is still required)
2132 */
2133 public function _sethtmlGlobals() {
2134 $GLOBALS['_HTML_QuickForm_registered_rules'] = [
2135 'required' => [
2136 'html_quickform_rule_required',
2137 'HTML/QuickForm/Rule/Required.php',
2138 ],
2139 'maxlength' => [
2140 'html_quickform_rule_range',
2141 'HTML/QuickForm/Rule/Range.php',
2142 ],
2143 'minlength' => [
2144 'html_quickform_rule_range',
2145 'HTML/QuickForm/Rule/Range.php',
2146 ],
2147 'rangelength' => [
2148 'html_quickform_rule_range',
2149 'HTML/QuickForm/Rule/Range.php',
2150 ],
2151 'email' => [
2152 'html_quickform_rule_email',
2153 'HTML/QuickForm/Rule/Email.php',
2154 ],
2155 'regex' => [
2156 'html_quickform_rule_regex',
2157 'HTML/QuickForm/Rule/Regex.php',
2158 ],
2159 'lettersonly' => [
2160 'html_quickform_rule_regex',
2161 'HTML/QuickForm/Rule/Regex.php',
2162 ],
2163 'alphanumeric' => [
2164 'html_quickform_rule_regex',
2165 'HTML/QuickForm/Rule/Regex.php',
2166 ],
2167 'numeric' => [
2168 'html_quickform_rule_regex',
2169 'HTML/QuickForm/Rule/Regex.php',
2170 ],
2171 'nopunctuation' => [
2172 'html_quickform_rule_regex',
2173 'HTML/QuickForm/Rule/Regex.php',
2174 ],
2175 'nonzero' => [
2176 'html_quickform_rule_regex',
2177 'HTML/QuickForm/Rule/Regex.php',
2178 ],
2179 'callback' => [
2180 'html_quickform_rule_callback',
2181 'HTML/QuickForm/Rule/Callback.php',
2182 ],
2183 'compare' => [
2184 'html_quickform_rule_compare',
2185 'HTML/QuickForm/Rule/Compare.php',
2186 ],
2187 ];
2188 // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
2189 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = [
2190 'group' => [
2191 'HTML/QuickForm/group.php',
2192 'HTML_QuickForm_group',
2193 ],
2194 'hidden' => [
2195 'HTML/QuickForm/hidden.php',
2196 'HTML_QuickForm_hidden',
2197 ],
2198 'reset' => [
2199 'HTML/QuickForm/reset.php',
2200 'HTML_QuickForm_reset',
2201 ],
2202 'checkbox' => [
2203 'HTML/QuickForm/checkbox.php',
2204 'HTML_QuickForm_checkbox',
2205 ],
2206 'file' => [
2207 'HTML/QuickForm/file.php',
2208 'HTML_QuickForm_file',
2209 ],
2210 'image' => [
2211 'HTML/QuickForm/image.php',
2212 'HTML_QuickForm_image',
2213 ],
2214 'password' => [
2215 'HTML/QuickForm/password.php',
2216 'HTML_QuickForm_password',
2217 ],
2218 'radio' => [
2219 'HTML/QuickForm/radio.php',
2220 'HTML_QuickForm_radio',
2221 ],
2222 'button' => [
2223 'HTML/QuickForm/button.php',
2224 'HTML_QuickForm_button',
2225 ],
2226 'submit' => [
2227 'HTML/QuickForm/submit.php',
2228 'HTML_QuickForm_submit',
2229 ],
2230 'select' => [
2231 'HTML/QuickForm/select.php',
2232 'HTML_QuickForm_select',
2233 ],
2234 'hiddenselect' => [
2235 'HTML/QuickForm/hiddenselect.php',
2236 'HTML_QuickForm_hiddenselect',
2237 ],
2238 'text' => [
2239 'HTML/QuickForm/text.php',
2240 'HTML_QuickForm_text',
2241 ],
2242 'textarea' => [
2243 'HTML/QuickForm/textarea.php',
2244 'HTML_QuickForm_textarea',
2245 ],
2246 'fckeditor' => [
2247 'HTML/QuickForm/fckeditor.php',
2248 'HTML_QuickForm_FCKEditor',
2249 ],
2250 'tinymce' => [
2251 'HTML/QuickForm/tinymce.php',
2252 'HTML_QuickForm_TinyMCE',
2253 ],
2254 'dojoeditor' => [
2255 'HTML/QuickForm/dojoeditor.php',
2256 'HTML_QuickForm_dojoeditor',
2257 ],
2258 'link' => [
2259 'HTML/QuickForm/link.php',
2260 'HTML_QuickForm_link',
2261 ],
2262 'advcheckbox' => [
2263 'HTML/QuickForm/advcheckbox.php',
2264 'HTML_QuickForm_advcheckbox',
2265 ],
2266 'date' => [
2267 'HTML/QuickForm/date.php',
2268 'HTML_QuickForm_date',
2269 ],
2270 'static' => [
2271 'HTML/QuickForm/static.php',
2272 'HTML_QuickForm_static',
2273 ],
2274 'header' => [
2275 'HTML/QuickForm/header.php',
2276 'HTML_QuickForm_header',
2277 ],
2278 'html' => [
2279 'HTML/QuickForm/html.php',
2280 'HTML_QuickForm_html',
2281 ],
2282 'hierselect' => [
2283 'HTML/QuickForm/hierselect.php',
2284 'HTML_QuickForm_hierselect',
2285 ],
2286 'autocomplete' => [
2287 'HTML/QuickForm/autocomplete.php',
2288 'HTML_QuickForm_autocomplete',
2289 ],
2290 'xbutton' => [
2291 'HTML/QuickForm/xbutton.php',
2292 'HTML_QuickForm_xbutton',
2293 ],
2294 'advmultiselect' => [
2295 'HTML/QuickForm/advmultiselect.php',
2296 'HTML_QuickForm_advmultiselect',
2297 ],
2298 ];
2299 }
2300
2301 /**
2302 * Set up an acl allowing contact to see 2 specified groups
2303 * - $this->_permissionedGroup & $this->_permissionedDisabledGroup
2304 *
2305 * You need to have pre-created these groups & created the user e.g
2306 * $this->createLoggedInUser();
2307 * $this->_permissionedDisabledGroup = $this->groupCreate(array('title' => 'pick-me-disabled', 'is_active' => 0, 'name' => 'pick-me-disabled'));
2308 * $this->_permissionedGroup = $this->groupCreate(array('title' => 'pick-me-active', 'is_active' => 1, 'name' => 'pick-me-active'));
2309 *
2310 * @param bool $isProfile
2311 */
2312 public function setupACL($isProfile = FALSE) {
2313 global $_REQUEST;
2314 $_REQUEST = $this->_params;
2315
2316 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
2317 $optionGroupID = $this->callAPISuccessGetValue('option_group', ['return' => 'id', 'name' => 'acl_role']);
2318 $ov = new CRM_Core_DAO_OptionValue();
2319 $ov->option_group_id = $optionGroupID;
2320 $ov->value = 55;
2321 if ($ov->find(TRUE)) {
2322 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_option_value WHERE id = {$ov->id}");
2323 }
2324 $optionValue = $this->callAPISuccess('option_value', 'create', [
2325 'option_group_id' => $optionGroupID,
2326 'label' => 'pick me',
2327 'value' => 55,
2328 ]);
2329
2330 CRM_Core_DAO::executeQuery("
2331 TRUNCATE civicrm_acl_cache
2332 ");
2333
2334 CRM_Core_DAO::executeQuery("
2335 TRUNCATE civicrm_acl_contact_cache
2336 ");
2337
2338 CRM_Core_DAO::executeQuery("
2339 INSERT INTO civicrm_acl_entity_role (
2340 `acl_role_id`, `entity_table`, `entity_id`, `is_active`
2341 ) VALUES (55, 'civicrm_group', {$this->_permissionedGroup}, 1);
2342 ");
2343
2344 if ($isProfile) {
2345 CRM_Core_DAO::executeQuery("
2346 INSERT INTO civicrm_acl (
2347 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2348 )
2349 VALUES (
2350 'view picked', 'civicrm_acl_role', 55, 'Edit', 'civicrm_uf_group', 0, 1
2351 );
2352 ");
2353 }
2354 else {
2355 CRM_Core_DAO::executeQuery("
2356 INSERT INTO civicrm_acl (
2357 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2358 )
2359 VALUES (
2360 'view picked', 'civicrm_group', $this->_permissionedGroup , 'Edit', 'civicrm_saved_search', {$this->_permissionedGroup}, 1
2361 );
2362 ");
2363
2364 CRM_Core_DAO::executeQuery("
2365 INSERT INTO civicrm_acl (
2366 `name`, `entity_table`, `entity_id`, `operation`, `object_table`, `object_id`, `is_active`
2367 )
2368 VALUES (
2369 'view picked', 'civicrm_group', $this->_permissionedGroup, 'Edit', 'civicrm_saved_search', {$this->_permissionedDisabledGroup}, 1
2370 );
2371 ");
2372 }
2373
2374 $this->_loggedInUser = CRM_Core_Session::singleton()->get('userID');
2375 $this->callAPISuccess('group_contact', 'create', [
2376 'group_id' => $this->_permissionedGroup,
2377 'contact_id' => $this->_loggedInUser,
2378 ]);
2379
2380 if (!$isProfile) {
2381 //flush cache
2382 CRM_ACL_BAO_Cache::resetCache();
2383 CRM_ACL_API::groupPermission('whatever', 9999, NULL, 'civicrm_saved_search', NULL, NULL);
2384 }
2385 }
2386
2387 /**
2388 * Alter default price set so that the field numbers are not all 1 (hiding errors)
2389 */
2390 public function offsetDefaultPriceSet() {
2391 $contributionPriceSet = $this->callAPISuccess('price_set', 'getsingle', ['name' => 'default_contribution_amount']);
2392 $firstID = $contributionPriceSet['id'];
2393 $this->callAPISuccess('price_set', 'create', [
2394 'id' => $contributionPriceSet['id'],
2395 'is_active' => 0,
2396 'name' => 'old',
2397 ]);
2398 unset($contributionPriceSet['id']);
2399 $newPriceSet = $this->callAPISuccess('price_set', 'create', $contributionPriceSet);
2400 $priceField = $this->callAPISuccess('price_field', 'getsingle', [
2401 'price_set_id' => $firstID,
2402 'options' => ['limit' => 1],
2403 ]);
2404 unset($priceField['id']);
2405 $priceField['price_set_id'] = $newPriceSet['id'];
2406 $newPriceField = $this->callAPISuccess('price_field', 'create', $priceField);
2407 $priceFieldValue = $this->callAPISuccess('price_field_value', 'getsingle', [
2408 'price_set_id' => $firstID,
2409 'sequential' => 1,
2410 'options' => ['limit' => 1],
2411 ]);
2412
2413 unset($priceFieldValue['id']);
2414 //create some padding to use up ids
2415 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2416 $this->callAPISuccess('price_field_value', 'create', $priceFieldValue);
2417 $this->callAPISuccess('price_field_value', 'create', array_merge($priceFieldValue, ['price_field_id' => $newPriceField['id']]));
2418 }
2419
2420 /**
2421 * Create an instance of the paypal processor.
2422 *
2423 * @todo this isn't a great place to put it - but really it belongs on a class that extends
2424 * this parent class & we don't have a structure for that yet
2425 * There is another function to this effect on the PaypalPro test but it appears to be silently failing
2426 * & the best protection against that is the functions this class affords
2427 *
2428 * @param array $params
2429 *
2430 * @return int $result['id'] payment processor id
2431 */
2432 public function paymentProcessorCreate($params = []) {
2433 $params = array_merge([
2434 'name' => 'demo',
2435 'domain_id' => CRM_Core_Config::domainID(),
2436 'payment_processor_type_id' => 'PayPal',
2437 'is_active' => 1,
2438 'is_default' => 0,
2439 'is_test' => 1,
2440 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in',
2441 'password' => '1183377788',
2442 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH',
2443 'url_site' => 'https://www.sandbox.paypal.com/',
2444 'url_api' => 'https://api-3t.sandbox.paypal.com/',
2445 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif',
2446 'class_name' => 'Payment_PayPalImpl',
2447 'billing_mode' => 3,
2448 'financial_type_id' => 1,
2449 'financial_account_id' => 12,
2450 // Credit card = 1 so can pass 'by accident'.
2451 'payment_instrument_id' => 'Debit Card',
2452 ], $params);
2453 if (!is_numeric($params['payment_processor_type_id'])) {
2454 // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
2455 //here
2456 $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
2457 'name' => $params['payment_processor_type_id'],
2458 'return' => 'id',
2459 ], 'integer');
2460 }
2461 $result = $this->callAPISuccess('payment_processor', 'create', $params);
2462 return $result['id'];
2463 }
2464
2465 /**
2466 * Set up initial recurring payment allowing subsequent IPN payments.
2467 *
2468 * @param array $recurParams (Optional)
2469 * @param array $contributionParams (Optional)
2470 *
2471 * @throws \CRM_Core_Exception
2472 */
2473 public function setupRecurringPaymentProcessorTransaction($recurParams = [], $contributionParams = []) {
2474 $contributionParams = array_merge([
2475 'total_amount' => '200',
2476 'invoice_id' => $this->_invoiceID,
2477 'financial_type_id' => 'Donation',
2478 'contribution_status_id' => 'Pending',
2479 'contact_id' => $this->_contactID,
2480 'contribution_page_id' => $this->_contributionPageID,
2481 'payment_processor_id' => $this->_paymentProcessorID,
2482 'is_test' => 0,
2483 'receive_date' => '2019-07-25 07:34:23',
2484 'skipCleanMoney' => TRUE,
2485 ], $contributionParams);
2486 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
2487 'contact_id' => $this->_contactID,
2488 'amount' => 1000,
2489 'sequential' => 1,
2490 'installments' => 5,
2491 'frequency_unit' => 'Month',
2492 'frequency_interval' => 1,
2493 'invoice_id' => $this->_invoiceID,
2494 'contribution_status_id' => 2,
2495 'payment_processor_id' => $this->_paymentProcessorID,
2496 // processor provided ID - use contact ID as proxy.
2497 'processor_id' => $this->_contactID,
2498 'api.Order.create' => $contributionParams,
2499 ], $recurParams))['values'][0];
2500 $this->_contributionRecurID = $contributionRecur['id'];
2501 $this->_contributionID = $contributionRecur['api.Order.create']['id'];
2502 $this->ids['Contribution'][0] = $this->_contributionID;
2503 }
2504
2505 /**
2506 * 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
2507 *
2508 * @param array $params Optionally modify params for membership/recur (duration_unit/frequency_unit)
2509 *
2510 * @throws \CRM_Core_Exception
2511 */
2512 public function setupMembershipRecurringPaymentProcessorTransaction($params = []) {
2513 $membershipParams = $recurParams = [];
2514 if (!empty($params['duration_unit'])) {
2515 $membershipParams['duration_unit'] = $params['duration_unit'];
2516 }
2517 if (!empty($params['frequency_unit'])) {
2518 $recurParams['frequency_unit'] = $params['frequency_unit'];
2519 }
2520
2521 $this->ids['membership_type'] = $this->membershipTypeCreate($membershipParams);
2522 //create a contribution so our membership & contribution don't both have id = 1
2523 if ($this->callAPISuccess('Contribution', 'getcount', []) === 0) {
2524 $this->contributionCreate([
2525 'contact_id' => $this->_contactID,
2526 'is_test' => 1,
2527 'financial_type_id' => 1,
2528 'invoice_id' => 'abcd',
2529 'trxn_id' => 345,
2530 'receive_date' => '2019-07-25 07:34:23',
2531 ]);
2532 }
2533
2534 $this->ids['membership'] = $this->callAPISuccess('Membership', 'create', [
2535 'contact_id' => $this->_contactID,
2536 'membership_type_id' => $this->ids['membership_type'],
2537 'format.only_id' => TRUE,
2538 'source' => 'Payment',
2539 'skipLineItem' => TRUE,
2540 ]);
2541 $this->setupRecurringPaymentProcessorTransaction($recurParams, [
2542 'line_items' => [
2543 [
2544 'line_item' => [
2545 [
2546 'entity_table' => 'civicrm_membership',
2547 'entity_id' => $this->ids['membership'],
2548 'label' => 'General',
2549 'qty' => 1,
2550 'unit_price' => 200,
2551 'line_total' => 200,
2552 'financial_type_id' => 1,
2553 'price_field_id' => $this->callAPISuccess('price_field', 'getvalue', [
2554 'return' => 'id',
2555 'label' => 'Membership Amount',
2556 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2557 ]),
2558 'price_field_value_id' => $this->callAPISuccess('price_field_value', 'getvalue', [
2559 'return' => 'id',
2560 'label' => 'General',
2561 'options' => ['limit' => 1, 'sort' => 'id DESC'],
2562 ]),
2563 ],
2564 ],
2565 ],
2566 ],
2567 ]);
2568 $this->callAPISuccess('Membership', 'create', ['id' => $this->ids['membership'], 'contribution_recur_id' => $this->_contributionRecurID]);
2569 }
2570
2571 /**
2572 * @param $message
2573 *
2574 * @throws Exception
2575 */
2576 public function CiviUnitTestCase_fatalErrorHandler($message) {
2577 throw new Exception("{$message['message']}: {$message['code']}");
2578 }
2579
2580 /**
2581 * Wrap the entire test case in a transaction.
2582 *
2583 * Only subsequent DB statements will be wrapped in TX -- this cannot
2584 * retroactively wrap old DB statements. Therefore, it makes sense to
2585 * call this at the beginning of setUp().
2586 *
2587 * Note: Recall that TRUNCATE and ALTER will force-commit transactions, so
2588 * this option does not work with, e.g., custom-data.
2589 *
2590 * WISHLIST: Monitor SQL queries in unit-tests and generate an exception
2591 * if TRUNCATE or ALTER is called while using a transaction.
2592 *
2593 * @param bool $nest
2594 * Whether to use nesting or reference-counting.
2595 */
2596 public function useTransaction($nest = TRUE) {
2597 if (!$this->tx) {
2598 $this->tx = new CRM_Core_Transaction($nest);
2599 $this->tx->rollback();
2600 }
2601 }
2602
2603 /**
2604 * Assert the attachment exists.
2605 *
2606 * @param bool $exists
2607 * @param array $apiResult
2608 */
2609 protected function assertAttachmentExistence($exists, $apiResult) {
2610 $fileId = $apiResult['id'];
2611 $this->assertTrue(is_numeric($fileId));
2612 $this->assertEquals($exists, file_exists($apiResult['values'][$fileId]['path']));
2613 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_file WHERE id = %1', [
2614 1 => [$fileId, 'Int'],
2615 ]);
2616 $this->assertDBQuery($exists ? 1 : 0, 'SELECT count(*) FROM civicrm_entity_file WHERE id = %1', [
2617 1 => [$fileId, 'Int'],
2618 ]);
2619 }
2620
2621 /**
2622 * Assert 2 sql strings are the same, ignoring double spaces.
2623 *
2624 * @param string $expectedSQL
2625 * @param string $actualSQL
2626 * @param string $message
2627 */
2628 protected function assertLike($expectedSQL, $actualSQL, $message = 'different sql') {
2629 $expected = trim((preg_replace('/[ \r\n\t]+/', ' ', $expectedSQL)));
2630 $actual = trim((preg_replace('/[ \r\n\t]+/', ' ', $actualSQL)));
2631 $this->assertEquals($expected, $actual, $message);
2632 }
2633
2634 /**
2635 * Create a price set for an event.
2636 *
2637 * @param int $feeTotal
2638 * @param int $minAmt
2639 * @param string $type
2640 *
2641 * @param array $options
2642 *
2643 * @return int
2644 * Price Set ID.
2645 * @throws \CRM_Core_Exception
2646 */
2647 protected function eventPriceSetCreate($feeTotal, $minAmt = 0, $type = 'Text', $options = [['name' => 'hundy', 'amount' => 100]]) {
2648 // creating price set, price field
2649 $paramsSet['title'] = 'Price Set';
2650 $paramsSet['name'] = CRM_Utils_String::titleToVar('Price Set');
2651 $paramsSet['is_active'] = FALSE;
2652 $paramsSet['extends'] = 1;
2653 $paramsSet['min_amount'] = $minAmt;
2654
2655 $priceSet = CRM_Price_BAO_PriceSet::create($paramsSet);
2656 $this->_ids['price_set'] = $priceSet->id;
2657
2658 $paramsField = [
2659 'label' => 'Price Field',
2660 'name' => CRM_Utils_String::titleToVar('Price Field'),
2661 'html_type' => $type,
2662 'price' => $feeTotal,
2663 'option_label' => ['1' => 'Price Field'],
2664 'option_value' => ['1' => $feeTotal],
2665 'option_name' => ['1' => $feeTotal],
2666 'option_weight' => ['1' => 1],
2667 'option_amount' => ['1' => 1],
2668 'is_display_amounts' => 1,
2669 'weight' => 1,
2670 'options_per_line' => 1,
2671 'is_active' => ['1' => 1],
2672 'price_set_id' => $this->_ids['price_set'],
2673 'is_enter_qty' => 1,
2674 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2675 ];
2676 if ($type === 'Radio') {
2677 foreach ($options as $index => $option) {
2678 $paramsField['is_enter_qty'] = 0;
2679 $optionID = $index + 2;
2680 $paramsField['option_value'][$optionID] = $paramsField['option_weight'][$optionID] = $paramsField['option_amount'][$optionID] = $option['amount'];
2681 $paramsField['option_label'][$optionID] = $paramsField['option_name'][$optionID] = $option['name'];
2682 }
2683
2684 }
2685 $this->callAPISuccess('PriceField', 'create', $paramsField);
2686 $fields = $this->callAPISuccess('PriceField', 'get', ['price_set_id' => $this->_ids['price_set']]);
2687 $this->_ids['price_field'] = array_keys($fields['values']);
2688 $fieldValues = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $this->_ids['price_field'][0]]);
2689 $this->_ids['price_field_value'] = array_keys($fieldValues['values']);
2690
2691 return $this->_ids['price_set'];
2692 }
2693
2694 /**
2695 * Add a profile to a contribution page.
2696 *
2697 * @param string $name
2698 * @param int $contributionPageID
2699 * @param string $module
2700 */
2701 protected function addProfile($name, $contributionPageID, $module = 'CiviContribute') {
2702 $params = [
2703 'uf_group_id' => $name,
2704 'module' => $module,
2705 'entity_table' => 'civicrm_contribution_page',
2706 'entity_id' => $contributionPageID,
2707 'weight' => 1,
2708 ];
2709 if ($module !== 'CiviContribute') {
2710 $params['module_data'] = [$module => []];
2711 }
2712 $this->callAPISuccess('UFJoin', 'create', $params);
2713 }
2714
2715 /**
2716 * Add participant with contribution
2717 *
2718 * @return array
2719 *
2720 * @throws \CRM_Core_Exception
2721 */
2722 protected function createPartiallyPaidParticipantOrder() {
2723 $orderParams = $this->getParticipantOrderParams();
2724 $orderParams['api.Payment.create'] = ['total_amount' => 150];
2725 return $this->callAPISuccess('Order', 'create', $orderParams);
2726 }
2727
2728 /**
2729 * Create price set
2730 *
2731 * @param string $component
2732 * @param int $componentId
2733 * @param array $priceFieldOptions
2734 *
2735 * @return array
2736 */
2737 protected function createPriceSet($component = 'contribution_page', $componentId = NULL, $priceFieldOptions = []) {
2738 $paramsSet['title'] = 'Price Set' . substr(sha1(rand()), 0, 7);
2739 $paramsSet['name'] = CRM_Utils_String::titleToVar($paramsSet['title']);
2740 $paramsSet['is_active'] = TRUE;
2741 $paramsSet['financial_type_id'] = 'Event Fee';
2742 $paramsSet['extends'] = 1;
2743 $priceSet = $this->callAPISuccess('price_set', 'create', $paramsSet);
2744 $priceSetId = $priceSet['id'];
2745 //Checking for priceset added in the table.
2746 $this->assertDBCompareValue('CRM_Price_BAO_PriceSet', $priceSetId, 'title',
2747 'id', $paramsSet['title'], 'Check DB for created priceset'
2748 );
2749 $paramsField = array_merge([
2750 'label' => 'Price Field',
2751 'name' => CRM_Utils_String::titleToVar('Price Field'),
2752 'html_type' => 'CheckBox',
2753 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2754 'option_value' => ['1' => 100, '2' => 200],
2755 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
2756 'option_weight' => ['1' => 1, '2' => 2],
2757 'option_amount' => ['1' => 100, '2' => 200],
2758 'is_display_amounts' => 1,
2759 'weight' => 1,
2760 'options_per_line' => 1,
2761 'is_active' => ['1' => 1, '2' => 1],
2762 'price_set_id' => $priceSet['id'],
2763 'is_enter_qty' => 1,
2764 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
2765 ], $priceFieldOptions);
2766
2767 $priceField = CRM_Price_BAO_PriceField::create($paramsField);
2768 if ($componentId) {
2769 CRM_Price_BAO_PriceSet::addTo('civicrm_' . $component, $componentId, $priceSetId);
2770 }
2771 return $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id]);
2772 }
2773
2774 /**
2775 * Replace the template with a test-oriented template designed to show all the variables.
2776 *
2777 * @param string $templateName
2778 * @param string $type
2779 */
2780 protected function swapMessageTemplateForTestTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2781 $testTemplate = file_get_contents(__DIR__ . '/../../templates/message_templates/' . $templateName . '_' . $type . '.tpl');
2782 CRM_Core_DAO::executeQuery(
2783 "UPDATE civicrm_option_group og
2784 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2785 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2786 SET m.msg_{$type} = %1
2787 WHERE og.name LIKE 'msg_tpl_workflow_%'
2788 AND ov.name = '{$templateName}'
2789 AND m.is_default = 1", [1 => [$testTemplate, 'String']]
2790 );
2791 }
2792
2793 /**
2794 * Reinstate the default template.
2795 *
2796 * @param string $templateName
2797 * @param string $type
2798 */
2799 protected function revertTemplateToReservedTemplate($templateName = 'contribution_online_receipt', $type = 'html') {
2800 CRM_Core_DAO::executeQuery(
2801 "UPDATE civicrm_option_group og
2802 LEFT JOIN civicrm_option_value ov ON ov.option_group_id = og.id
2803 LEFT JOIN civicrm_msg_template m ON m.workflow_id = ov.id
2804 LEFT JOIN civicrm_msg_template m2 ON m2.workflow_id = ov.id AND m2.is_reserved = 1
2805 SET m.msg_{$type} = m2.msg_{$type}
2806 WHERE og.name = 'msg_tpl_workflow_contribution'
2807 AND ov.name = '{$templateName}'
2808 AND m.is_default = 1"
2809 );
2810 }
2811
2812 /**
2813 * Flush statics relating to financial type.
2814 */
2815 protected function flushFinancialTypeStatics() {
2816 if (isset(\Civi::$statics['CRM_Financial_BAO_FinancialType'])) {
2817 unset(\Civi::$statics['CRM_Financial_BAO_FinancialType']);
2818 }
2819 if (isset(\Civi::$statics['CRM_Contribute_PseudoConstant'])) {
2820 unset(\Civi::$statics['CRM_Contribute_PseudoConstant']);
2821 }
2822 CRM_Contribute_PseudoConstant::flush('financialType');
2823 CRM_Contribute_PseudoConstant::flush('membershipType');
2824 // Pseudoconstants may be saved to the cache table.
2825 CRM_Core_DAO::executeQuery("TRUNCATE civicrm_cache");
2826 CRM_Financial_BAO_FinancialType::$_statusACLFt = [];
2827 CRM_Financial_BAO_FinancialType::$_availableFinancialTypes = NULL;
2828 }
2829
2830 /**
2831 * Set the permissions to the supplied array.
2832 *
2833 * @param array $permissions
2834 */
2835 protected function setPermissions($permissions) {
2836 CRM_Core_Config::singleton()->userPermissionClass->permissions = $permissions;
2837 $this->flushFinancialTypeStatics();
2838 }
2839
2840 /**
2841 * @param array $params
2842 * @param $context
2843 */
2844 public function _checkFinancialRecords($params, $context) {
2845 $entityParams = [
2846 'entity_id' => $params['id'],
2847 'entity_table' => 'civicrm_contribution',
2848 ];
2849 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $params['id']]);
2850 $this->assertEquals($contribution['total_amount'] - $contribution['fee_amount'], $contribution['net_amount']);
2851 if ($context == 'pending') {
2852 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
2853 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
2854 return;
2855 }
2856 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
2857 $trxnParams = [
2858 'id' => $trxn['financial_trxn_id'],
2859 ];
2860 if ($context != 'online' && $context != 'payLater') {
2861 $compareParams = [
2862 'to_financial_account_id' => 6,
2863 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2864 'status_id' => 1,
2865 ];
2866 }
2867 if ($context == 'feeAmount') {
2868 $compareParams['fee_amount'] = 50;
2869 }
2870 elseif ($context == 'online') {
2871 $compareParams = [
2872 'to_financial_account_id' => 12,
2873 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2874 'status_id' => 1,
2875 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, 1),
2876 ];
2877 }
2878 elseif ($context == 'payLater') {
2879 $compareParams = [
2880 'to_financial_account_id' => 7,
2881 'total_amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2882 'status_id' => 2,
2883 ];
2884 }
2885 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2886 $entityParams = [
2887 'financial_trxn_id' => $trxn['financial_trxn_id'],
2888 'entity_table' => 'civicrm_financial_item',
2889 ];
2890 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
2891 $fitemParams = [
2892 'id' => $entityTrxn['entity_id'],
2893 ];
2894 $compareParams = [
2895 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2896 'status_id' => 1,
2897 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
2898 ];
2899 if ($context == 'payLater') {
2900 $compareParams = [
2901 'amount' => (float) CRM_Utils_Array::value('total_amount', $params, 100.00),
2902 'status_id' => 3,
2903 'financial_account_id' => CRM_Utils_Array::value('financial_account_id', $params, 1),
2904 ];
2905 }
2906 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2907 if ($context == 'feeAmount') {
2908 $maxParams = [
2909 'entity_id' => $params['id'],
2910 'entity_table' => 'civicrm_contribution',
2911 ];
2912 $maxTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($maxParams, TRUE));
2913 $trxnParams = [
2914 'id' => $maxTrxn['financial_trxn_id'],
2915 ];
2916 $compareParams = [
2917 'to_financial_account_id' => 5,
2918 'from_financial_account_id' => 6,
2919 'total_amount' => 50,
2920 'status_id' => 1,
2921 ];
2922 $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['id'], 'DESC');
2923 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
2924 $fitemParams = [
2925 'entity_id' => $trxnId['financialTrxnId'],
2926 'entity_table' => 'civicrm_financial_trxn',
2927 ];
2928 $compareParams = [
2929 'amount' => 50.00,
2930 'status_id' => 1,
2931 'financial_account_id' => 5,
2932 ];
2933 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
2934 }
2935 // This checks that empty Sales tax rows are not being created. If for any reason it needs to be removed the
2936 // line should be copied into all the functions that call this function & evaluated there
2937 // Be really careful not to remove or bypass this without ensuring stray rows do not re-appear
2938 // when calling completeTransaction or repeatTransaction.
2939 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2940 }
2941
2942 /**
2943 * Return financial type id on basis of name
2944 *
2945 * @param string $name Financial type m/c name
2946 *
2947 * @return int
2948 */
2949 public function getFinancialTypeId($name) {
2950 return CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $name, 'id', 'name');
2951 }
2952
2953 /**
2954 * Cleanup function for contents of $this->ids.
2955 *
2956 * This is a best effort cleanup to use in tear downs etc.
2957 *
2958 * It will not fail if the data has already been removed (some tests may do
2959 * their own cleanup).
2960 */
2961 protected function cleanUpSetUpIDs() {
2962 foreach ($this->setupIDs as $entity => $id) {
2963 try {
2964 civicrm_api3($entity, 'delete', ['id' => $id, 'skip_undelete' => 1]);
2965 }
2966 catch (CiviCRM_API3_Exception $e) {
2967 // This is a best-effort cleanup function, ignore.
2968 }
2969 }
2970 }
2971
2972 /**
2973 * Create Financial Type.
2974 *
2975 * @param array $params
2976 *
2977 * @return array
2978 */
2979 protected function createFinancialType($params = []) {
2980 $params = array_merge($params,
2981 [
2982 'name' => 'Financial-Type -' . substr(sha1(rand()), 0, 7),
2983 'is_active' => 1,
2984 ]
2985 );
2986 return $this->callAPISuccess('FinancialType', 'create', $params);
2987 }
2988
2989 /**
2990 * Create Payment Instrument.
2991 *
2992 * @param array $params
2993 * @param string $financialAccountName
2994 *
2995 * @return int
2996 */
2997 protected function createPaymentInstrument($params = [], $financialAccountName = 'Donation') {
2998 $params = array_merge([
2999 'label' => 'Payment Instrument -' . substr(sha1(rand()), 0, 7),
3000 'option_group_id' => 'payment_instrument',
3001 'is_active' => 1,
3002 ], $params);
3003 $newPaymentInstrument = $this->callAPISuccess('OptionValue', 'create', $params)['id'];
3004
3005 $relationTypeID = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
3006
3007 $financialAccountParams = [
3008 'entity_table' => 'civicrm_option_value',
3009 'entity_id' => $newPaymentInstrument,
3010 'account_relationship' => $relationTypeID,
3011 'financial_account_id' => $this->callAPISuccess('FinancialAccount', 'getValue', ['name' => $financialAccountName, 'return' => 'id']),
3012 ];
3013 CRM_Financial_BAO_FinancialTypeAccount::add($financialAccountParams);
3014
3015 return CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $params['label']);
3016 }
3017
3018 /**
3019 * Enable Tax and Invoicing
3020 *
3021 * @param array $params
3022 *
3023 * @return \Civi\Core\SettingsBag
3024 */
3025 protected function enableTaxAndInvoicing($params = []) {
3026 // Enable component contribute setting
3027 $contributeSetting = array_merge($params,
3028 [
3029 'invoicing' => 1,
3030 'invoice_prefix' => 'INV_',
3031 'due_date' => 10,
3032 'due_date_period' => 'days',
3033 'notes' => '',
3034 'is_email_pdf' => 1,
3035 'tax_term' => 'Sales Tax',
3036 'tax_display_settings' => 'Inclusive',
3037 ]
3038 );
3039 return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
3040 }
3041
3042 /**
3043 * Enable Tax and Invoicing
3044 *
3045 * @throws \CRM_Core_Exception
3046 */
3047 protected function disableTaxAndInvoicing() {
3048 $accounts = $this->callAPISuccess('EntityFinancialAccount', 'get', ['account_relationship' => 'Sales Tax Account is'])['values'];
3049 foreach ($accounts as $account) {
3050 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $account['id']]);
3051 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $account['financial_account_id']]);
3052 }
3053
3054 if (!empty(\Civi::$statics['CRM_Core_PseudoConstant']) && isset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates'])) {
3055 unset(\Civi::$statics['CRM_Core_PseudoConstant']['taxRates']);
3056 }
3057 return Civi::settings()->set('invoicing', FALSE);
3058 }
3059
3060 /**
3061 * Add Sales Tax relation for financial type with financial account.
3062 *
3063 * @param int $financialTypeId
3064 *
3065 * @return obj
3066 */
3067 protected function relationForFinancialTypeWithFinancialAccount($financialTypeId) {
3068 $params = [
3069 'name' => 'Sales tax account ' . substr(sha1(rand()), 0, 4),
3070 'financial_account_type_id' => key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Liability' ")),
3071 'is_deductible' => 1,
3072 'is_tax' => 1,
3073 'tax_rate' => 10,
3074 'is_active' => 1,
3075 ];
3076 $account = CRM_Financial_BAO_FinancialAccount::add($params);
3077 $entityParams = [
3078 'entity_table' => 'civicrm_financial_type',
3079 'entity_id' => $financialTypeId,
3080 'account_relationship' => key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")),
3081 ];
3082
3083 // set tax rate (as 10) for provided financial type ID to static variable, later used to fetch tax rates of all financial types
3084 \Civi::$statics['CRM_Core_PseudoConstant']['taxRates'][$financialTypeId] = 10;
3085
3086 //CRM-20313: As per unique index added in civicrm_entity_financial_account table,
3087 // first check if there's any record on basis of unique key (entity_table, account_relationship, entity_id)
3088 $dao = new CRM_Financial_DAO_EntityFinancialAccount();
3089 $dao->copyValues($entityParams);
3090 $dao->find();
3091 if ($dao->fetch()) {
3092 $entityParams['id'] = $dao->id;
3093 }
3094 $entityParams['financial_account_id'] = $account->id;
3095
3096 return CRM_Financial_BAO_FinancialTypeAccount::add($entityParams);
3097 }
3098
3099 /**
3100 * Create price set with contribution test for test setup.
3101 *
3102 * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
3103 * on parent class at some point (fn is not in 4.4).
3104 *
3105 * @param $entity
3106 * @param array $params
3107 */
3108 public function createPriceSetWithPage($entity = NULL, $params = []) {
3109 $membershipTypeID = $this->membershipTypeCreate(['name' => 'Special']);
3110 $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', [
3111 'title' => "Test Contribution Page",
3112 'financial_type_id' => 1,
3113 'currency' => 'NZD',
3114 'goal_amount' => 50,
3115 'is_pay_later' => 1,
3116 'is_monetary' => TRUE,
3117 'is_email_receipt' => FALSE,
3118 ]);
3119 $priceSet = $this->callAPISuccess('price_set', 'create', [
3120 'is_quick_config' => 0,
3121 'extends' => 'CiviMember',
3122 'financial_type_id' => 1,
3123 'title' => 'my Page',
3124 ]);
3125 $priceSetID = $priceSet['id'];
3126
3127 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
3128 $priceField = $this->callAPISuccess('price_field', 'create', [
3129 'price_set_id' => $priceSetID,
3130 'label' => 'Goat Breed',
3131 'html_type' => 'Radio',
3132 ]);
3133 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3134 'price_set_id' => $priceSetID,
3135 'price_field_id' => $priceField['id'],
3136 'label' => 'Long Haired Goat',
3137 'amount' => 20,
3138 'financial_type_id' => 'Donation',
3139 'membership_type_id' => $membershipTypeID,
3140 'membership_num_terms' => 1,
3141 ]);
3142 $this->_ids['price_field_value'] = [$priceFieldValue['id']];
3143 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3144 'price_set_id' => $priceSetID,
3145 'price_field_id' => $priceField['id'],
3146 'label' => 'Shoe-eating Goat',
3147 'amount' => 10,
3148 'financial_type_id' => 'Donation',
3149 'membership_type_id' => $membershipTypeID,
3150 'membership_num_terms' => 2,
3151 ]);
3152 $this->_ids['price_field_value'][] = $priceFieldValue['id'];
3153
3154 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
3155 'price_set_id' => $priceSetID,
3156 'price_field_id' => $priceField['id'],
3157 'label' => 'Shoe-eating Goat',
3158 'amount' => 10,
3159 'financial_type_id' => 'Donation',
3160 ]);
3161 $this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];
3162
3163 $this->_ids['price_set'] = $priceSetID;
3164 $this->_ids['contribution_page'] = $contributionPageResult['id'];
3165 $this->_ids['price_field'] = [$priceField['id']];
3166
3167 $this->_ids['membership_type'] = $membershipTypeID;
3168 }
3169
3170 /**
3171 * Only specified contact returned.
3172 *
3173 * @implements CRM_Utils_Hook::aclWhereClause
3174 *
3175 * @param $type
3176 * @param $tables
3177 * @param $whereTables
3178 * @param $contactID
3179 * @param $where
3180 */
3181 public function aclWhereMultipleContacts($type, &$tables, &$whereTables, &$contactID, &$where) {
3182 $where = " contact_a.id IN (" . implode(', ', $this->allowedContacts) . ")";
3183 }
3184
3185 /**
3186 * @implements CRM_Utils_Hook::selectWhereClause
3187 *
3188 * @param string $entity
3189 * @param array $clauses
3190 */
3191 public function selectWhereClauseHook($entity, &$clauses) {
3192 if ($entity == 'Event') {
3193 $clauses['event_type_id'][] = "IN (2, 3, 4)";
3194 }
3195 }
3196
3197 /**
3198 * An implementation of hook_civicrm_post used with all our test cases.
3199 *
3200 * @param $op
3201 * @param string $objectName
3202 * @param int $objectId
3203 * @param $objectRef
3204 */
3205 public function onPost($op, $objectName, $objectId, &$objectRef) {
3206 if ($op == 'create' && $objectName == 'Individual') {
3207 CRM_Core_DAO::executeQuery(
3208 "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
3209 [
3210 1 => [$objectId, 'Integer'],
3211 ]
3212 );
3213 }
3214
3215 if ($op == 'edit' && $objectName == 'Participant') {
3216 $params = [
3217 1 => [$objectId, 'Integer'],
3218 ];
3219 $query = "UPDATE civicrm_participant SET source = 'Post Hook Update' WHERE id = %1";
3220 CRM_Core_DAO::executeQuery($query, $params);
3221 }
3222 }
3223
3224 /**
3225 * Instantiate form object.
3226 *
3227 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
3228 *
3229 * @param string $class
3230 * Name of form class.
3231 *
3232 * @param array $formValues
3233 *
3234 * @param string $pageName
3235 *
3236 * @return \CRM_Core_Form
3237 * @throws \CRM_Core_Exception
3238 */
3239 public function getFormObject($class, $formValues = [], $pageName = '') {
3240 $form = new $class();
3241 $_SERVER['REQUEST_METHOD'] = 'GET';
3242 $form->controller = new CRM_Core_Controller();
3243 $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller));
3244 $_SESSION['_' . $form->controller->_name . '_container']['values'][$pageName] = $formValues;
3245 return $form;
3246 }
3247
3248 /**
3249 * Get possible thousand separators.
3250 *
3251 * @return array
3252 */
3253 public function getThousandSeparators() {
3254 return [['.'], [',']];
3255 }
3256
3257 /**
3258 * Get the boolean options as a provider.
3259 *
3260 * @return array
3261 */
3262 public function getBooleanDataProvider() {
3263 return [[TRUE], [FALSE]];
3264 }
3265
3266 /**
3267 * Set the separators for thousands and decimal points.
3268 *
3269 * Note that this only covers some common scenarios.
3270 *
3271 * It does not cater for a situation where the thousand separator is a [space]
3272 * Latter is the Norwegian localization. At least some tests need to
3273 * use setMonetaryDecimalPoint and setMonetaryThousandSeparator directly
3274 * to provide broader coverage.
3275 *
3276 * @param string $thousandSeparator
3277 */
3278 protected function setCurrencySeparators($thousandSeparator) {
3279 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3280 Civi::settings()->set('monetaryDecimalPoint', ($thousandSeparator === ',' ? '.' : ','));
3281 }
3282
3283 /**
3284 * Sets the thousand separator.
3285 *
3286 * If you use this function also set the decimal separator: setMonetaryDecimalSeparator
3287 *
3288 * @param $thousandSeparator
3289 */
3290 protected function setMonetaryThousandSeparator($thousandSeparator) {
3291 Civi::settings()->set('monetaryThousandSeparator', $thousandSeparator);
3292 }
3293
3294 /**
3295 * Sets the decimal separator.
3296 *
3297 * If you use this function also set the thousand separator setMonetaryDecimalPoint
3298 *
3299 * @param $decimalPoint
3300 */
3301 protected function setMonetaryDecimalPoint($decimalPoint) {
3302 Civi::settings()->set('monetaryDecimalPoint', $decimalPoint);
3303 }
3304
3305 /**
3306 * Sets the default currency.
3307 *
3308 * @param $currency
3309 */
3310 protected function setDefaultCurrency($currency) {
3311 Civi::settings()->set('defaultCurrency', $currency);
3312 }
3313
3314 /**
3315 * Format money as it would be input.
3316 *
3317 * @param string $amount
3318 *
3319 * @return string
3320 */
3321 protected function formatMoneyInput($amount) {
3322 return CRM_Utils_Money::format($amount, NULL, '%a');
3323 }
3324
3325 /**
3326 * Get the contribution object.
3327 *
3328 * @param int $contributionID
3329 *
3330 * @return \CRM_Contribute_BAO_Contribution
3331 */
3332 protected function getContributionObject($contributionID) {
3333 $contributionObj = new CRM_Contribute_BAO_Contribution();
3334 $contributionObj->id = $contributionID;
3335 $contributionObj->find(TRUE);
3336 return $contributionObj;
3337 }
3338
3339 /**
3340 * Enable multilingual.
3341 */
3342 public function enableMultilingual() {
3343 $this->callAPISuccess('Setting', 'create', [
3344 'lcMessages' => 'en_US',
3345 'languageLimit' => [
3346 'en_US' => 1,
3347 ],
3348 ]);
3349
3350 CRM_Core_I18n_Schema::makeMultilingual('en_US');
3351
3352 global $dbLocale;
3353 $dbLocale = '_en_US';
3354 }
3355
3356 /**
3357 * Setup or clean up SMS tests
3358 *
3359 * @param bool $teardown
3360 *
3361 * @throws \CiviCRM_API3_Exception
3362 */
3363 public function setupForSmsTests($teardown = FALSE) {
3364 require_once 'CiviTest/CiviTestSMSProvider.php';
3365
3366 // Option value params for CiviTestSMSProvider
3367 $groupID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'sms_provider_name', 'id', 'name');
3368 $params = [
3369 'option_group_id' => $groupID,
3370 'label' => 'unittestSMS',
3371 'value' => 'unit.test.sms',
3372 'name' => 'CiviTestSMSProvider',
3373 'is_default' => 1,
3374 'is_active' => 1,
3375 'version' => 3,
3376 ];
3377
3378 if ($teardown) {
3379 // Test completed, delete provider
3380 $providerOptionValueResult = civicrm_api3('option_value', 'get', $params);
3381 civicrm_api3('option_value', 'delete', ['id' => $providerOptionValueResult['id']]);
3382 return;
3383 }
3384
3385 // Create an SMS provider "CiviTestSMSProvider". Civi handles "CiviTestSMSProvider" as a special case and allows it to be instantiated
3386 // in CRM/Sms/Provider.php even though it is not an extension.
3387 return civicrm_api3('option_value', 'create', $params);
3388 }
3389
3390 /**
3391 * Start capturing browser output.
3392 *
3393 * The starts the process of browser output being captured, setting any variables needed for e-notice prevention.
3394 */
3395 protected function startCapturingOutput() {
3396 ob_start();
3397 $_SERVER['HTTP_USER_AGENT'] = 'unittest';
3398 }
3399
3400 /**
3401 * Stop capturing browser output and return as a csv.
3402 *
3403 * @param bool $isFirstRowHeaders
3404 *
3405 * @return \League\Csv\Reader
3406 *
3407 * @throws \League\Csv\Exception
3408 */
3409 protected function captureOutputToCSV($isFirstRowHeaders = TRUE) {
3410 $output = ob_get_flush();
3411 $stream = fopen('php://memory', 'r+');
3412 fwrite($stream, $output);
3413 rewind($stream);
3414 $this->assertEquals("\xEF\xBB\xBF", substr($output, 0, 3));
3415 $csv = Reader::createFromString($output);
3416 if ($isFirstRowHeaders) {
3417 $csv->setHeaderOffset(0);
3418 }
3419 ob_clean();
3420 return $csv;
3421 }
3422
3423 /**
3424 * Rename various labels to not match the names.
3425 *
3426 * Doing these mimics the fact the name != the label in international installs & triggers failures in
3427 * code that expects it to.
3428 */
3429 protected function renameLabels() {
3430 $replacements = ['Pending', 'Refunded'];
3431 foreach ($replacements as $name) {
3432 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = '{$name} Label**' where label = '{$name}' AND name = '{$name}'");
3433 }
3434 }
3435
3436 /**
3437 * Undo any label renaming.
3438 */
3439 protected function resetLabels() {
3440 CRM_Core_DAO::executeQuery("UPDATE civicrm_option_value SET label = REPLACE(name, ' Label**', '') WHERE label LIKE '% Label**'");
3441 }
3442
3443 /**
3444 * Get parameters to set up a multi-line participant order.
3445 *
3446 * @return array
3447 * @throws \CRM_Core_Exception
3448 */
3449 protected function getParticipantOrderParams(): array {
3450 $this->_contactId = $this->individualCreate();
3451 $event = $this->eventCreate();
3452 $this->_eventId = $event['id'];
3453 $eventParams = [
3454 'id' => $this->_eventId,
3455 'financial_type_id' => 4,
3456 'is_monetary' => 1,
3457 ];
3458 $this->callAPISuccess('event', 'create', $eventParams);
3459 $priceFields = $this->createPriceSet('event', $this->_eventId);
3460 $participantParams = [
3461 'financial_type_id' => 4,
3462 'event_id' => $this->_eventId,
3463 'role_id' => 1,
3464 'status_id' => 14,
3465 'fee_currency' => 'USD',
3466 'contact_id' => $this->_contactId,
3467 ];
3468 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
3469 $orderParams = [
3470 'total_amount' => 300,
3471 'currency' => 'USD',
3472 'contact_id' => $this->_contactId,
3473 'financial_type_id' => 4,
3474 'contribution_status_id' => 'Pending',
3475 'contribution_mode' => 'participant',
3476 'participant_id' => $participant['id'],
3477 ];
3478 foreach ($priceFields['values'] as $key => $priceField) {
3479 $orderParams['line_items'][] = [
3480 'line_item' => [
3481 [
3482 'price_field_id' => $priceField['price_field_id'],
3483 'price_field_value_id' => $priceField['id'],
3484 'label' => $priceField['label'],
3485 'field_title' => $priceField['label'],
3486 'qty' => 1,
3487 'unit_price' => $priceField['amount'],
3488 'line_total' => $priceField['amount'],
3489 'financial_type_id' => $priceField['financial_type_id'],
3490 'entity_table' => 'civicrm_participant',
3491 ],
3492 ],
3493 'params' => $participant,
3494 ];
3495 }
3496 return $orderParams;
3497 }
3498
3499 /**
3500 * @param $payments
3501 *
3502 * @throws \CRM_Core_Exception
3503 */
3504 protected function validatePayments($payments) {
3505 foreach ($payments as $payment) {
3506 $balance = CRM_Contribute_BAO_Contribution::getContributionBalance($payment['contribution_id']);
3507 if ($balance < 0 && $balance + $payment['total_amount'] === 0.0) {
3508 // This is an overpayment situation. there are no financial items to allocate the overpayment.
3509 // This is a pretty rough way at guessing which payment is the overpayment - but
3510 // for the test suite it should be enough.
3511 continue;
3512 }
3513 $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
3514 'financial_trxn_id' => $payment['id'],
3515 'entity_table' => 'civicrm_financial_item',
3516 'return' => ['amount'],
3517 ])['values'];
3518 $itemTotal = 0;
3519 foreach ($items as $item) {
3520 $itemTotal += $item['amount'];
3521 }
3522 $this->assertEquals($payment['total_amount'], $itemTotal);
3523 }
3524 }
3525
3526 /**
3527 * Validate all created payments.
3528 *
3529 * @throws \CRM_Core_Exception
3530 */
3531 protected function validateAllPayments() {
3532 $payments = $this->callAPISuccess('Payment', 'get', ['options' => ['limit' => 0]])['values'];
3533 $this->validatePayments($payments);
3534 }
3535
3536 /**
3537 * Validate all created contributions.
3538 *
3539 * @throws \CRM_Core_Exception
3540 */
3541 protected function validateAllContributions() {
3542 $contributions = $this->callAPISuccess('Contribution', 'get', [])['values'];
3543 foreach ($contributions as $contribution) {
3544 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
3545 $total = 0;
3546 foreach ($lineItems as $lineItem) {
3547 $total += $lineItem['line_total'];
3548 }
3549 $this->assertEquals($total, $contribution['total_amount']);
3550 }
3551 }
3552
3553 /**
3554 * @return array|int
3555 * @throws \CRM_Core_Exception
3556 */
3557 protected function createRuleGroup() {
3558 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
3559 'contact_type' => 'Individual',
3560 'threshold' => 8,
3561 'used' => 'General',
3562 'name' => 'TestRule',
3563 'title' => 'TestRule',
3564 'is_reserved' => 0,
3565 ]);
3566 return $ruleGroup;
3567 }
3568
3569 /**
3570 * Generic create test.
3571 *
3572 * @param int $version
3573 *
3574 * @throws \CRM_Core_Exception
3575 */
3576 protected function basicCreateTest(int $version) {
3577 $this->_apiversion = $version;
3578 $result = $this->callAPIAndDocument($this->_entity, 'create', $this->params, __FUNCTION__, __FILE__);
3579 $this->assertEquals(1, $result['count']);
3580 $this->assertNotNull($result['values'][$result['id']]['id']);
3581 $this->getAndCheck($this->params, $result['id'], $this->_entity);
3582 }
3583
3584 /**
3585 * Generic delete test.
3586 *
3587 * @param int $version
3588 *
3589 * @throws \CRM_Core_Exception
3590 */
3591 protected function basicDeleteTest($version) {
3592 $this->_apiversion = $version;
3593 $result = $this->callAPISuccess($this->_entity, 'create', $this->params);
3594 $deleteParams = ['id' => $result['id']];
3595 $this->callAPIAndDocument($this->_entity, 'delete', $deleteParams, __FUNCTION__, __FILE__);
3596 $checkDeleted = $this->callAPISuccess($this->_entity, 'get', []);
3597 $this->assertEquals(0, $checkDeleted['count']);
3598 }
3599
3600 /**
3601 * Create and return a case object for the given Client ID.
3602 *
3603 * @param int $clientId
3604 * @param int $loggedInUser
3605 * Omit or pass NULL to use the same as clientId
3606 * @param array $extra
3607 * Optional specific parameters such as start_date
3608 *
3609 * @return CRM_Case_BAO_Case
3610 */
3611 public function createCase($clientId, $loggedInUser = NULL, $extra = NULL) {
3612 if (empty($loggedInUser)) {
3613 // backwards compatibility - but it's more typical that the creator is a different person than the client
3614 $loggedInUser = $clientId;
3615 }
3616 $caseParams = [
3617 'activity_subject' => 'Case Subject',
3618 'client_id' => $clientId,
3619 'case_type_id' => 1,
3620 'status_id' => 1,
3621 'case_type' => 'housing_support',
3622 'subject' => 'Case Subject',
3623 'start_date' => ($extra['start_date'] ?? date("Y-m-d")),
3624 'start_date_time' => ($extra['start_date_time'] ?? date("YmdHis")),
3625 'medium_id' => 2,
3626 'activity_details' => '',
3627 ];
3628 $form = new CRM_Case_Form_Case();
3629 $caseObj = $form->testSubmit($caseParams, "OpenCase", $loggedInUser, "standalone");
3630 return $caseObj;
3631 }
3632
3633 }