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