Total Complexity | 64 |
Total Lines | 467 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like admin_acl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use admin_acl, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | class admin_acl |
||
24 | { |
||
25 | /** |
||
26 | * Methods callable via menuaction |
||
27 | * @var array |
||
28 | */ |
||
29 | public $public_functions = array( |
||
30 | 'index' => true, |
||
31 | ); |
||
32 | |||
33 | /** |
||
34 | * Reference to global Acl class (instanciated for current user) |
||
35 | * |
||
36 | * @var Acl |
||
37 | */ |
||
38 | protected $acl; |
||
39 | |||
40 | /** |
||
41 | * Constructor |
||
42 | */ |
||
43 | public function __construct() |
||
44 | { |
||
45 | $this->acl = $GLOBALS['egw']->acl; |
||
46 | } |
||
47 | |||
48 | /** |
||
49 | * Save run rights and refresh opener |
||
50 | * |
||
51 | * @param array $content |
||
52 | */ |
||
53 | protected function save_run_rights(array $content) |
||
54 | { |
||
55 | $old_apps = array_keys($this->acl->get_user_applications($content['acl_account'], false, false)); |
||
56 | // add new apps |
||
57 | $added_apps = array_diff($content['apps'], $old_apps); |
||
58 | foreach($added_apps as $app) |
||
59 | { |
||
60 | $this->acl->add_repository($app, 'run', $content['acl_account'], 1); |
||
61 | } |
||
62 | // remove no longer checked apps |
||
63 | $removed_apps = array_diff($old_apps, $content['apps']); |
||
64 | $deleted_ids = array(); |
||
65 | foreach($removed_apps as $app) |
||
66 | { |
||
67 | $this->acl->delete_repository($app, 'run', $content['acl_account']); |
||
68 | $deleted_ids[] = $app.':'.$content['acl_account'].':run'; |
||
69 | } |
||
70 | //error_log(__METHOD__."() apps=".array2string($content['apps']).", old_apps=".array2string($old_apps).", added_apps=".array2string($added_apps).", removed_apps=".array2string($removed_apps)); |
||
71 | |||
72 | if (!$added_apps && !$removed_apps) |
||
73 | { |
||
74 | // nothing changed --> nothing to do/notify |
||
75 | } |
||
76 | elseif (!$old_apps) |
||
77 | { |
||
78 | Framework::refresh_opener(lang('ACL added.'), 'admin', null, 'add'); |
||
79 | } |
||
80 | elseif (!$added_apps) |
||
81 | { |
||
82 | Framework::refresh_opener(lang('ACL deleted.'), 'admin', $deleted_ids, 'delete'); |
||
83 | } |
||
84 | else |
||
85 | { |
||
86 | Framework::refresh_opener(lang('ACL updated.'), 'admin', null, 'edit'); |
||
87 | } |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Save rights and refresh opener |
||
92 | * |
||
93 | * @param array $content |
||
94 | */ |
||
95 | protected function save_rights(array $content) |
||
96 | { |
||
97 | // assamble rights again |
||
98 | $rights = (int)$content['preserve_rights']; |
||
99 | foreach($content['acl'] as $right) |
||
100 | { |
||
101 | $rights |= $right; |
||
102 | } |
||
103 | $id = !empty($content['id']) ? $content['id'] : |
||
104 | $content['acl_appname'].':'.$content['acl_account'].':'.$content['acl_location']; |
||
105 | //error_log(__METHOD__."() id=$id, acl=".array2string($content['acl'])." --> rights=$rights"); |
||
106 | |||
107 | if ($this->acl->get_specific_rights_for_account($content['acl_account'], $content['acl_location'], $content['acl_appname']) == $rights) |
||
108 | { |
||
109 | // nothing changed --> nothing to do |
||
110 | } |
||
111 | elseif (!$rights) // all rights removed --> delete it |
||
112 | { |
||
113 | $this->acl->delete_repository($content['acl_appname'], $content['acl_location'], $content['acl_account']); |
||
114 | Framework::refresh_opener(lang('ACL deleted.'), 'admin', $id, 'delete'); |
||
115 | } |
||
116 | else |
||
117 | { |
||
118 | $this->acl->add_repository($content['acl_appname'], $content['acl_location'], $content['acl_account'], $rights); |
||
119 | if ($content['id']) |
||
120 | { |
||
121 | Framework::refresh_opener(lang('ACL updated.'), 'admin', $id, 'edit'); |
||
122 | } |
||
123 | else |
||
124 | { |
||
125 | Framework::refresh_opener(lang('ACL added.'), 'admin', $id, 'add'); |
||
126 | } |
||
127 | } |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Callback for nextmatch to fetch Acl |
||
132 | * |
||
133 | * @param array $query |
||
134 | * @param array &$rows=null |
||
135 | * @return int total number of rows available |
||
136 | */ |
||
137 | public static function get_rows(array $query, array &$rows=null) |
||
138 | { |
||
139 | $so_sql = new Api\Storage\Base('phpgwapi', Acl::TABLE, null, '', true); |
||
140 | |||
141 | // client queries destinct rows by their row-id |
||
142 | if (isset($query['col_filter']['id'])) |
||
143 | { |
||
144 | $query['col_filter'][] = $GLOBALS['egw']->db->concat('acl_appname',"':'",'acl_account',"':'",'acl_location'). |
||
145 | ' IN ('.implode(',', array_map(array($GLOBALS['egw']->db, 'quote'), (array)$query['col_filter']['id'])).')'; |
||
146 | unset($query['col_filter']['id']); |
||
147 | } |
||
148 | else |
||
149 | { |
||
150 | $memberships = $GLOBALS['egw']->accounts->memberships($query['account_id'], true); |
||
151 | $memberships[] = $query['account_id']; |
||
152 | |||
153 | Api\Cache::setSession(__CLASS__, 'state', array( |
||
154 | 'account_id' => $query['account_id'], |
||
155 | 'filter' => $query['filter'], |
||
156 | 'acl_appname' => $query['filter2'], |
||
157 | )); |
||
158 | |||
159 | if ($GLOBALS['egw_info']['user']['preferences']['admin']['acl_filter'] != $query['filter']) |
||
160 | { |
||
161 | $GLOBALS['egw']->preferences->add('admin', 'acl_filter', $query['filter']); |
||
162 | $GLOBALS['egw']->preferences->save_repository(false,'user',false); |
||
163 | } |
||
164 | switch($query['filter']) |
||
165 | { |
||
166 | case 'run': |
||
167 | $query['col_filter']['acl_location'] = 'run'; |
||
168 | $query['col_filter']['acl_account'] = $memberships; |
||
169 | break; |
||
170 | default: |
||
171 | case 'other': |
||
172 | //$query['col_filter'][] = "acl_location!='run'"; |
||
173 | // remove everything not an account-id in location, like category based acl |
||
174 | if (substr($GLOBALS['egw']->db->Type, 0, 5) == 'mysql') |
||
175 | { |
||
176 | $query['col_filter'][] = "acl_location REGEXP '^-?[0-9]+$'"; |
||
177 | } |
||
178 | else |
||
179 | { |
||
180 | $query['col_filter'][] = "acl_location SIMILAR TO '-?[0-9]+'"; |
||
181 | } |
||
182 | // get apps not using group-acl (eg. Addressbook) or using it only partialy (eg. InfoLog) |
||
183 | $not_enum_group_acls = Api\Hooks::process('not_enum_group_acls', array(), true); |
||
184 | //error_log(__METHOD__."(filter=$query[filter]) not_enum_group_acl=".array2string($not_enum_group_acls)); |
||
185 | if ($not_enum_group_acls) |
||
186 | { |
||
187 | $sql = '(CASE acl_appname'; |
||
188 | foreach($not_enum_group_acls as $app => $groups) |
||
189 | { |
||
190 | if ($groups === true) |
||
191 | { |
||
192 | $check = $query['account_id']; |
||
193 | } |
||
194 | else |
||
195 | { |
||
196 | $check = array_diff($memberships, $groups); |
||
197 | //error_log(__METHOD__."() app=$app, array_diff(memberships=".array2string($memberships).", groups=".array2string($groups).")=".array2string($check)); |
||
198 | if (!$check) continue; // would give sql error otherwise! |
||
199 | } |
||
200 | $sql .= ' WHEN '.$GLOBALS['egw']->db->quote($app).' THEN '.$GLOBALS['egw']->db->expression(Acl::TABLE, array( |
||
201 | 'acl_account' => $check, |
||
202 | )); |
||
203 | } |
||
204 | $sql .= ' ELSE '; |
||
205 | } |
||
206 | $sql .= $GLOBALS['egw']->db->expression(Acl::TABLE, array( |
||
207 | 'acl_account' => $memberships, |
||
208 | )); |
||
209 | if ($not_enum_group_acls) $sql .= ' END)'; |
||
210 | $query['col_filter'][] = $sql; |
||
211 | break; |
||
212 | |||
213 | case 'own': |
||
214 | $query['col_filter']['acl_location'] = $memberships; |
||
215 | break; |
||
216 | } |
||
217 | // do NOT list group-memberships and other non-ACL stuff here |
||
218 | $query['col_filter']['acl_appname'] = $query['filter2']; |
||
219 | if (empty($query['col_filter']['acl_appname']) && $query['filter'] != 'run') |
||
220 | { |
||
221 | //$query['col_filter'][] = "NOT acl_appname IN ('phpgw_group','sqlfs')"; |
||
222 | $query['col_filter']['acl_appname'] = array_keys($query['acl_rights']); |
||
223 | } |
||
224 | } |
||
225 | $readonlys = array(); |
||
226 | $total = $so_sql->get_rows($query, $rows, $readonlys); |
||
227 | |||
228 | foreach($rows as &$row) |
||
229 | { |
||
230 | // generate a row-id |
||
231 | $row['id'] = $row['acl_appname'].':'.$row['acl_account'].':'.$row['acl_location']; |
||
232 | |||
233 | if ($row['acl_location'] == 'run') |
||
234 | { |
||
235 | $row['acl1'] = lang('run'); |
||
236 | } |
||
237 | else |
||
238 | { |
||
239 | if ($app !== $row['acl_appname']) Api\Translation::add_app($row['app_name']); |
||
240 | foreach($query['acl_rights'][$row['acl_appname']] as $val => $label) |
||
241 | { |
||
242 | if ($row['acl_rights'] & $val) |
||
243 | { |
||
244 | $row['acl'.$val] = lang($label); |
||
245 | } |
||
246 | } |
||
247 | } |
||
248 | if (!self::check_access($row['acl_account'], $row['acl_location'], false)) // false: do NOT throw an exception! |
||
249 | { |
||
250 | $row['class'] = 'rowNoEdit'; |
||
251 | } |
||
252 | //error_log(__METHOD__."() $n: ".array2string($row)); |
||
253 | } |
||
254 | //error_log(__METHOD__."(".array2string($query).") returning ".$total); |
||
255 | |||
256 | // Get supporting or all apps for filter2 depending on filter |
||
257 | if($query['filter'] == 'run') |
||
258 | { |
||
259 | $rows['sel_options']['filter2'] = array( |
||
260 | '' => lang('All applications'), |
||
261 | )+Etemplate\Widget\Select::app_options('enabled'); |
||
262 | } |
||
263 | else |
||
264 | { |
||
265 | $rows['sel_options']['filter2'] = array( |
||
266 | array('value' => '', 'label' => lang('All applications')) |
||
267 | ); |
||
268 | $apps = Api\Hooks::process(array( |
||
269 | 'location' => 'acl_rights', |
||
270 | 'owner' => $query['account_id'], |
||
271 | ), array(), true); |
||
272 | foreach(array_keys($apps) as $appname) |
||
273 | { |
||
274 | $rows['sel_options']['filter2'][] = array( |
||
275 | 'value' => $appname, |
||
276 | 'label' => lang($appname) |
||
277 | ); |
||
278 | } |
||
279 | usort($rows['sel_options']['filter2'], function($a,$b) { |
||
280 | return strcasecmp($a['label'], $b['label']); |
||
281 | }); |
||
282 | } |
||
283 | |||
284 | return $total; |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * Check if current user has access to ACL setting of a given location |
||
289 | * |
||
290 | * @param int $account_id numeric account-id |
||
291 | * @param int|string $location =null numeric account-id or "run" |
||
292 | * @param boolean $throw =true if true, throw an exception if no access, instead of just returning false |
||
293 | * @return boolean true if access is granted, false if notification_bo |
||
294 | * @throws Api\Exception\NoPermission |
||
295 | */ |
||
296 | public static function check_access($account_id, $location=null, $throw=true) |
||
297 | { |
||
298 | static $admin_access=null; |
||
299 | static $own_access=null; |
||
300 | if (is_null($admin_access)) |
||
301 | { |
||
302 | $admin_access = isset($GLOBALS['egw_info']['user']['apps']['admin']) && |
||
303 | !$GLOBALS['egw']->acl->check('account_access', 64, 'admin'); // ! because this denies access! |
||
304 | $own_access = $admin_access || isset($GLOBALS['egw_info']['user']['apps']['preferences']); |
||
305 | } |
||
306 | if (!(int)$account_id || !((int)$account_id == (int)$GLOBALS['egw_info']['user']['account_id'] && $location !== 'run' ? |
||
307 | $own_access : $admin_access)) |
||
308 | { |
||
309 | if ($throw) throw new Api\Exception\NoPermission(lang('Permission denied!!!')); |
||
310 | return false; |
||
311 | } |
||
312 | return true; |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * Get the list of applications allowed for the given user |
||
317 | * |
||
318 | * The list of applications is added to the json response |
||
319 | * |
||
320 | * @param int $account_id |
||
321 | */ |
||
322 | public static function ajax_get_app_list($account_id) |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Change (add, modify, delete) an ACL entry |
||
334 | * |
||
335 | * Checks access and throws an exception, if a change is attempted without proper access |
||
336 | * |
||
337 | * @param string|array $ids "$app:$account:$location" string used as row-id in list |
||
338 | * @param int $rights null to delete, or new rights |
||
339 | * @param array $values Additional values from UI |
||
340 | * @param string $etemplate_exec_id to check against CSRF |
||
341 | * @throws Api\Exception\NoPermission |
||
342 | */ |
||
343 | public static function ajax_change_acl($ids, $rights, $values, $etemplate_exec_id) |
||
344 | { |
||
345 | Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args()); |
||
346 | |||
347 | try { |
||
348 | foreach((array)$ids as $id) |
||
349 | { |
||
350 | list($app, $account_id, $location) = explode(':', $id, 3); |
||
351 | |||
352 | self::check_access($account_id, $location); // throws exception, if no rights |
||
353 | |||
354 | $acl = $GLOBALS['egw']->acl; |
||
355 | |||
356 | if($location == 'run') |
||
357 | { |
||
358 | $right_list = array(1 => 'run'); |
||
359 | } |
||
360 | else |
||
361 | { |
||
362 | $right_list = Api\Hooks::single(array( |
||
363 | 'location' => 'acl_rights', |
||
364 | 'owner' => $location, |
||
365 | ), $app); |
||
366 | } |
||
367 | $current = (int)$acl->get_specific_rights_for_account($account_id,$location,$app); |
||
368 | foreach(array_keys((array)$right_list) as $right) |
||
369 | { |
||
370 | $have_it = !!($current & $right); |
||
371 | $set_it = !!($rights & $right); |
||
372 | if($have_it == $set_it) continue; |
||
373 | $data = array( |
||
374 | 'allow' => $set_it, |
||
375 | 'account' => $account_id, |
||
376 | 'app' => $app, |
||
377 | 'location' => $location, |
||
378 | 'rights' => (int)$right |
||
379 | // This is the documentation from policy app |
||
380 | )+(array)$values['admin_cmd']; |
||
381 | if($location == 'run') |
||
382 | { |
||
383 | $cmd = new admin_cmd_account_app($set_it,$account_id, $app, (array)$values['admin_cmd']); |
||
384 | } |
||
385 | else |
||
386 | { |
||
387 | $cmd = new admin_cmd_acl($data); |
||
388 | } |
||
389 | $cmd->run(); |
||
390 | } |
||
391 | } |
||
392 | if (!(int)$rights) |
||
393 | { |
||
394 | if (count($ids) > 1) |
||
395 | { |
||
396 | $msg = lang('%1 ACL entries deleted.', count($ids)); |
||
397 | } |
||
398 | else |
||
399 | { |
||
400 | $msg = lang('ACL entry deleted.'); |
||
401 | } |
||
402 | } |
||
403 | else |
||
404 | { |
||
405 | $msg = lang('ACL updated'); |
||
406 | } |
||
407 | Api\Json\Response::get()->data(array( |
||
408 | 'msg' => $msg, |
||
409 | 'ids' => $ids, |
||
410 | 'type' => !(int)$rights ? 'delete' : 'add', |
||
411 | )); |
||
412 | } |
||
413 | catch (Exception $e) { |
||
414 | Api\Json\Response::get()->call('egw.message', $e->getMessage(), 'error'); |
||
415 | } |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * New index page |
||
420 | * |
||
421 | * @param array $_content =null |
||
422 | */ |
||
423 | public function index(array $_content=null) |
||
424 | { |
||
425 | unset($_content); // not used, required by function signature |
||
426 | |||
427 | $tpl = new Etemplate('admin.acl'); |
||
428 | |||
429 | $content = array(); |
||
430 | $account_id = isset($_GET['account_id']) && (int)$_GET['account_id'] ? |
||
431 | (int)$_GET['account_id'] : $GLOBALS['egw_info']['user']['account_id']; |
||
432 | $content['nm'] = array( |
||
433 | 'get_rows' => 'admin_acl::get_rows', |
||
434 | 'no_cat' => true, |
||
435 | 'filter' => !empty($_GET['acl_filter']) ? $_GET['acl_filter'] : |
||
436 | ($GLOBALS['egw_info']['flags']['currentapp'] != 'admin' ? 'other' : |
||
437 | $GLOBALS['egw_info']['user']['preferences']['admin']['acl_filter']), |
||
438 | 'filter2' => !empty($_GET['acl_app']) ? $_GET['acl_app'] : '', |
||
439 | 'lettersearch' => false, |
||
440 | 'order' => 'acl_appname', |
||
441 | 'sort' => 'ASC', |
||
442 | 'row_id' => 'id', |
||
443 | 'account_id' => $account_id, |
||
444 | 'actions' => self::get_actions(), |
||
445 | 'acl_rights' => Api\Hooks::process(array( |
||
446 | 'location' => 'acl_rights', |
||
447 | 'owner' => $account_id, |
||
448 | ), array(), true), |
||
449 | ); |
||
450 | $user = Api\Accounts::username($content['nm']['account_id']); |
||
451 | $sel_options = array( |
||
452 | 'filter' => array( |
||
453 | 'other' => lang('Access to %1 data by others', $user), |
||
454 | 'own' => lang('%1 access to other data', $user), |
||
455 | 'run' => lang('%1 run rights for applications', $user), |
||
456 | ) |
||
457 | ); |
||
458 | |||
459 | // Set this so if loaded via preferences, js is still properly |
||
460 | // loaded into global app.admin |
||
461 | $GLOBALS['egw_info']['flags']['currentapp'] = 'admin'; |
||
462 | |||
463 | $tpl->exec('admin.admin_acl.index', $content, $sel_options, array(), array(), 2); |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * Get actions for ACL |
||
468 | * |
||
469 | * @return array |
||
470 | */ |
||
471 | static function get_actions() |
||
490 | ), |
||
491 | ); |
||
492 | } |
||
494 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.