Ticket #132: groups.patch
| File groups.patch, 23.8 kB (added by michaeltwofish, 11 months ago) |
|---|
-
system/admin/groups.php
14 14 </form> 15 15 <ul> 16 16 <?php 17 foreach ( $groups as $id => $name ) 18 { 19 echo '<li>'; 20 echo '<form method="post" action=""><input type="hidden" name="group" value="' . $id . '"><input type="submit" name="delete_group" value="Delete"> '; 17 foreach ( $groups as $group ) { 18 echo '<li>'; 19 echo '<form method="post" action=""><input type="hidden" name="group" value="' . $group->name . '"><input type="submit" name="delete_group" value="Delete"> '; 21 20 echo '<input type="submit" name="edit_group" value="Edit"> '; 22 echo $ name . '</form>';23 echo '</li>';21 echo $group->name . '</form>'; 22 echo '</li>'; 24 23 } 25 24 ?> 26 25 </ul> … … 28 27 <div class="column span-8"> 29 28 <p>Members</p> 30 29 <?php 31 if ( isset( $group ) ) {30 if ( isset( $group_edit ) ) { 32 31 if ( isset( $users) && ( ! empty( $users ) ) ) { 32 echo '<p>Editing members of ' . $group_edit->name . '</p>'; 33 33 echo '<form method="post" action="">'; 34 echo '<input type="hidden" name="group" value="' . $group . '">'; 35 echo Utils::html_select( 'add_user', $users ); 36 echo ' <input type="submit" value="Add"></form>'; 34 echo '<input type="hidden" name="group" value="' . $group_edit->name . '">'; 35 $user_data= array(); 36 foreach ( $users as $user ) { 37 $user_data[$user->id]= $user->username; 38 } 39 echo Utils::html_select( 'add_user', $user_data ); 40 echo '<input type="submit" value="Add"></form>'; 37 41 } 38 if ( ! empty($group_members) ) { 39 echo '<ul>'; 40 foreach ( $group_members as $member ) { 41 $user= User::get_by_id($member); 42 echo '<li><form method="post" action=""><input type="hidden" name="remove_user" value="' . $user->id . '">'; 43 echo '<input type="hidden" name="user_group" value="' . $group . '"><input type="submit" value="Remove"></form> ' . $user->username . '</li>'; 44 } 45 echo '</ul>'; 42 if ( ! empty($members) ) { 43 echo '<ul>'; 44 foreach ( $members as $member ) { 45 echo '<li><form method="post" action=""><input type="hidden" name="remove_user" value="' . $member->id . '">'; 46 echo '<input type="hidden" name="group" value="' . $group_edit->name . '"><input type="submit" value="Remove"> ' . $member->username . '</form></li>'; 47 } 48 echo '</ul>'; 46 49 } else { 47 50 echo '<p>No members.</p>'; 48 51 } … … 51 54 </div> 52 55 <div class="column span-8 last"> 53 56 <p>Permissions</p> 57 <?php 58 if ( isset( $group_edit ) ) { 59 if ( isset( $permissions) && ( ! empty( $permissions ) ) ) { 60 echo '<p>Editing Permissions of ' . $group_edit->name . '</p>'; 61 echo '<form method="post" action="">'; 62 echo '<input type="hidden" name="group" value="' . $group_edit->name . '">'; 63 $permission_data= array(); 64 foreach ( $permissions as $permission ) { 65 $permission_data[$permission->id]= $permission->name; 66 } 67 echo Utils::html_select( 'grant_permission', $permission_data ); 68 echo '<input type="submit" value="Grant"></form>'; 69 echo Utils::html_select( 'deny_permission', $permission_data ); 70 echo '<input type="submit" value="Deny"></form>'; 71 } 72 if ( ! empty($permissions_set) ) { 73 echo '<ul>'; 74 foreach ( $permissions_set as $permission_set ) { 75 echo '<li><form method="post" action=""><input type="hidden" name="permission_set" value="' . $permission_set->id . '">'; 76 echo '<input type="hidden" name="group" value="' . $group_edit->name . '">'; 77 echo '<input type="submit" value="Revoke"> ' . $permission->name . '</form></li>'; 78 } 79 echo '</ul>'; 80 } else { 81 echo '<p>No permissions.</p>'; 82 } 83 } 84 ?> 54 85 </div> 55 86 </div> 56 87 <?php include('footer.php');?> -
system/classes/adminhandler.php
991 991 992 992 public function post_groups() 993 993 { 994 $this->theme->groups= UserGroup ::all_groups();994 $this->theme->groups= UserGroups::get(); 995 995 if ( isset( $this->handler_vars['add_group'] ) ) { 996 if ( UserGroup::add_group( $this->handler_vars['add_group'] ) ) {997 Session::notice( sprintf(_t( 'Added group %s'), $this->handler_vars['add_group'] ) );998 $this->theme->groups= UserGroup::all_groups();996 $name= $this->handler_vars['add_group']; 997 if ( UserGroup::exists($name) ) { 998 Session::notice( sprintf(_t( 'The group %s already exists'), $name ) ); 999 999 } 1000 else { 1001 $groupdata= array( 1002 'name' => $name 1003 ); 1004 $group= UserGroup::create($groupdata); 1005 Session::notice( sprintf(_t( 'Added group %s'), $name ) ); 1006 // reload the groups 1007 $this->theme->groups= UserGroups::get(); 1008 } 1000 1009 } 1001 1010 1002 1011 if ( isset( $this->handler_vars['delete_group'] ) ) { 1003 // capture the group name before we delete it 1004 $group_name= $this->theme->groups[$this->handler_vars['group']]; 1005 if ( UserGroup::remove_group( intval( $this->handler_vars['group'] ) ) ) { 1006 Session::notice( sprintf( _t( 'Removed group %s' ), $group_name ) ); 1012 $name= $this->handler_vars['group']; 1013 if ( !UserGroup::exists($name) ) { 1014 Session::notice( sprintf(_t( 'The group %s does not exist'), $name ) ); 1015 } 1016 else { 1017 $group= UserGroup::get($name); 1018 $group->delete(); 1019 Session::notice( sprintf( _t( 'Removed group %s' ), $name ) ); 1007 1020 // reload the groups 1008 $this->theme->groups= UserGroup ::all_groups();1021 $this->theme->groups= UserGroups::get(); 1009 1022 } 1010 1023 } 1011 1024 1012 1025 if ( isset( $this->handler_vars['add_user'] ) ) { 1013 UserGroup::add_user( $this->handler_vars['group'], $this->handler_vars['add_user'] ); 1014 $this->theme->groups= UserGroup::all_groups(); 1026 $name= $this->handler_vars['group']; 1027 $user_id= $this->handler_vars['add_user']; 1028 if ( !UserGroup::exists($name) ) { 1029 Session::notice( sprintf(_t( 'The group %s does not exist'), $name ) ); 1030 } 1031 else { 1032 $group= UserGroup::get($name); 1033 $user= User::get((int)$user_id); 1034 $group->add((int)$user_id); 1035 $group->update(); 1036 Session::notice( sprintf(_t( 'Added user %s to group %s'), $user->username, $name ) ); 1037 // reload the groups 1038 $this->theme->groups= UserGroups::get(); 1039 } 1015 1040 } 1016 1041 1017 1042 if ( isset( $this->handler_vars['remove_user'] ) ) { 1018 UserGroup::remove_user( $this->handler_vars['user_group'], $this->handler_vars['remove_user'] ); 1019 $this->theme->groups= UserGroup::all_groups(); 1043 $name= $this->handler_vars['group']; 1044 $user_id= $this->handler_vars['remove_user']; 1045 if ( !UserGroup::exists($name) ) { 1046 Session::notice( sprintf(_t( 'The group %s does not exist'), $name ) ); 1047 } 1048 else { 1049 $group= UserGroup::get($name); 1050 $user= User::get((int)$user_id); 1051 $group->remove((int)$user_id); 1052 $group->update(); 1053 Session::notice( sprintf(_t( 'Removed user %s from group %s'), $user->username, $name ) ); 1054 // reload the groups 1055 $this->theme->groups= UserGroups::get(); 1056 } 1020 1057 } 1021 1058 1022 1059 if ( isset( $this->handler_vars['edit_group'] ) ) { 1023 $this->theme->group= $this->handler_vars['group']; 1024 $this->theme->group_members= UserGroup::members( $this->theme->group ); 1025 $all_users= Users::get_all(); 1026 $users= array(); 1027 foreach ( $all_users as $user ) { 1028 if ( ! in_array( $user->id, $this->theme->group_members ) ) { 1029 $users[$user->id]= $user->username; 1030 } 1060 $name= $this->handler_vars['group']; 1061 if ( !UserGroup::exists($name) ) { 1062 Session::notice( sprintf(_t( 'The group %s does not exist'), $name ) ); 1031 1063 } 1032 $this->theme->users= $users; 1064 else { 1065 $group= UserGroup::get($name); 1066 $this->theme->group_edit= $group; 1067 $this->theme->members= $group->members(); 1068 $this->theme->users= Users::get_all(); 1069 } 1033 1070 } 1034 1071 $this->display( 'groups' ); 1035 1072 } -
system/classes/usergroup.php
1 1 <?php 2 2 /** 3 3 * Habari UserGroup Class 4 * 4 5 * @package Habari 5 6 **/ 6 class UserGroup 7 class UserGroup extends QueryRecord 7 8 { 8 /** 9 * An array of users assigned to a specific group id. 10 * Both the group and the user are id integers, not string values. 11 * The user arrays have both the key and the value set to the user_id. 12 * 13 * For example: 14 * <code> 15 * self::$groups= array( 16 * 1 => array(1 => 1), 17 * 2 => array(1 => 1, 2 => 2), 18 * ); 19 * </code> 20 **/ 21 private static $groups= array(); 9 /** 10 * Static storage for this group's info 11 **/ 12 // these hold the values as fetched from the DB 13 private $members= array(); 14 private $permissions_granted= null; 15 private $permissons_denied= null; 22 16 23 /** 24 * An array of group IDs with group names. 25 * For example: 26 * <code> 27 * self::$group_names= array( 28 * 1 => 'administrators', 29 * 2 => 'guests', 30 * ); 31 * </code> 32 **/ 33 private static $group_names= array(); 17 // these hold changes before they're committed to the DB 18 private $new_members= array(); 19 private $removed_members= array(); 20 private $new_permissions_granted= null; 21 private $new_permissons_denied= null; 34 22 35 /**36 * An array of permssions assigned to a group.37 * The group and permission are both id integers, not string values.38 * The key of each permission array is the permission id.39 * The value of the permission id is boolean on whether to grant or deny40 * that permission.41 *42 * For example:43 * <code>44 * self::$group_permissions= array(45 * 1 => array( 1 => true, 2 => true),46 * 2 => array( 2 => false),47 * );48 * </code>49 **/50 private static $group_permissions= array();51 52 23 /** 53 * __static() class members are called by __autoload() 24 * get default fields for this record 25 * @return array an array of the fields used in the UserGroup table 54 26 **/ 55 public static function __static()27 public static function default_fields() 56 28 { 57 self::load_groups(); 29 return array( 30 'id' => '', 31 'name' => '' 32 ); 58 33 } 59 34 60 35 /** 61 * Load all group data from the DB 36 * Constructor for the UserGroup class 37 * @param array $paramarray an associative array of UserGroup fields 62 38 **/ 63 public static function load_groups ()39 public function __construct( $paramarray= array() ) 64 40 { 65 self::$group_names= array(); 66 $results= DB::get_results( 'SELECT * FROM {groups}' ); 67 natsort( $results ); 68 foreach ($results as $group) { 69 self::$group_names[$group->id]= $group->name; 41 $this->fields= array_merge( 42 self::default_fields(), 43 $this->fields ); 44 parent::__construct( $paramarray ); 45 $this->exclude_fields('id'); 46 47 // if we have an ID, load this UserGroup's members and permissions 48 if ( $this->id ) { 49 // TODO can we do this through Users::get()? 50 $member_ids= DB::get_column( 'SELECT user_id FROM {users_groups} WHERE group_id= ?', array( $this->id ) ); 51 foreach ($member_ids as $id) { 52 $this->members[]= User::get((int)$id); 53 } 54 $this->permissions_granted= DB::get_column( 'SELECT permission_id FROM {groups_permissions} WHERE group_id=? AND denied=0 ', array( $this->id ) ); 55 $this->permissions_denied= DB::get_column( 'SELECT permission_id FROM {groups_permissions} WHERE group_id=? AND denied=1', array( $this->id ) ); 70 56 } 71 self::$groups= array();72 $results= DB::get_results( 'SELECT * FROM {users_groups}' );73 foreach ( $results as $group ) {74 self::$groups[$group->group_id][$group->user_id]= $group->user_id;75 }76 57 } 77 58 78 59 /** 79 * function all_groups80 * Returns an array of all the groups, in the form id => name81 * @return array an array of group id => name60 * Create a new UserGroup object and save it to the database 61 * @param array An associative array of UserGroup fields 62 * @return UserGroup the UserGroup that was created 82 63 **/ 83 public static function all_groups()64 public static function create( $paramarray ) 84 65 { 85 return self::$group_names; 66 $usergroup= new UserGroup( $paramarray ); 67 $usergroup->insert(); 68 return $usergroup; 86 69 } 87 70 88 71 /** 89 * function add_group 90 * Adds a new group to the Groups table 91 * @param string The name of the group to add 92 * @return bool Whether the group was added or not 72 * Save a new UserGroup to the UserGroup table 93 73 **/ 94 public static function add_group( $group)74 public function insert() 95 75 { 96 if ( in_array( $group, self::$group_names ) ) { 97 Session::notice( _t( 'That group already exists.' ) ); 98 return false; 76 // If all goes according plan, $result will stay true 77 $result= true; 78 // Allow plugins to disallow adding the group, by changing $result 79 $result= Plugins::filter('usergroup_insert_allow', $result, $this); 80 // If still allowed, add the group 81 if ( $result ) { 82 Plugins::act('usergroup_insert_before', $this); 83 $this->exclude_fields('id'); 84 // $result will change to false if insertion fails 85 // Perhaps we should throw an exception here instead? 86 $result= parent::insertRecord( DB::table('groups') ); 87 $this->fields['id']= DB::last_insert_id(); 88 EventLog::log('New group created: ' . $this->name, 'info', 'default', 'habari'); 89 Plugins::act('usergroup_insert_after', $this); 99 90 } 100 $allow= true; 101 $allow= Plugins::filter('usergroup_insert_allow', $allow ); 102 if ( ! $allow ) { 103 return false; 104 } 105 Plugins::act('usergroup_add_before'); 106 $results= DB::query( 'INSERT INTO {groups} (name) VALUES (?)', array( $group ) ); 107 Plugins::act('usergroup_add_after'); 108 self::load_groups(); 109 return true; 91 return $result; 110 92 } 111 93 112 94 /** 113 * function remove_group 114 * Remove a group from the Groups table. Also removes all users_groups members of the group. 115 * @param mixed A Group name or integer ID 116 * @return bool Whether the group was removed or not 95 * Updates an existing UserGroup in the DB 117 96 **/ 118 public static function remove_group( $group)97 public function update() 119 98 { 120 if ( is_int( $group ) ) { 121 if ( ! array_key_exists( $group, self::$group_names ) ) { 122 Session::notice( _t('That group does not exist.' ) ); 123 return false; 99 $allow= true; 100 // Allow plugins to disallow updating the group, by changing $allow 101 $allow= Plugins::filter('usergroup_update_allow', $allow, $this); 102 if ( !$allow ) { 103 return; 104 } 105 Plugins::act('usergroup_update_before', $this); 106 // figure out what needs to be changed 107 if ( !empty($this->new_members) ) { 108 // add this group's new members 109 foreach ($this->new_members as $new_member) { 110 $result= DB::query( 'INSERT INTO {users_groups} (user_id, group_id) VALUES (?, ?)', array( $new_member->id, $this->id ) ); 111 EventLog::log('User ' . $new_member->username . ' added to group ' . $this->name, 'info', 'default', 'habari'); 124 112 } 125 } else { 126 if ( ! in_array( $group, self::$group_names ) ) { 127 Session::notice( _t('That group does not exist.' ) ); 128 return false; 113 } 114 if ( !empty($this->removed_members) ) { 115 // remove this group's removed members 116 foreach ($this->removed_members as $removed_member) { 117 $result= DB::query( 'DELETE FROM {users_groups} WHERE user_id=? AND group_id=?', array( $removed_member->id, $this->id ) ); 118 EventLog::log('User ' . $removed_member->username . ' removed from group ' . $this->name, 'info', 'default', 'habari'); 129 119 } 130 $groups= array_flip( self::$group_names );131 $group= $groups[ $group ];132 120 } 133 $allow= true; 134 $allow= Plugins::filter('usergroup_insert_allow', $allow ); 135 if ( ! $allow ) { 136 return false; 121 // add this group's new permissions 122 //$result= DB::query( 'DELETE FROM {groups_permissions} WHERE group_id=?', array( $this->id ) ); 123 Plugins::act('usergroup_update_after', $this); 124 } 125 126 /** 127 * Delete a UserGroup 128 **/ 129 public function delete() 130 { 131 // If all goes according plan, $result will stay true 132 $result= true; 133 // Allow plugins to disallow removing the group, by changing $result 134 $result= Plugins::filter('usergroup_delete_allow', $result, $this ); 135 // If still allowed, remove the group 136 if ( $result ) { 137 Plugins::act('usergroup_delete_before', $this); 138 // If anything fails, this will return false. Perhaps this isn't the right thing to do. 139 // remove all this group's permissions 140 $result= DB::query( 'DELETE FROM {groups_permissions} WHERE group_id=?', array( $this->id ) ) && $result; 141 // remove all this group's members 142 $result= DB::query( 'DELETE FROM {users_groups} WHERE group_id=?', array( $this->id ) ) && $result; 143 // remove this group 144 $result= parent::deleteRecord( DB::table('groups'), array( 'id' => $this->id ) ) && $result; 145 Plugins::act('usergroup_delete_after', $this); 146 EventLog::log('Group created: ' . $this->name, 'info', 'default', 'habari'); 137 147 } 138 Plugins::act('usergroup_remove_before'); 139 // delete the group 140 $results= DB::query( 'DELETE FROM {groups} WHERE id=?', array( $group) ); 141 // and delete any members assigned to the group 142 $results= DB::query( 'DELETE FROM {users_groups} WHERE group_id=?', array( $group ) ); 143 Plugins::act('usergroup_remove_after'); 144 self::load_groups(); 145 return true; 148 return $result; 146 149 } 147 150 148 151 /** 149 152 * function members 150 * returns an array of user IDs belogning to the specified group 151 * @param int a group ID 152 * @return array an array of user IDs 153 * returns an array of users belonging to this UserGroup 154 * @return array an array of Users that belong to this group 153 155 **/ 154 public static function members( $group)156 public function members() 155 157 { 156 $members= array(); 157 if ( isset( self::$groups[ intval($group) ] ) ) { 158 $members= self::$groups[ intval($group) ]; 158 return $this->members; 159 } 160 161 /** 162 * Helper function to add a user to this group 163 * @param mixed a user ID or name 164 **/ 165 private function add_user( $param ) 166 { 167 $user= User::get($param); 168 // Add the user if not already in the group 169 if ( !in_array( $user, $this->members ) && !in_array( $user, $this->new_members ) ) { 170 $this->new_members[]= $user; 159 171 } 160 return $members;172 return true; 161 173 } 162 174 163 175 /** 164 * Add a user to a group 165 * @param int a group ID 166 * @param int a user ID 176 * Add a user or users to this group 177 * @param mixed a user ID or name, or an array of them 167 178 **/ 168 public static function add_user( $group, $id)179 public function add( $param ) 169 180 { 170 if ( empty( self::$groups[ intval( $group) ] ) || ! in_array( self::$groups[ intval($group) ], $id ) ) 171 { 172 $results= DB::query( 'INSERT INTO ' . DB::table('users_groups') . ' (group_id, user_id) VALUES (?, ?)', array( intval($group), intval($id) ) ); 173 $groups= self::load_groups(); 181 if ( is_array( $param ) ) { 182 foreach ( $param as $user ) { 183 $this->add_user($user); 184 } 185 } 186 else { 187 $this->add_user($param); 174 188 } 175 $user= User::get_by_id( $id ); 176 Session::notice( sprintf( _t('Added %1s to %2s'), $user->username, self::$group_names[$group] ) ); 189 return true; 177 190 } 178 191 179 192 /** 180 * Remove a user from a group 181 * @param int a group ID 182 * @param int a user ID 193 * Helper function to remove a user from this group 194 * @param mixed a user ID or name 183 195 **/ 184 p ublic static function remove_user( $group, $id)196 private function remove_user( $param ) 185 197 { 186 if ( in_array( intval($id), self::$groups[ intval($group) ] ) ) { 187 $results= DB::query( 'DELETE FROM {users_groups} WHERE group_id=? and user_id= ?', array( intval($group), intval($id) ) ); 188 $groups= self::load_groups(); 198 $user= User::get($param); 199 // Remove the user if already in the group 200 if ( in_array( $user, $this->new_members ) ) { 201 unset($this->new_members[$user]); 189 202 } 190 $user= User::get_by_id($id); 191 Session::notice( sprintf( _t('Removed %1s from %2s'), $user->username, self::$group_names[$group]) ); 203 elseif ( in_array( $user, $this->members ) ) { 204 $this->removed_members[]= $user; 205 } 206 return true; 192 207 } 193 208 194 209 /** 195 * Assign a new permission to a group 196 * @param int A group ID 197 * @param int A permission ID 198 * @param int Whether this permission should be denied to this group 210 * Remove a user or users from this group 211 * @param mixed a user ID or name, or an array of them 199 212 **/ 200 public static function grant_permission( $group, $permission, $denied= 0)213 public function remove( $param ) 201 214 { 202 // first, see if this group has this permission 203 // either granted or denied 204 $check= DB::get_row('SELECT id,denied FROM ' . DB::table('groups_permissions') . ' WHERE group_id=? AND permission_id=?', array( intval($group), intval($permission) ) ); 205 if ( ! empty( $check ) ) { 206 if ( $check->denied === intval($denied) ) { 207 // no change 208 return; 209 } else { 210 // the "denied" value is different, so update 211 // the existing record 212 DB::query('UPDATE ' . DB::table('groups_permissions') . ' SET denied=? WHERE id=?', array( intval($denied), $check->id) ); 213 return; 215 if ( is_array( $param ) ) { 216 foreach ( $param as $user ) { 217 $this->remove_user($user); 214 218 } 219 } 220 else { 221 $this->remove_user($param); 215 222 } 216 // We got here, which means that the permission does not yet 217 // exist. Let's add it. 218 DB::query('INSERT INTO ' . DB::table('groups_permissions') . ' (group_id, permission_id, denied) VALUES ( ?, ?, ?)', array( intval($group), intval($permission), intval($denied) ) ); 223 return true; 219 224 } 220 225 221 226 /** 227 * Assign a new permission to this group 228 * @param int A permission ID 229 **/ 230 public function grant( $permission ) 231 { 232 } 233 234 /** 235 * Deny a permission to this group 236 * @param int The permission ID to be denied 237 **/ 238 public function deny( $permission ) 239 { 240 } 241 242 /** 222 243 * Remove a permission from a group 223 * @param int a group ID224 244 * @param int a permission ID 225 245 **/ 226 public static function revoke_permission( $group,$permission )246 public function revoke( $permission ) 227 247 { 228 DB::query('DELETE FROM ' . DB::table('groups_permissions') . ' WHERE group_id=? and permission_id=?', array( intval($group), intval($permission) ) );229 248 } 230 249 231 250 /** 232 251 * Determine whether members of a group can do something 233 * @param int a group ID234 252 * @param string a text description of a permission 235 253 * @return bool Whether the group can do the thing 236 254 **/ 237 public static function can( $group_id,$permission )255 public function can( $permission ) 238 256 { 239 return ACL::group_can( intval($group_id), $permission );257 return ACL::group_can( $this->id, $permission ); 240 258 } 259 260 /** 261 * Fetch a group from the database by name or ID. 262 * This is a wrapper function that will invoke the appropriate 263 * get_by_* method. 264 * 265 * @param mixed $group group ID or name 266 * @return object UserGroup object, or FALSE 267 */ 268 public static function get( $group ) 269 { 270 if ( is_int( $group ) ) { 271 // Got a UserGroup ID 272 $group= self::get_by_id( $group ); 273 } 274 else { 275 // Got UserGroup name 276 $group= self::get_by_name( $group ); 277 } 278 // $group will be a UserGroup object, or false depending 279 // on the results of the get_by_* method called above 280 return $group; 281 } 282 283 /** 284 * Select a group from the database by its id 285 * 286 * @param int $id 287 * @return Group 288 */ 289 public static function get_by_id( $id ) { 290 if ( 0 == $id ) { 291 return false; 292 } 293 294 $params= array( 295 'id' => $id, 296 &
