PageRenderTime 71ms CodeModel.GetById 3ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 1ms

/app/protected/modules/zurmo/utils/ReadPermissionsOptimizationUtil.php

https://bitbucket.org/jim_fulford/fulford_zurmo
PHP | 994 lines | 837 code | 72 blank | 85 comment | 75 complexity | 7a51257385f5fade876b1c17f717f9ae MD5 | raw file
  1<?php
  2    /*********************************************************************************
  3     * Zurmo is a customer relationship management program developed by
  4     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
  5     *
  6     * Zurmo is free software; you can redistribute it and/or modify it under
  7     * the terms of the GNU General Public License version 3 as published by the
  8     * Free Software Foundation with the addition of the following permission added
  9     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 10     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
 11     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 12     *
 13     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
 14     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 15     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 16     * details.
 17     *
 18     * You should have received a copy of the GNU General Public License along with
 19     * this program; if not, see http://www.gnu.org/licenses or write to the Free
 20     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 21     * 02110-1301 USA.
 22     *
 23     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
 24     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
 25     ********************************************************************************/
 26
 27    abstract class ReadPermissionsOptimizationUtil
 28    {
 29        // $forcePhp is for use in tests only. So that the php version and
 30        // the optimized version can be run in succession to compare them.
 31        public static function rebuild($forcePhp = false)
 32        {
 33            assert('is_bool($forcePhp)');
 34            foreach (self::getMungableModelClassNames() as $modelClassName)
 35            {
 36                if (!SECURITY_OPTIMIZED || $forcePhp)
 37                {
 38                    // The slow way will remain here as documentation
 39                    // for what the optimized way is doing.
 40                    $mungeTableName  = self::getMungeTableName($modelClassName);
 41                    self::recreateTable($mungeTableName);
 42                    //Specifically call RedBeanModel to avoid the use of the security in OwnedSecurableItem since for
 43                    //rebuild it needs to look at all models regardless of permissions of the current user.
 44                    $modelCount = RedBeanModel::getCount(null, null, $modelClassName);
 45                    $subset = intval($modelCount / 20);
 46                    if ($subset < 100)
 47                    {
 48                        $subset = 100;
 49                    }
 50                    elseif ($subset > 1000)
 51                    {
 52                        $subset = 1000;
 53                    }
 54                    for ($i = 0; $i < $modelCount; $i += $subset)
 55                    {
 56                        //Specifically call RedBeanModel to avoid the use of the security in OwnedSecurableItem since for
 57                        //rebuild it needs to look at all models regardless of permissions of the current user.
 58                        $models = RedBeanModel::getSubset(null, $i, $subset, null, null, $modelClassName);
 59                        foreach ($models as $model)
 60                        {
 61                            assert('$model instanceof SecurableItem');
 62                            $securableItemId = $model->getClassId('SecurableItem');
 63                            $users = User::getAll();
 64                            foreach ($users as $user)
 65                            {
 66                                list($allowPermissions, $denyPermissions) = $model->getExplicitActualPermissions($user);
 67                                $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
 68                                if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
 69                                {
 70                                    self::incrementCount($mungeTableName, $securableItemId, $user);
 71                                }
 72                            }
 73                            $groups = Group::getAll();
 74                            foreach ($groups as $group)
 75                            {
 76                                list($allowPermissions, $denyPermissions) = $model->getExplicitActualPermissions($group);
 77                                $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
 78                                if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
 79                                {
 80                                    self::incrementCount($mungeTableName, $securableItemId, $group);
 81                                    foreach ($group->users as $user)
 82                                    {
 83                                        if ($user->role->id > 0)
 84                                        {
 85                                            self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
 86                                        }
 87                                    }
 88                                    foreach ($group->groups as $subGroup)
 89                                    {
 90                                        self::processNestedGroupWhereParentHasReadPermissionOnSecurableItem(
 91                                              $mungeTableName, $securableItemId, $subGroup);
 92                                    }
 93                                }
 94                            }
 95                            $roles = Role::getAll();
 96                            foreach ($roles as $role)
 97                            {
 98                                $count = self::getRoleMungeCount($model, $role);
 99                                assert('$count >= 0');
100                                if ($count > 0)
101                                {
102                                    self::setCount($mungeTableName, $securableItemId, $role, $count);
103                                }
104                            }
105                        }
106                    }
107                }
108                else
109                {
110                    $modelTableName = RedBeanModel::getTableName($modelClassName);
111                    $mungeTableName = self::getMungeTableName($modelClassName);
112                    ZurmoDatabaseCompatibilityUtil::
113                        callProcedureWithoutOuts("rebuild('$modelTableName', '$mungeTableName')");
114                }
115            }
116        }
117
118        protected static function processNestedGroupWhereParentHasReadPermissionOnSecurableItem(
119                                  $mungeTableName, $securableItemId, Group $group)
120        {
121            assert('is_string($mungeTableName) && $mungeTableName != ""');
122            assert('is_int($securableItemId) && $securableItemId > 0');
123            self::incrementCount($mungeTableName, $securableItemId, $group);
124            foreach ($group->users as $user)
125            {
126                if ($user->role->id > 0)
127                {
128                    self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
129                }
130            }
131            foreach ($group->groups as $subGroup)
132            {
133                self::processNestedGroupWhereParentHasReadPermissionOnSecurableItem(
134                      $mungeTableName, $securableItemId, $subGroup);
135            }
136        }
137
138        protected static function getRoleMungeCount(SecurableItem $securableItem, Role $role)
139        {
140            $count = 0;
141            foreach ($role->roles as $subRole)
142            {
143                $count += self::getSubRoleMungeCount($securableItem, $subRole);
144            }
145            return $count;
146        }
147
148        protected static function getSubRoleMungeCount(SecurableItem $securableItem, Role $role)
149        {
150            $count = self::getImmediateRoleMungeCount($securableItem, $role);
151            foreach ($role->roles as $subRole)
152            {
153                $count += self::getSubRoleMungeCount($securableItem, $subRole);
154            }
155            return $count;
156        }
157
158        protected static function getImmediateRoleMungeCount(SecurableItem $securableItem, Role $role)
159        {
160            $count = 0;
161            foreach ($role->users as $user)
162            {
163                if ($securableItem->owner->isSame($user))
164                {
165                    $count++;
166                }
167                list($allowPermissions, $denyPermissions) = $securableItem->getExplicitActualPermissions($user);
168                $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
169                if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
170                {
171                    $count++;
172                }
173                foreach ($user->groups as $group)
174                {
175                    $count += self::getGroupMungeCount($securableItem, $group);
176                }
177            }
178            return $count;
179        }
180
181        protected static function getGroupMungeCount(SecurableItem $securableItem, Group $group)
182        {
183            $count = 0;
184            list($allowPermissions, $denyPermissions) = $securableItem->getExplicitActualPermissions($group);
185            $effectiveExplicitPermissions = $allowPermissions & ~$denyPermissions;
186            if (($effectiveExplicitPermissions & Permission::READ) == Permission::READ)
187            {
188                $count++;
189            }
190            if ($group->group->id > 0 && !(!RedBeanDatabase::isFrozen() && $group->group->isSame($group))) // Prevent cycles in database auto build.
191            {
192                $count += self::getGroupMungeCount($securableItem, $group->group);
193            }
194            return $count;
195        }
196
197        // SecurableItem create, assigned, or deleted.
198
199        // Past tense implies the method must be called immediately after the associated operation.
200        public static function ownedSecurableItemCreated(OwnedSecurableItem $ownedSecurableItem)
201        {
202            self::ownedSecurableItemOwnerChanged($ownedSecurableItem);
203        }
204
205        public static function ownedSecurableItemOwnerChanged(OwnedSecurableItem $ownedSecurableItem, User $oldUser = null)
206        {
207            $modelClassName = get_class($ownedSecurableItem);
208            assert('$modelClassName != "OwnedSecurableItem"');
209            $mungeTableName = self::getMungeTableName($modelClassName);
210            if ($oldUser !== null && $oldUser->role->id > 0)
211            {
212                self::decrementParentRolesCounts($mungeTableName, $ownedSecurableItem->getClassId('SecurableItem'), $oldUser->role);
213                self::garbageCollect($mungeTableName);
214            }
215            if ($ownedSecurableItem->owner->role->id > 0)
216            {
217                self::incrementParentRolesCounts($mungeTableName, $ownedSecurableItem->getClassId('SecurableItem'), $ownedSecurableItem->owner->role);
218            }
219        }
220
221        // Being implies the the method must be called just before the associated operation.
222        // The object is needed before the delete occurs and the delete cannot fail.
223        public static function securableItemBeingDeleted(SecurableItem $securableItem) // Call being methods before the destructive operation.
224        {
225            $modelClassName = get_class($securableItem);
226            assert('$modelClassName != "OwnedSecurableItem"');
227            $mungeTableName = self::getMungeTableName($modelClassName);
228            $securableItemId = $securableItem->id;
229            R::exec("delete from $mungeTableName
230                     where       securableitem_id = $securableItemId");
231        }
232
233        // Permissions added or removed.
234
235        public static function securableItemGivenPermissionsForUser(SecurableItem $securableItem, User $user)
236        {
237            $modelClassName = get_class($securableItem);
238            assert('$modelClassName != "OwnedSecurableItem"');
239            $mungeTableName = self::getMungeTableName($modelClassName);
240            $securableItemId = $securableItem->getClassId('SecurableItem');
241            self::incrementCount($mungeTableName, $securableItemId, $user);
242            if ($user->role->id > 0)
243            {
244                self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
245            }
246        }
247
248        public static function securableItemGivenPermissionsForGroup(SecurableItem $securableItem, Group $group)
249        {
250            $modelClassName = get_class($securableItem);
251            assert('$modelClassName != "OwnedSecurableItem"');
252            $mungeTableName = self::getMungeTableName($modelClassName);
253            $securableItemId = $securableItem->getClassId('SecurableItem');
254            self::incrementCount($mungeTableName, $securableItemId, $group);
255            foreach ($group->users as $user)
256            {
257                if ($user->role->id > 0)
258                {
259                    self::incrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
260                }
261            }
262            foreach ($group->groups as $subGroup)
263            {
264                self::securableItemGivenPermissionsForGroup($securableItem, $subGroup);
265            }
266        }
267
268        public static function securableItemLostPermissionsForUser(SecurableItem $securableItem, User $user)
269        {
270            $modelClassName = get_class($securableItem);
271            assert('$modelClassName != "OwnedSecurableItem"');
272            $mungeTableName = self::getMungeTableName($modelClassName);
273            $securableItemId = $securableItem->getClassId('SecurableItem');
274            self::decrementCount($mungeTableName, $securableItemId, $user);
275            if ($user->role->id > 0)
276            {
277                self::decrementParentRolesCounts($mungeTableName, $securableItemId, $user->role);
278            }
279            self::garbageCollect($mungeTableName);
280        }
281
282        public static function securableItemLostPermissionsForGroup(SecurableItem $securableItem, Group $group)
283        {
284            $modelClassName = get_class($securableItem);
285            assert('$modelClassName != "OwnedSecurableItem"');
286            $mungeTableName = self::getMungeTableName($modelClassName);
287            $securableItemId = $securableItem->getClassId('SecurableItem');
288            self::decrementCount($mungeTableName, $securableItemId, $group);
289            foreach ($group->users as $user)
290            {
291                self::securableItemLostPermissionsForUser($securableItem, $user);
292            }
293            foreach ($group->groups as $subGroup)
294            {
295                self::securableItemLostPermissionsForGroup($securableItem, $subGroup);
296            }
297            self::garbageCollect($mungeTableName);
298        }
299
300        // User operations.
301
302        public static function userBeingDeleted($user) // Call being methods before the destructive operation.
303        {
304            foreach (self::getMungableModelClassNames() as $modelClassName)
305            {
306                $mungeTableName = self::getMungeTableName($modelClassName);
307                if ($user->role->id > 0)
308                {
309                    self::decrementParentRolesCountsForAllSecurableItems($mungeTableName, $user->role);
310                    self::garbageCollect($mungeTableName);
311                }
312                $userId = $user->id;
313                R::exec("delete from $mungeTableName
314                         where       munge_id = 'U$userId'");
315            }
316        }
317
318        // Group operations.
319
320        public static function userAddedToGroup(Group $group, User $user)
321        {
322            foreach (self::getMungableModelClassNames() as $modelClassName)
323            {
324                $mungeTableName = self::getMungeTableName($modelClassName);
325                $groupId = $group->id;
326                $sql = "select securableitem_id
327                        from   $mungeTableName
328                        where  munge_id = concat('G', $groupId)";
329                $securableItemIds = R::getCol($sql);
330                self::bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
331                /*
332                 * This extra step is not needed. See slide 21.  This is similar to userBeingRemovedFromRole in that
333                 * the above query already is trapping the information needed.
334                    Follow the same process for any upstream groups that the group is a member of.
335                */
336            }
337        }
338
339        public static function userRemovedFromGroup(Group $group, User $user)
340        {
341            foreach (self::getMungableModelClassNames() as $modelClassName)
342            {
343                $mungeTableName = self::getMungeTableName($modelClassName);
344                $groupId = $group->id;
345                $sql = "select securableitem_id
346                        from   $mungeTableName
347                        where  munge_id = concat('G', $groupId)";
348                $securableItemIds = R::getCol($sql);
349                self::bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
350                /*
351                 * This extra step is not needed. See slide 22. This is similar to userBeingRemovedFromRole or
352                 * userAddedToGroup in that the above query is already trapping the information needed.
353                    Follow the same process for any upstream groups that the group is a member of.
354                */
355                self::garbageCollect($mungeTableName);
356            }
357        }
358
359        public static function groupAddedToGroup(Group $group)
360        {
361            self::groupAddedOrRemovedFromGroup(true, $group);
362        }
363
364        public static function groupBeingRemovedFromGroup(Group $group) // Call being methods before the destructive operation.
365        {
366            self::groupAddedOrRemovedFromGroup(false, $group);
367        }
368
369        public static function groupBeingDeleted($group) // Call being methods before the destructive operation.
370        {
371            if ($group->group->id > 0 && !(!RedBeanDatabase::isFrozen() && $group->group->isSame($group))) // Prevent cycles in database auto build.
372            {
373                self::groupBeingRemovedFromGroup($group);
374            }
375            foreach ($group->groups as $childGroup)
376            {
377                if (!RedBeanDatabase::isFrozen() && $group->isSame($childGroup)) // Prevent cycles in database auto build.
378                {
379                    continue;
380                }
381                self::groupBeingRemovedFromGroup($childGroup);
382            }
383            foreach ($group->users as $user)
384            {
385                self::userRemovedFromGroup($group, $user);
386            }
387            foreach (self::getMungableModelClassNames() as $modelClassName)
388            {
389                $groupId = $group->id;
390                $mungeTableName = self::getMungeTableName($modelClassName);
391                R::exec("delete from $mungeTableName
392                     where       munge_id = 'G$groupId'");
393            }
394        }
395
396        protected static function groupAddedOrRemovedFromGroup($isAdd, Group $group)
397        {
398            assert('is_bool($isAdd)');
399            if (!RedBeanDatabase::isFrozen() && $group->group->isSame($group)) // Prevent cycles in database auto build.
400            {
401                return;
402            }
403
404            $countMethod1 = $isAdd ? 'bulkIncrementCount'             : 'bulkDecrementCount';
405            $countMethod2 = $isAdd ? 'bulkIncrementParentRolesCounts' : 'bulkDecrementParentRolesCounts';
406
407            $parentGroups = self::getAllParentGroups($group);
408            $users  = self::getAllUsersInGroupAndChildGroupsRecursively($group);
409
410            // Handle groups that $parentGroup is in. In/decrement for the containing groups' containing
411            // groups the models they have explicit permissions on.
412            // And handle user's role's parents. In/decrement for all users that have permission because
413            // they are now in the containing group.
414            if (count($parentGroups) > 0)
415            {
416                $parentGroupPermitableIds = array();
417                foreach ($parentGroups as $parentGroup)
418                {
419                    $parentGroupPermitableIds[] = $parentGroup->getClassId('Permitable');
420                }
421                $sql = 'select securableitem_id
422                        from   permission
423                        where  permitable_id in (' . join(', ', $parentGroupPermitableIds) . ')';
424                $securableItemIds = R::getCol($sql);
425                foreach (self::getMungableModelClassNames() as $modelClassName)
426                {
427                    $mungeTableName = self::getMungeTableName($modelClassName);
428                    self::$countMethod1($mungeTableName, $securableItemIds, $group);
429                    foreach ($users as $user)
430                    {
431                        if ($user->role->id > 0)
432                        {
433                            self::$countMethod2($mungeTableName, $securableItemIds, $user->role);
434                        }
435                    }
436                }
437            }
438            if (!$isAdd)
439            {
440                foreach (self::getMungableModelClassNames() as $modelClassName)
441                {
442                    $mungeTableName = self::getMungeTableName($modelClassName);
443                    self::garbageCollect($mungeTableName);
444                }
445            }
446        }
447
448        protected static function getAllUsersInGroupAndChildGroupsRecursively(Group $group)
449        {
450            $users = array();
451            foreach ($group->users as $user)
452            {
453                $users[] = $user;
454            }
455            foreach ($group->groups as $childGroup)
456            {
457                if (!RedBeanDatabase::isFrozen() && $group->isSame($childGroup)) // Prevent cycles in database auto build.
458                {
459                    continue;
460                }
461                $users = array_merge($users, self::getAllUsersInGroupAndChildGroupsRecursively($childGroup));
462            }
463            return $users;
464        }
465
466        protected static function getAllParentGroups(Group $group)
467        {
468            $parentGroups = array();
469            $parentGroup = $group->group;
470            while ($parentGroup->id > 0 && !(!RedBeanDatabase::isFrozen() && $parentGroup->isSame($parentGroup->group))) // Prevent cycles in database auto build.
471            {
472                $parentGroups[] = $parentGroup;
473                $parentGroup = $parentGroup->group;
474            }
475            return $parentGroups;
476        }
477
478        // Role operations.
479
480        public static function roleParentSet(Role $role)
481        {
482            assert('$role->role->id > 0');
483            self::roleParentSetOrRemoved(true, $role);
484        }
485
486        public static function roleParentBeingRemoved(Role $role) // Call being methods before the destructive operation.
487        {
488            assert('$role->role->id > 0');
489            self::roleParentSetOrRemoved(false, $role);
490        }
491
492        public static function roleBeingDeleted(Role $role) // Call being methods before the destructive operation.
493        {
494            foreach (self::getMungableModelClassNames() as $modelClassName)
495            {
496                if ($role->role->id > 0)
497                {
498                    self::roleParentBeingRemoved($role);
499                }
500                foreach ($role->roles as $childRole)
501                {
502                    if ($childRole->role->id > 0)
503                    {
504                        self::roleParentBeingRemoved($childRole);
505                    }
506                }
507                $mungeTableName = self::getMungeTableName($modelClassName);
508                $roleId = $role->id;
509                $sql = "delete from $mungeTableName
510                        where       munge_id = 'R$roleId'";
511                R::exec($sql);
512            }
513        }
514
515        protected static function roleParentSetOrRemoved($isSet, Role $role)
516        {
517            assert('is_bool($isSet)');
518            if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
519            {
520                return;
521            }
522
523            $countMethod = $isSet ? 'bulkIncrementParentRolesCounts' : 'bulkDecrementParentRolesCounts';
524
525            foreach (self::getMungableModelClassNames() as $modelClassName)
526            {
527                $mungeTableName = self::getMungeTableName($modelClassName);
528
529                $usersInRolesChildren = self::getAllUsersInRolesChildRolesRecursively($role);
530
531                // Handle users in $role. In/decrement for the parent's parent
532                // roles the models they either own or have explicit permissions on.
533
534                if (count($role->users) > 0)
535                {
536                    $userIds      = array();
537                    $permitableIds = array();
538                    foreach ($role->users as $user)
539                    {
540                        $userIds[]       = $user->id;
541                        $permitableIds[] = $user->getClassId('Permitable');
542                    }
543                    $sql = 'select securableitem_id
544                            from   ownedsecurableitem
545                            where  owner__user_id in (' . join(', ', $userIds) . ')
546                            union all
547                            select securableitem_id
548                            from   permission
549                            where  permitable_id in (' . join(', ', $permitableIds) . ')';
550                    $securableItemIds = R::getCol($sql);
551                    self::$countMethod($mungeTableName, $securableItemIds, $role->role);
552                }
553
554                // Handle users in the child roles of $role. Increment for the parent's parent
555                // roles the models they either own or have explicit permissions on.
556
557                if (count($usersInRolesChildren))
558                {
559                    $userIds       = array();
560                    $permitableIds = array();
561                    foreach ($usersInRolesChildren as $user)
562                    {
563                        $userIds[]       = $user->id;
564                        $permitableIds[] = $user->getClassId('Permitable');
565                    }
566                    $sql = 'select securableitem_id
567                            from   ownedsecurableitem
568                            where  owner__user_id in (' . join(', ', $userIds) . ')
569                            union all
570                            select securableitem_id
571                            from   permission
572                            where  permitable_id in (' . join(', ', $permitableIds) . ')';
573                    $securableItemIds = R::getCol($sql);
574                    self::$countMethod($mungeTableName, $securableItemIds, $role);
575                }
576
577                // Handle groups for the users in $role. Increment for the parent's parent
578                // roles the models they have explicit permissions on.
579
580                if (count($role->users) > 0)
581                {
582                    $permitableIds = array();
583                    foreach ($role->users as $user)
584                    {
585                        foreach ($user->groups as $group)
586                        {
587                            $permitableIds[] = $group->getClassId('Permitable');
588                        }
589                    }
590                    $permitableIds = array_unique($permitableIds);
591                    $sql = 'select securableitem_id
592                            from   permission
593                            where  permitable_id in (' . join(', ', $permitableIds) . ')';
594                    $securableItemIds = R::getCol($sql);
595                    self::$countMethod($mungeTableName, $securableItemIds, $role->role);
596                }
597
598                // Handle groups for the users $role's child roles. Increment for the role's parent
599                // roles the models they have explicit permissions on.
600
601                if (count($usersInRolesChildren))
602                {
603                    $permitableIds = array();
604                    foreach ($usersInRolesChildren as $user)
605                    {
606                        foreach ($user->groups as $group)
607                        {
608                            $permitableIds[] = $group->getClassId('Permitable');
609                        }
610                    }
611                    $permitableIds = array_unique($permitableIds);
612                    if (count($permitableIds) > 0)
613                    {
614                        $sql = 'select securableitem_id
615                                from   permission
616                                where  permitable_id in (' . join(', ', $permitableIds) . ')';
617                        $securableItemIds = R::getCol($sql);
618                    }
619                    else
620                    {
621                        $securableItemIds = array();
622                    }
623                    self::$countMethod($mungeTableName, $securableItemIds, $role);
624                }
625                if (!$isSet)
626                {
627                    self::garbageCollect($mungeTableName);
628                }
629            }
630        }
631
632        protected static function getAllUsersInRolesChildRolesRecursively(Role $role)
633        {
634            $users = array();
635            foreach ($role->roles as $childRole)
636            {
637                if (!RedBeanDatabase::isFrozen() && $role->isSame($childRole)) // Prevent cycles in database auto build.
638                {
639                    continue;
640                }
641                foreach ($childRole->users as $user)
642                {
643                    $users[] = $user;
644                }
645                $users = array_merge($users, self::getAllUsersInRolesChildRolesRecursively($childRole));
646            }
647            return $users;
648        }
649
650        public static function userAddedToRole(User $user)
651        {
652            assert('$user->role->id > 0');
653            foreach (self::getMungableModelClassNames() as $modelClassName)
654            {
655                $mungeTableName = self::getMungeTableName($modelClassName);
656                $userId = $user->id;
657                $sql = "select securableitem_id
658                        from   ownedsecurableitem
659                        where  owner__user_id = $userId";
660                $securableItemIds = R::getCol($sql);
661                //Increment the parent roles for securableItems that the user is the owner on.
662                self::bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
663
664                //Get all downstream groups the user is in including any groups that are in those groups recursively.
665                //Then for each group found, add weight for the user's upstream roles.
666                $groupMungeIds = array();
667                foreach ($user->groups as $group)
668                {
669                    $groupMungeIds[] = 'G' . $group->id;
670                    self::getAllUpstreamGroupsRecursively($group, $groupMungeIds);
671                }
672                if (count($groupMungeIds) > 0)
673                {
674                    $inSqlPart = SQLOperatorUtil::resolveOperatorAndValueForOneOf('oneOf', $groupMungeIds, true);
675                    $sql = "select distinct $mungeTableName.securableitem_id
676                            from   $mungeTableName
677                            where  $mungeTableName.munge_id $inSqlPart";
678                    $securableItemIds = R::getCol($sql);
679                    self::bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, $user->role);
680                }
681            }
682        }
683
684        public static function userBeingRemovedFromRole(User $user, Role $role)
685        {
686            foreach (self::getMungableModelClassNames() as $modelClassName)
687            {
688                $mungeTableName = self::getMungeTableName($modelClassName);
689                $userId = $user->id;
690                $sql = "select securableitem_id
691                        from   ownedsecurableitem
692                        where  owner__user_id = $userId";
693                $securableItemIds = R::getCol($sql);
694                self::bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, $role);
695
696                $sql = "select $mungeTableName.securableitem_id
697                        from   $mungeTableName, _group__user
698                        where  $mungeTableName.munge_id = concat('G', _group__user._group_id) and
699                               _group__user._user_id = $userId";
700                $securableItemIds = R::getCol($sql);
701                self::bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, $role);
702                /*
703                 * This additional step I don't think is needed because the sql query above actually traps
704                 * the upstream explicit securableItems because the lower level groups will already have a point for
705                 * each of them.
706                    What groups are the user part of and what groups are those groups children of recursively?
707                    For any models that have that group explicity for read, subtract 1 point for the user's
708                    upstream roles from the disconnected role.
709                */
710                self::garbageCollect($mungeTableName);
711            }
712        }
713
714        ///////////////////////////////////////////////////////////////////////
715
716        public static function getAllUpstreamGroupsRecursively(Group $group, & $groupMungeIds)
717        {
718            assert('is_array($groupMungeIds)');
719            if ($group->group->id > 0 )
720            {
721                $groupMungeIds[] = 'G' . $group->group->id;
722                if (!RedBeanDatabase::isFrozen() && $group->isSame($group->group))
723                {
724                    //Do Nothing. Prevent cycles in database auto build.
725                }
726                else
727                {
728                    self::getAllUpstreamGroupsRecursively($group->group, $groupMungeIds);
729                }
730            }
731        }
732
733        public static function getUserRoleIdAndGroupIds(User $user)
734        {
735            if ($user->role->id > 0)
736            {
737                $roleId = $user->role->id;
738            }
739            else
740            {
741                $roleId = null;
742            }
743            $groupIds = array();
744            foreach ($user->groups as $group)
745            {
746                $groupIds[] = $group->id;
747            }
748            return array($roleId, $groupIds);
749        }
750
751        public static function getMungeIdsByUser(User $user)
752        {
753            list($roleId, $groupIds) = self::getUserRoleIdAndGroupIds($user);
754            $mungeIds = array("U$user->id");
755            if ($roleId != null)
756            {
757                $mungeIds[] = "R$roleId";
758            }
759            foreach ($groupIds as $groupId)
760            {
761                $mungeIds[] = "G$groupId";
762            }
763            //Add everyone group
764            $everyoneGroupId = Group::getByName(Group::EVERYONE_GROUP_NAME)->id;
765            if (!in_array("G" . $everyoneGroupId, $mungeIds) && $everyoneGroupId > 0)
766            {
767                $mungeIds[] = "G" . $everyoneGroupId;
768            }
769            return $mungeIds;
770        }
771
772        /**
773         * Public for testing only. Need to manually create test model tables that would not be picked up normally.
774         */
775        public static function recreateTable($mungeTableName)
776        {
777            assert('is_string($mungeTableName) && $mungeTableName  != ""');
778            R::exec("drop table if exists $mungeTableName");
779            R::exec("create table $mungeTableName (
780                        securableitem_id int(11)     unsigned not null,
781                        munge_id         varchar(12)          not null,
782                        count            int(8)      unsigned not null,
783                        primary key (securableitem_id, munge_id)
784                     )");
785            R::exec("create index index_${mungeTableName}_securable_item_id
786                     on $mungeTableName (securableitem_id);");
787        }
788
789        protected static function incrementCount($mungeTableName, $securableItemId, $item)
790        {
791            assert('is_string($mungeTableName) && $mungeTableName != ""');
792            assert('is_int($securableItemId) && $securableItemId > 0');
793            assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
794            $itemId  = $item->id;
795            $type    = self::getMungeType($item);
796            $mungeId = "$type$itemId";
797            R::exec("insert into $mungeTableName
798                                 (securableitem_id, munge_id, count)
799                     values ($securableItemId, '$mungeId', 1)
800                     on duplicate key
801                     update count = count + 1");
802        }
803
804        protected static function setCount($mungeTableName, $securableItemId, $item, $count)
805        {
806            assert('is_string($mungeTableName) && $mungeTableName != ""');
807            assert('is_int($securableItemId) && $securableItemId > 0');
808            assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
809            $itemId  = $item->id;
810            $type    = self::getMungeType($item);
811            $mungeId = "$type$itemId";
812            R::exec("insert into $mungeTableName
813                                 (securableitem_id, munge_id, count)
814                     values ($securableItemId, '$mungeId', $count)
815                     on duplicate key
816                     update count = $count");
817        }
818
819        protected static function decrementCount($mungeTableName, $securableItemId, $item)
820        {
821            assert('is_string($mungeTableName) && $mungeTableName != ""');
822            assert('is_int($securableItemId) && $securableItemId > 0');
823            assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
824            $itemId  = $item->id;
825            $type    = self::getMungeType($item);
826            $mungeId = "$type$itemId";
827            R::exec("update $mungeTableName
828                     set count = count - 1
829                     where securableitem_id = $securableItemId and
830                           munge_id         = '$mungeId'");
831        }
832
833        protected static function decrementCountForAllSecurableItems($mungeTableName, $item)
834        {
835            assert('is_string($mungeTableName) && $mungeTableName != ""');
836            assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
837            $itemId  = $item->id;
838            $type    = self::getMungeType($item);
839            $mungeId = "$type$itemId";
840            R::exec("update $mungeTableName
841                     set count = count - 1
842                     where munge_id = '$mungeId'");
843        }
844
845        protected static function bulkIncrementCount($mungeTableName, $securableItemIds, $item)
846        {
847            assert('is_string($mungeTableName) && $mungeTableName != ""');
848            assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
849            foreach ($securableItemIds as $securableItemId)
850            {
851                self::incrementCount($mungeTableName, intval($securableItemId), $item);
852            }
853        }
854
855        protected static function bulkDecrementCount($mungeTableName, $securableItemIds, $item)
856        {
857            assert('is_string($mungeTableName) && $mungeTableName != ""');
858            assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
859            foreach ($securableItemIds as $securableItemId)
860            {
861                self::decrementCount($mungeTableName, intval($securableItemId), $item);
862            }
863        }
864
865        protected static function incrementParentRolesCounts($mungeTableName, $securableItemId, Role $role)
866        {
867            assert('is_string($mungeTableName) && $mungeTableName != ""');
868            assert('is_int($securableItemId) && $securableItemId > 0');
869            if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
870            {
871                return;
872            }
873            if ($role->role->id > 0)
874            {
875                self::incrementCount            ($mungeTableName, $securableItemId, $role->role);
876                self::incrementParentRolesCounts($mungeTableName, $securableItemId, $role->role);
877            }
878        }
879
880        protected static function decrementParentRolesCounts($mungeTableName, $securableItemId, Role $role)
881        {
882            assert('is_string($mungeTableName) && $mungeTableName != ""');
883            assert('is_int($securableItemId) && $securableItemId > 0');
884            if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
885            {
886                return;
887            }
888            if ($role->role->id > 0)
889            {
890                self::decrementCount            ($mungeTableName, $securableItemId, $role->role);
891                self::decrementParentRolesCounts($mungeTableName, $securableItemId, $role->role);
892            }
893        }
894
895        protected static function decrementParentRolesCountsForAllSecurableItems($mungeTableName, Role $role)
896        {
897            assert('is_string($mungeTableName) && $mungeTableName != ""');
898            if (!RedBeanDatabase::isFrozen() && $role->role->isSame($role)) // Prevent cycles in database auto build.
899            {
900                return;
901            }
902            if ($role->role->id > 0)
903            {
904                self::decrementCountForAllSecurableItems            ($mungeTableName, $role->role);
905                self::decrementParentRolesCountsForAllSecurableItems($mungeTableName, $role->role);
906            }
907        }
908
909        protected static function bulkIncrementParentRolesCounts($mungeTableName, $securableItemIds, Role $role)
910        {
911            foreach ($securableItemIds as $securableItemId)
912            {
913                self::incrementParentRolesCounts($mungeTableName, intval($securableItemId), $role);
914            }
915        }
916
917        protected static function bulkDecrementParentRolesCounts($mungeTableName, $securableItemIds, Role $role)
918        {
919            foreach ($securableItemIds as $securableItemId)
920            {
921                self::decrementParentRolesCounts($mungeTableName, intval($securableItemId), $role);
922            }
923        }
924
925        // This must be called ny any public method which decrements
926        // counts after it has done all its count decrementing.
927        // It is not done in decrementCount to avoid doing it more
928        // than is necessary.
929        protected static function garbageCollect($mungeTableName)
930        {
931            assert("R::getCell('select count(*)
932                                from   $mungeTableName
933                                where  count < 0') == 0");
934            R::exec("delete from $mungeTableName
935                     where       count = 0");
936            assert("R::getCell('select count(*)
937                                from   $mungeTableName
938                                where  count < 1') == 0");
939        }
940
941        protected static function getMungeType($item)
942        {
943            assert('$item instanceof User || $item instanceof Group || $item instanceof Role');
944            return substr(get_class($item), 0, 1);
945        }
946
947        //public for testing only
948        public static function getMungableModelClassNames()
949        {
950            try
951            {
952                return GeneralCache::getEntry('mungableModelClassNames');
953            }
954            catch (NotFoundException $e)
955            {
956                $mungableClassNames = self::findMungableModelClassNames();
957                GeneralCache::cacheEntry('mungableModelClassNames', $mungableClassNames);
958                return $mungableClassNames;
959            }
960        }
961
962        //public for testing only.
963        public static function findMungableModelClassNames()
964        {
965            $mungableModelClassNames = array();
966            $modules = Module::getModuleObjects();
967            foreach ($modules as $module)
968            {
969                $modelClassNames = $module::getModelClassNames();
970                foreach ($modelClassNames as $modelClassName)
971                {
972                    if (is_subclass_of($modelClassName, 'SecurableItem') &&
973                        $modelClassName::hasReadPermissionsOptimization())
974                    {
975                        $mungableModelClassNames[] = $modelClassName;
976                    }
977                }
978            }
979            return $mungableModelClassNames;
980        }
981
982        protected static function getMainTableName($modelClassName)
983        {
984            assert('is_string($modelClassName) && $modelClassName != ""');
985            return RedBeanModel::getTableName($modelClassName);
986        }
987
988        public static function getMungeTableName($modelClassName)
989        {
990            assert('is_string($modelClassName) && $modelClassName != ""');
991            return self::getMainTableName($modelClassName) . '_read';
992        }
993    }
994?>