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