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