CrmUI - Improve crmUiEditable to support placeholders, and use its own css
[civicrm-core.git] / ext / financialacls / financialacls.php
CommitLineData
e7339d59 1<?php
2
3require_once 'financialacls.civix.php';
4// phpcs:disable
87e130bb 5use Civi\Api4\EntityFinancialAccount;
56f5e9db 6use Civi\Api4\MembershipType;
e7339d59 7use CRM_Financialacls_ExtensionUtil as E;
8// phpcs:enable
9
10/**
11 * Implements hook_civicrm_config().
12 *
13 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config/
14 */
15function financialacls_civicrm_config(&$config) {
16 _financialacls_civix_civicrm_config($config);
17}
18
af4cccf7
TO
19/**
20 * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
21 */
22function financialacls_civicrm_container($container) {
23 $dispatcherDefn = $container->getDefinition('dispatcher');
24 $container->addResource(new \Symfony\Component\Config\Resource\FileResource(__FILE__));
25 $dispatcherDefn->addMethodCall('addListener', ['civi.api4.authorizeRecord::Contribution', '_financialacls_civi_api4_authorizeContribution']);
26}
27
e7339d59 28/**
29 * Implements hook_civicrm_install().
30 *
31 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install
32 */
33function financialacls_civicrm_install() {
34 _financialacls_civix_civicrm_install();
35}
36
37/**
38 * Implements hook_civicrm_postInstall().
39 *
40 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall
41 */
42function financialacls_civicrm_postInstall() {
43 _financialacls_civix_civicrm_postInstall();
44}
45
46/**
47 * Implements hook_civicrm_uninstall().
48 *
49 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall
50 */
51function financialacls_civicrm_uninstall() {
52 _financialacls_civix_civicrm_uninstall();
53}
54
55/**
56 * Implements hook_civicrm_enable().
57 *
58 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable
59 */
60function financialacls_civicrm_enable() {
61 _financialacls_civix_civicrm_enable();
62}
63
64/**
65 * Implements hook_civicrm_disable().
66 *
67 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable
68 */
69function financialacls_civicrm_disable() {
70 _financialacls_civix_civicrm_disable();
71}
72
73/**
74 * Implements hook_civicrm_upgrade().
75 *
76 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_upgrade
77 */
78function financialacls_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
79 return _financialacls_civix_civicrm_upgrade($op, $queue);
80}
81
e7339d59 82/**
83 * Implements hook_civicrm_entityTypes().
84 *
85 * Declare entity types provided by this module.
86 *
87 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes
88 */
89function financialacls_civicrm_entityTypes(&$entityTypes) {
90 _financialacls_civix_civicrm_entityTypes($entityTypes);
91}
92
b82fb202 93/**
94 * Intervene to prevent deletion, where permissions block it.
95 *
aeee327d 96 * @param string $op
b82fb202 97 * @param string $objectName
98 * @param int|null $id
99 * @param array $params
100 *
101 * @throws \API_Exception
102 * @throws \CRM_Core_Exception
103 */
104function financialacls_civicrm_pre($op, $objectName, $id, &$params) {
34509ae3 105 if (!financialacls_is_acl_limiting_enabled()) {
106 return;
107 }
aeee327d 108 if ($objectName === 'LineItem' && !empty($params['check_permissions'])) {
34509ae3 109 $operationMap = ['delete' => CRM_Core_Action::DELETE, 'edit' => CRM_Core_Action::UPDATE, 'create' => CRM_Core_Action::ADD];
110 CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($types, $operationMap[$op]);
111 if (empty($params['financial_type_id'])) {
112 $params['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_LineItem', $params['id'], 'financial_type_id');
113 }
c3f7ab62 114 if (!array_key_exists($params['financial_type_id'], $types)) {
34509ae3 115 throw new API_Exception('You do not have permission to ' . $op . ' this line item');
b82fb202 116 }
117 }
d646594f 118 if ($objectName === 'FinancialType' && !empty($params['id']) && !empty($params['name'])) {
119 $prevName = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $params['id']);
120 if ($prevName !== $params['name']) {
121 CRM_Core_Session::setStatus(ts("Changing the name of a Financial Type will result in losing the current permissions associated with that Financial Type.
122 Before making this change you should likely note the existing permissions at Administer > Users and Permissions > Permissions (Access Control),
123 then clicking the Access Control link for your Content Management System, then noting down the permissions for 'CiviCRM: {financial type name} view', etc.
124 Then after making the change of name, reset the permissions to the way they were."), ts('Warning'), 'warning');
125 }
126 }
b82fb202 127}
128
28188a1e 129/**
130 * Implements hook_civicrm_selectWhereClause().
131 *
132 * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_selectWhereClause
133 */
134function financialacls_civicrm_selectWhereClause($entity, &$clauses) {
34509ae3 135 if (!financialacls_is_acl_limiting_enabled()) {
136 return;
137 }
2dc76e8d
MW
138
139 switch ($entity) {
140 case 'LineItem':
141 case 'MembershipType':
f4a72e8d 142 case 'ContributionRecur':
00d50ac2 143 case 'Contribution':
87e130bb
EM
144 $clauses['financial_type_id'] = _financialacls_civicrm_get_type_clause();
145 break;
146
56f5e9db
EM
147 case 'Membership':
148 $clauses['membership_type_id'] = _financialacls_civicrm_get_membership_type_clause();
149 break;
150
87e130bb
EM
151 case 'FinancialType':
152 $clauses['id'] = _financialacls_civicrm_get_type_clause();
153 break;
154
155 case 'FinancialAccount':
156 $clauses['id'] = _financialacls_civicrm_get_accounts_clause();
2dc76e8d
MW
157 break;
158
28188a1e 159 }
160
161}
162
87e130bb
EM
163/**
164 * Get the clause to limit available types.
165 *
166 * @return string
167 */
168function _financialacls_civicrm_get_accounts_clause(): string {
169 if (!isset(Civi::$statics['financial_acls'][__FUNCTION__][CRM_Core_Session::getLoggedInContactID()])) {
170 try {
171 $clause = '= 0';
172 Civi::$statics['financial_acls'][__FUNCTION__][CRM_Core_Session::getLoggedInContactID()] = &$clause;
173 $accounts = (array) EntityFinancialAccount::get()
174 ->addWhere('account_relationship:name', '=', 'Income Account is')
175 ->addWhere('entity_table', '=', 'civicrm_financial_type')
176 ->addSelect('entity_id', 'financial_account_id')
177 ->addJoin('FinancialType AS financial_type', 'LEFT', [
178 'entity_id',
179 '=',
180 'financial_type.id',
181 ])
182 ->execute()->indexBy('financial_account_id');
183 if (!empty($accounts)) {
184 $clause = 'IN (' . implode(',', array_keys($accounts)) . ')';
185 }
186 }
187 catch (\API_Exception $e) {
188 // We've already set it to 0 so we can quietly handle this.
189 }
190 }
191 return Civi::$statics['financial_acls'][__FUNCTION__][CRM_Core_Session::getLoggedInContactID()];
192}
193
194/**
195 * Get the clause to limit available types.
196 *
197 * @return string
198 */
199function _financialacls_civicrm_get_type_clause(): string {
56f5e9db
EM
200 return 'IN (' . implode(',', _financialacls_civicrm_get_accessible_financial_types()) . ')';
201}
202
203/**
204 * Get an array of the ids of accessible financial types.
205 *
206 * If none then it will be [0]
207 *
208 * @return int[]
209 */
210function _financialacls_civicrm_get_accessible_financial_types(): array {
87e130bb
EM
211 $types = [];
212 CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($types);
56f5e9db
EM
213 if (empty($types)) {
214 $types = [0];
215 }
216 return array_keys($types);
217}
218
219/**
220 * Get the clause to limit available membership types.
221 *
222 * @return string
223 *
224 * @throws \API_Exception
225 */
226function _financialacls_civicrm_get_membership_type_clause(): string {
a532fa54
EM
227 if (!CRM_Core_Component::isEnabled('CiviMember')) {
228 return 1;
229 }
56f5e9db
EM
230 $financialTypes = _financialacls_civicrm_get_accessible_financial_types();
231 if ($financialTypes === [0]) {
232 return 0;
87e130bb 233 }
56f5e9db
EM
234 $membershipTypes = (array) MembershipType::get(FALSE)
235 ->addWhere('financial_type_id', 'IN', $financialTypes)->execute()->indexBy('id');
236 return empty($membershipTypes) ? '= 0' : ('IN (' . implode(',', array_keys($membershipTypes)) . ')');
87e130bb
EM
237}
238
07d89c14 239/**
34509ae3 240 * Remove unpermitted options.
07d89c14 241 *
242 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_buildAmount
243 *
244 * @param string $component
245 * @param \CRM_Core_Form $form
246 * @param array $feeBlock
247 */
248function financialacls_civicrm_buildAmount($component, $form, &$feeBlock) {
34509ae3 249 if (!financialacls_is_acl_limiting_enabled()) {
250 return;
251 }
252
253 foreach ($feeBlock as $key => $value) {
254 foreach ($value['options'] as $k => $options) {
255 if (!CRM_Core_Permission::check('add contributions of type ' . CRM_Contribute_PseudoConstant::financialType($options['financial_type_id']))) {
256 unset($feeBlock[$key]['options'][$k]);
07d89c14 257 }
258 }
34509ae3 259 if (empty($feeBlock[$key]['options'])) {
260 unset($feeBlock[$key]);
261 }
07d89c14 262 }
263}
264
e9eed3db 265/**
266 * Remove unpermitted membership types from selection availability..
267 *
268 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_membershipTypeValues
269 *
270 * @param \CRM_Core_Form $form
271 * @param array $membershipTypeValues
272 */
273function financialacls_civicrm_membershipTypeValues($form, &$membershipTypeValues) {
34509ae3 274 if (!financialacls_is_acl_limiting_enabled()) {
275 return;
276 }
e9eed3db 277 $financialTypes = NULL;
278 $financialTypes = CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, CRM_Core_Action::ADD);
279 foreach ($membershipTypeValues as $id => $type) {
280 if (!isset($financialTypes[$type['financial_type_id']])) {
281 unset($membershipTypeValues[$id]);
282 }
283 }
284}
285
4e0bf3e8 286/**
287 * Add permissions.
288 *
289 * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_permission/
290 *
291 * @param array $permissions
292 */
293function financialacls_civicrm_permission(&$permissions) {
294 if (!financialacls_is_acl_limiting_enabled()) {
295 return;
296 }
297 $actions = [
298 'add' => ts('add'),
299 'view' => ts('view'),
300 'edit' => ts('edit'),
301 'delete' => ts('delete'),
302 ];
303 $financialTypes = \CRM_Contribute_BAO_Contribution::buildOptions('financial_type_id', 'validate');
304 foreach ($financialTypes as $id => $type) {
305 foreach ($actions as $action => $action_ts) {
306 $permissions[$action . ' contributions of type ' . $type] = [
307 ts("CiviCRM: %1 contributions of type %2", [1 => $action_ts, 2 => $type]),
308 ts('%1 contributions of type %2', [1 => $action_ts, 2 => $type]),
309 ];
310 }
311 }
312 $permissions['administer CiviCRM Financial Types'] = [
313 ts('CiviCRM: administer CiviCRM Financial Types'),
314 ts('Administer access to Financial Types'),
315 ];
316}
317
9cef6066 318/**
af4cccf7 319 * Listener for 'civi.api4.authorizeRecord::Contribution'
9cef6066 320 *
af4cccf7 321 * @param \Civi\Api4\Event\AuthorizeRecordEvent $e
9cef6066 322 * @throws \CRM_Core_Exception
323 */
af4cccf7 324function _financialacls_civi_api4_authorizeContribution(\Civi\Api4\Event\AuthorizeRecordEvent $e) {
9cef6066 325 if (!financialacls_is_acl_limiting_enabled()) {
326 return;
327 }
0552d667
EM
328 if ($e->getEntityName() === 'Contribution') {
329 $contributionID = $e->getRecord()['id'] ?? NULL;
330 $financialTypeID = $e->getRecord()['financial_type_id'] ?? CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionID, 'financial_type_id');
331 if (!CRM_Core_Permission::check(_financialacls_getRequiredPermission($financialTypeID, $e->getActionName()), $e->getUserID())) {
af4cccf7 332 $e->setAuthorized(FALSE);
9cef6066 333 }
0552d667
EM
334 if ($e->getActionName() === 'delete') {
335 // First check contribution financial type
336 // Now check permissioned line items & permissioned contribution
337 if (!CRM_Financial_BAO_FinancialType::checkPermissionedLineItems($contributionID, 'delete', FALSE, $e->getUserID())
338 ) {
339 $e->setAuthorized(FALSE);
340 }
341 }
9cef6066 342 }
343}
344
0552d667
EM
345/**
346 * Get the permission required to perform this action on this financial type.
347 *
348 * @param int $financialTypeID
349 * @param string $action
350 *
351 * @return string
352 */
353function _financialacls_getRequiredPermission(int $financialTypeID, string $action): string {
354 $financialType = CRM_Core_PseudoConstant::getName('CRM_Contribute_DAO_Contribution', 'financial_type_id', $financialTypeID);
355 $actionMap = [
356 'create' => 'add',
357 'update' => 'edit',
358 'delete' => 'delete',
359 ];
360 return $actionMap[$action] . ' contributions of type ' . $financialType;
361}
362
51d1f926 363/**
364 * Remove unpermitted financial types from field Options in search context.
365 *
366 * Search context is described as
367 * 'search' => "search: searchable options are returned; labels are translated.",
368 * So this is appropriate to removing the options from search screens.
369 *
370 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_fieldOptions
371 *
372 * @param string $entity
373 * @param string $field
374 * @param array $options
375 * @param array $params
376 */
377function financialacls_civicrm_fieldOptions($entity, $field, &$options, $params) {
34509ae3 378 if (!financialacls_is_acl_limiting_enabled()) {
379 return;
380 }
f4a72e8d 381 if (in_array($entity, ['Contribution', 'ContributionRecur'], TRUE) && $field === 'financial_type_id' && $params['context'] === 'search') {
51d1f926 382 $action = CRM_Core_Action::VIEW;
383 // At this stage we are only considering the view action. Code from
384 // CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes().
385 $actions = [
386 CRM_Core_Action::VIEW => 'view',
387 CRM_Core_Action::UPDATE => 'edit',
388 CRM_Core_Action::ADD => 'add',
389 CRM_Core_Action::DELETE => 'delete',
390 ];
391 $cacheKey = 'available_types_' . $action;
392 if (!isset(\Civi::$statics['CRM_Financial_BAO_FinancialType'][$cacheKey])) {
393 foreach ($options as $finTypeId => $type) {
394 if (!CRM_Core_Permission::check($actions[$action] . ' contributions of type ' . $type)) {
395 unset($options[$finTypeId]);
396 }
397 }
398 \Civi::$statics['CRM_Financial_BAO_FinancialType'][$cacheKey] = $options;
399 }
400 $options = \Civi::$statics['CRM_Financial_BAO_FinancialType'][$cacheKey];
401 }
402}
403
34509ae3 404/**
405 * Is financial acl limiting enabled.
406 *
407 * Once this extension is detangled enough to be optional this will go
408 * and the status of the extension rather than the setting will dictate.
409 *
410 * @return bool
411 */
d646594f 412function financialacls_is_acl_limiting_enabled(): bool {
34509ae3 413 return (bool) Civi::settings()->get('acl_financial_type');
414}
415
a572646e
EM
416/**
417 * Clear the statics cache when the setting is enabled or disabled.
418 *
419 * Note the setting will eventually disappear in favour of whether
420 * the extension is enabled or disabled.
421 */
422function financialacls_toggle() {
423 unset(\Civi::$statics['CRM_Financial_BAO_FinancialType']);
424}
425
e7339d59 426// --- Functions below this ship commented out. Uncomment as required. ---
427
428/**
429 * Implements hook_civicrm_preProcess().
430 *
431 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_preProcess
432 */
433//function financialacls_civicrm_preProcess($formName, &$form) {
434//
435//}
436
d017acf3
EM
437/**
438 * Require financial acl permissions for financial screens.
439 *
440 * @param array $menu
441 */
442function financialacls_civicrm_alterMenu(array &$menu): void {
e58bbf77
EM
443 if (!financialacls_is_acl_limiting_enabled()) {
444 return;
445 }
d017acf3
EM
446 $menu['civicrm/admin/financial/financialType']['access_arguments'] = [['administer CiviCRM Financial Types']];
447}
448
e7339d59 449/**
450 * Implements hook_civicrm_navigationMenu().
451 *
452 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_navigationMenu
453 */
454//function financialacls_civicrm_navigationMenu(&$menu) {
455// _financialacls_civix_insert_navigation_menu($menu, 'Mailings', array(
456// 'label' => E::ts('New subliminal message'),
457// 'name' => 'mailing_subliminal_message',
458// 'url' => 'civicrm/mailing/subliminal',
459// 'permission' => 'access CiviMail',
460// 'operator' => 'OR',
461// 'separator' => 0,
462// ));
463// _financialacls_civix_navigationMenu($menu);
464//}