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