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