features
3 years ago
roles
3 years ago
admin-load.php
3 years ago
admin.php
3 years ago
backup-handler.php
4 years ago
backup.php
4 years ago
cap-helper.php
4 years ago
filters-admin.php
4 years ago
filters-woocommerce.php
4 years ago
filters-wp_rest_workarounds.php
4 years ago
filters.php
4 years ago
functions-admin.php
3 years ago
functions.php
4 years ago
handler.php
4 years ago
inflect-cme.php
4 years ago
manager.php
3 years ago
network.php
4 years ago
pp-handler.php
4 years ago
pp-ui.php
4 years ago
publishpress-roles.php
4 years ago
settings-handler.php
3 years ago
settings-ui.php
3 years ago
settings.php
3 years ago
admin-load.php
449 lines
| 1 | <?php |
| 2 | |
| 3 | /* |
| 4 | * PublishPress Capabilities [Free] |
| 5 | * |
| 6 | * Admin execution controller: menu registration and other filters and actions that need to be loaded for every wp-admin URL |
| 7 | * |
| 8 | * This module should not include full functions related to our own plugin screens. |
| 9 | * Instead, use these filter and action handlers to load other classes when needed. |
| 10 | * |
| 11 | */ |
| 12 | class PP_Capabilities_Admin_UI { |
| 13 | function __construct() { |
| 14 | global $pagenow; |
| 15 | |
| 16 | /** |
| 17 | * The class responsible for handling notifications |
| 18 | */ |
| 19 | require_once (dirname(CME_FILE) . '/classes/pp-capabilities-notices.php'); |
| 20 | |
| 21 | add_action('init', [$this, 'featureRestrictionsGutenberg'], PHP_INT_MAX - 1); |
| 22 | |
| 23 | if (is_admin()) { |
| 24 | add_action('admin_init', [$this, 'featureRestrictionsClassic'], PHP_INT_MAX - 1); |
| 25 | } |
| 26 | |
| 27 | add_action('admin_enqueue_scripts', [$this, 'adminScripts'], 100); |
| 28 | add_action('admin_print_scripts', [$this, 'adminPrintScripts']); |
| 29 | |
| 30 | add_action('profile_update', [$this, 'action_profile_update'], 10, 2); |
| 31 | |
| 32 | if (is_multisite()) { |
| 33 | add_action('add_user_to_blog', [$this, 'action_profile_update'], 9); |
| 34 | } else { |
| 35 | add_action('user_register', [$this, 'action_profile_update'], 9); |
| 36 | } |
| 37 | |
| 38 | if (is_admin() && (isset($_REQUEST['page']) && (in_array($_REQUEST['page'], ['pp-capabilities', 'pp-capabilities-backup', 'pp-capabilities-roles', 'pp-capabilities-admin-menus', 'pp-capabilities-editor-features', 'pp-capabilities-nav-menus', 'pp-capabilities-settings', 'pp-capabilities-admin-features'])) |
| 39 | |
| 40 | || (!empty($_REQUEST['action']) && in_array($_REQUEST['action'], ['pp-roles-add-role', 'pp-roles-delete-role', 'pp-roles-hide-role', 'pp-roles-unhide-role'])) |
| 41 | || ( ! empty($_SERVER['SCRIPT_NAME']) && strpos(sanitize_text_field($_SERVER['SCRIPT_NAME']), 'p-admin/plugins.php' ) && ! empty($_REQUEST['action'] ) ) |
| 42 | || ( isset($_GET['action']) && ('reset-defaults' == $_GET['action']) && isset($_REQUEST['_wpnonce']) && wp_verify_nonce(sanitize_key($_REQUEST['_wpnonce']), 'capsman-reset-defaults') ) |
| 43 | || in_array( $pagenow, array( 'users.php', 'user-edit.php', 'profile.php', 'user-new.php' ) ) |
| 44 | ) ) { |
| 45 | global $capsman; |
| 46 | |
| 47 | // Run the plugin |
| 48 | require_once ( dirname(CME_FILE) . '/framework/lib/formating.php' ); |
| 49 | require_once ( dirname(CME_FILE) . '/framework/lib/users.php' ); |
| 50 | |
| 51 | require_once ( dirname(CME_FILE) . '/includes/manager.php' ); |
| 52 | $capsman = new CapabilityManager(); |
| 53 | } else { |
| 54 | add_action( 'admin_menu', [$this, 'cmeSubmenus'], 20 ); |
| 55 | } |
| 56 | |
| 57 | add_action('init', function() { // late execution avoids clash with autoloaders in other plugins |
| 58 | global $pagenow; |
| 59 | |
| 60 | if ((($pagenow == 'admin.php') && isset($_GET['page']) && in_array($_GET['page'], ['pp-capabilities', 'pp-capabilities-roles', 'pp-capabilities-backup'])) // @todo: CSS for button alignment in Editor Features, Admin Features |
| 61 | || (defined('DOING_AJAX') && DOING_AJAX && !empty($_REQUEST['action']) && (false !== strpos(sanitize_key($_REQUEST['action']), 'capability-manager-enhanced'))) |
| 62 | ) { |
| 63 | if (!class_exists('\PublishPress\WordPressReviews\ReviewsController')) { |
| 64 | include_once PUBLISHPRESS_CAPS_ABSPATH . '/vendor/publishpress/wordpress-reviews/ReviewsController.php'; |
| 65 | } |
| 66 | |
| 67 | if (class_exists('\PublishPress\WordPressReviews\ReviewsController')) { |
| 68 | $reviews = new \PublishPress\WordPressReviews\ReviewsController( |
| 69 | 'capability-manager-enhanced', |
| 70 | 'PublishPress Capabilities', |
| 71 | plugin_dir_url(CME_FILE) . 'common/img/capabilities-wp-logo.png' |
| 72 | ); |
| 73 | |
| 74 | add_filter('publishpress_wp_reviews_display_banner_capability-manager-enhanced', [$this, 'shouldDisplayBanner']); |
| 75 | |
| 76 | $reviews->init(); |
| 77 | } |
| 78 | } |
| 79 | }); |
| 80 | |
| 81 | |
| 82 | add_filter('pp_capabilities_feature_post_types', [$this, 'fltEditorFeaturesPostTypes'], 5); |
| 83 | add_filter('block_editor_settings_all', [$this, 'filterCodeEditingStatus'], 999); |
| 84 | add_filter('classic_editor_enabled_editors_for_post_type', [$this, 'filterRolePostTypeEditor'], 10, 2); |
| 85 | add_filter('classic_editor_plugin_settings', [$this, 'filterRoleEditorSettings']); |
| 86 | |
| 87 | //capabilities settings |
| 88 | add_action('pp-capabilities-settings-ui', [$this, 'settingsUI']); |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * Filters the editors that are enabled for the post type. |
| 93 | * |
| 94 | * @param array $editors Associative array of the editors and whether they are enabled for the post type. |
| 95 | * @param string $post_type The post type. |
| 96 | */ |
| 97 | public function filterRolePostTypeEditor($editors, $post_type) { |
| 98 | $user = wp_get_current_user(); |
| 99 | |
| 100 | if (is_object($user) && isset($user->roles)) { |
| 101 | $current_user_editors = []; |
| 102 | foreach ($user->roles as $user_role) { |
| 103 | //get role option |
| 104 | $role_option = get_option("pp_capabilities_{$user_role}_role_option", []); |
| 105 | if (is_array($role_option) && !empty($role_option) && !empty($role_option['role_editor'])) { |
| 106 | $current_user_editors = array_merge($current_user_editors, $role_option['role_editor']); |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | if (!empty($current_user_editors)) { |
| 111 | $current_user_editors = array_unique($current_user_editors); |
| 112 | $editors = array( |
| 113 | 'classic_editor' => in_array('classic_editor', $current_user_editors) ? true : false, |
| 114 | 'block_editor' => in_array('block_editor', $current_user_editors) ? true : false, |
| 115 | ); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | return $editors; |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Override the classic editor plugin's settings. |
| 124 | * |
| 125 | * @param bool $settings |
| 126 | * @return mixed |
| 127 | */ |
| 128 | public function filterRoleEditorSettings($settings) { |
| 129 | $user = wp_get_current_user(); |
| 130 | |
| 131 | if (is_object($user) && isset($user->roles)) { |
| 132 | $current_user_editors = []; |
| 133 | foreach ($user->roles as $user_role) { |
| 134 | //get role option |
| 135 | $role_option = get_option("pp_capabilities_{$user_role}_role_option", []); |
| 136 | if (is_array($role_option) && !empty($role_option) && !empty($role_option['role_editor'])) { |
| 137 | $current_user_editors = array_merge($current_user_editors, $role_option['role_editor']); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | if (!empty($current_user_editors)) { |
| 142 | $current_user_editors = array_unique($current_user_editors); |
| 143 | $settings = []; |
| 144 | $settings['editor'] = ($current_user_editors[0] === 'classic_editor') ? 'classic' : 'block'; |
| 145 | $settings['allow-users'] = count($current_user_editors) > 1 ? true : false; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | return $settings; |
| 150 | } |
| 151 | |
| 152 | public function filterCodeEditingStatus($settings) { |
| 153 | $user = wp_get_current_user(); |
| 154 | |
| 155 | if (is_object($user) && isset($user->roles)) { |
| 156 | foreach ($user->roles as $user_role) { |
| 157 | //get role option |
| 158 | $role_option = get_option("pp_capabilities_{$user_role}_role_option", []); |
| 159 | if (is_array($role_option) && !empty($role_option) && !empty($role_option['disable_code_editor']) && (int)$role_option['disable_code_editor'] > 0) { |
| 160 | $settings['codeEditingEnabled'] = false; |
| 161 | break; |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | return $settings; |
| 167 | } |
| 168 | |
| 169 | public function fltEditorFeaturesPostTypes($def_post_types) { |
| 170 | if((int)get_option('cme_editor_features_private_post_type') > 0 || defined('PP_CAPABILITIES_PRIVATE_TYPES')){ |
| 171 | $private_cpt = get_post_types(['public' => true, 'show_ui' => true], 'names', 'or'); |
| 172 | $public_cpt = get_post_types(['public' => true, 'show_ui' => true], 'names', 'or'); |
| 173 | $def_post_types = array_unique(array_merge($def_post_types, $private_cpt, $public_cpt)); |
| 174 | }else{ |
| 175 | $def_post_types = array_merge($def_post_types, get_post_types(['public' => true], 'names')); |
| 176 | } |
| 177 | |
| 178 | unset($def_post_types['attachment']); |
| 179 | |
| 180 | if ((count($def_post_types) > 14) && !defined('PP_CAPABILITIES_UNLIMITED_FEATURE_TYPES')) { |
| 181 | $custom_types = array_diff($def_post_types, ['post', 'page']); |
| 182 | $def_post_types = array_merge(['post', 'page'], array_slice($custom_types, 0, 12)); |
| 183 | } |
| 184 | |
| 185 | return $def_post_types; |
| 186 | } |
| 187 | |
| 188 | public function shouldDisplayBanner() { |
| 189 | global $pagenow; |
| 190 | |
| 191 | return ($pagenow == 'admin.php') && isset($_GET['page']) && in_array($_GET['page'], ['pp-capabilities', 'pp-capabilities-roles', 'pp-capabilities-backup']); |
| 192 | } |
| 193 | |
| 194 | private function applyFeatureRestrictions($editor = 'gutenberg') { |
| 195 | global $pagenow; |
| 196 | |
| 197 | if (is_multisite() && is_super_admin() && !defined('PP_CAPABILITIES_RESTRICT_SUPER_ADMIN')) { |
| 198 | return; |
| 199 | } |
| 200 | |
| 201 | // Return if not a post editor request |
| 202 | if (!in_array($pagenow, ['post.php', 'post-new.php'])) { |
| 203 | return; |
| 204 | } |
| 205 | |
| 206 | static $def_post_types; // avoid redundant filter application |
| 207 | |
| 208 | if (!isset($def_post_types)) { |
| 209 | $def_post_types = array_unique(apply_filters('pp_capabilities_feature_post_types', ['post', 'page'])); |
| 210 | } |
| 211 | |
| 212 | $post_type = pp_capabilities_get_post_type(); |
| 213 | |
| 214 | // Return if not a supported post type |
| 215 | if (in_array($post_type, apply_filters('pp_capabilities_unsupported_post_types', ['attachment']))) { |
| 216 | return; |
| 217 | } |
| 218 | |
| 219 | switch ($editor) { |
| 220 | case 'gutenberg': |
| 221 | if (_pp_capabilities_is_block_editor_active()) { |
| 222 | require_once ( dirname(CME_FILE) . '/includes/features/restrict-editor-features.php' ); |
| 223 | PP_Capabilities_Post_Features::applyRestrictions($post_type); |
| 224 | } |
| 225 | |
| 226 | break; |
| 227 | |
| 228 | case 'classic': |
| 229 | if (!_pp_capabilities_is_block_editor_active()) { |
| 230 | require_once ( dirname(CME_FILE) . '/includes/features/restrict-editor-features.php' ); |
| 231 | PP_Capabilities_Post_Features::adminInitClassic($post_type); |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | function featureRestrictionsGutenberg() { |
| 237 | $this->applyFeatureRestrictions(); |
| 238 | } |
| 239 | |
| 240 | function featureRestrictionsClassic() { |
| 241 | $this->applyFeatureRestrictions('classic'); |
| 242 | } |
| 243 | |
| 244 | function adminScripts() { |
| 245 | global $publishpress; |
| 246 | |
| 247 | if (function_exists('get_current_screen') && (!defined('PUBLISHPRESS_VERSION') || empty($publishpress) || empty($publishpress->modules) || empty($publishpress->modules->roles))) { |
| 248 | $screen = get_current_screen(); |
| 249 | |
| 250 | if ('user-edit' === $screen->base || ('user' === $screen->base && 'add' === $screen->action && (defined('PP_CAPABILITIES_ADD_USER_MULTI_ROLES') || get_option('cme_capabilities_add_user_multi_roles')))) { |
| 251 | // Check if we are on the user's profile page |
| 252 | wp_enqueue_script( |
| 253 | 'pp-capabilities-chosen-js', |
| 254 | plugin_dir_url(CME_FILE) . 'common/libs/chosen-v1.8.7/chosen.jquery.js', |
| 255 | ['jquery'], |
| 256 | CAPSMAN_VERSION |
| 257 | ); |
| 258 | |
| 259 | wp_enqueue_script( |
| 260 | 'pp-capabilities-jquery-ui', |
| 261 | plugin_dir_url(CME_FILE) . 'common/libs/jquery/jquery-ui.js', |
| 262 | ['jquery'], |
| 263 | CAPSMAN_VERSION |
| 264 | ); |
| 265 | |
| 266 | wp_enqueue_script( |
| 267 | 'pp-capabilities-roles-profile-js', |
| 268 | plugin_dir_url(CME_FILE) . 'common/js/profile.js', |
| 269 | ['jquery', 'pp-capabilities-chosen-js'], |
| 270 | CAPSMAN_VERSION |
| 271 | ); |
| 272 | |
| 273 | wp_enqueue_style( |
| 274 | 'pp-capabilities-chosen-css', |
| 275 | plugin_dir_url(CME_FILE) . 'common/libs/chosen-v1.8.7/chosen.css', |
| 276 | false, |
| 277 | CAPSMAN_VERSION |
| 278 | ); |
| 279 | wp_enqueue_style( |
| 280 | 'pp-capabilities-roles-profile-css', |
| 281 | plugin_dir_url(CME_FILE) . 'common/css/profile.css', |
| 282 | ['pp-capabilities-chosen-css'], |
| 283 | CAPSMAN_VERSION |
| 284 | ); |
| 285 | |
| 286 | $roles = !empty($_GET['user_id']) ? $this->getUsersRoles((int) $_GET['user_id']) : []; |
| 287 | |
| 288 | if (empty($roles)) { |
| 289 | $roles = (array) get_option('default_role'); |
| 290 | } |
| 291 | |
| 292 | wp_localize_script( |
| 293 | 'pp-capabilities-roles-profile-js', |
| 294 | 'ppCapabilitiesProfileData', |
| 295 | [ |
| 296 | 'role_description' => esc_html__('Drag multiple roles selection to change order.', 'capsman-enhanced'), |
| 297 | 'selected_roles' => $roles |
| 298 | ] |
| 299 | ); |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | function adminPrintScripts() { |
| 305 | |
| 306 | |
| 307 | /** |
| 308 | * Update capabilities top level slug from roles to capabilities |
| 309 | */ |
| 310 | $menu_inline_script = " |
| 311 | jQuery(document).ready( function($) { |
| 312 | if (jQuery('li#toplevel_page_pp-capabilities-roles a.toplevel_page_pp-capabilities-roles').length > 0) { |
| 313 | var toplevel_page = jQuery('li#toplevel_page_pp-capabilities-roles a.toplevel_page_pp-capabilities-roles'); |
| 314 | var toplevel_page_link = toplevel_page.attr('href'); |
| 315 | if (toplevel_page_link) { |
| 316 | toplevel_page.attr('href', toplevel_page_link.replace('pp-capabilities-roles', 'pp-capabilities')); |
| 317 | } |
| 318 | } |
| 319 | });"; |
| 320 | ppc_add_inline_script($menu_inline_script); |
| 321 | |
| 322 | // Counteract overzealous menu icon styling in PublishPress <= 3.2.0 :) |
| 323 | if (defined('PUBLISHPRESS_VERSION') && version_compare(constant('PUBLISHPRESS_VERSION'), '3.2.0', '<=') && defined('PP_CAPABILITIES_FIX_ADMIN_ICON')):?> |
| 324 | <style type="text/css"> |
| 325 | #toplevel_page_pp-capabilities-roles .dashicons-before::before, #toplevel_page_pp-capabilities-roles .wp-has-current-submenu .dashicons-before::before { |
| 326 | background-image: inherit !important; |
| 327 | content: "\f112" !important; |
| 328 | } |
| 329 | </style> |
| 330 | <?php endif; |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | * Returns a list of roles with name and display name to populate a select field. |
| 335 | * |
| 336 | * @param int $userId |
| 337 | * |
| 338 | * @return array |
| 339 | */ |
| 340 | protected function getUsersRoles($userId) |
| 341 | { |
| 342 | if (empty($userId)) { |
| 343 | return []; |
| 344 | } |
| 345 | |
| 346 | $user = get_user_by('id', $userId); |
| 347 | |
| 348 | if (empty($user)) { |
| 349 | return []; |
| 350 | } |
| 351 | |
| 352 | return $user->roles; |
| 353 | } |
| 354 | |
| 355 | public function action_profile_update($userId, $oldUserData = []) |
| 356 | { |
| 357 | // Check if we need to update the user's roles, allowing to set multiple roles. |
| 358 | if ((!empty($_REQUEST['_wpnonce']) && wp_verify_nonce(sanitize_key($_REQUEST['_wpnonce']), 'update-user_' . $userId) |
| 359 | || !empty($_REQUEST['_wpnonce_create-user']) && wp_verify_nonce(sanitize_key($_REQUEST['_wpnonce_create-user']), 'create-user')) |
| 360 | && isset($_POST['pp_roles']) && current_user_can('promote_users')) { |
| 361 | // Remove the user's roles |
| 362 | $user = get_user_by('ID', $userId); |
| 363 | |
| 364 | $newRoles = array_map('sanitize_key', $_POST['pp_roles']); |
| 365 | $currentRoles = $user->roles; |
| 366 | |
| 367 | if (empty($newRoles) || !is_array($newRoles)) { |
| 368 | return; |
| 369 | } |
| 370 | |
| 371 | // Remove all roles |
| 372 | foreach ($currentRoles as $role) { |
| 373 | // Check if it is a bbPress rule. If so, don't remove it. |
| 374 | $isBBPressRole = preg_match('/^bbp_/', $role); |
| 375 | |
| 376 | if (!$isBBPressRole) { |
| 377 | $user->remove_role($role); |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | // Add new roles in order |
| 382 | foreach ($newRoles as $role) { |
| 383 | $user->add_role($role); |
| 384 | } |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | |
| 389 | // perf enhancement: display submenu links without loading framework and plugin code |
| 390 | function cmeSubmenus() { |
| 391 | // First we check if user is administrator and can 'manage_capabilities'. |
| 392 | if (current_user_can('administrator') && ! current_user_can('manage_capabilities')) { |
| 393 | if ($admin = get_role('administrator')) { |
| 394 | $admin->add_cap('manage_capabilities'); |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | $cap_name = (is_multisite() && is_super_admin()) ? 'read' : 'manage_capabilities'; |
| 399 | |
| 400 | $permissions_title = __('Capabilities', 'capsman-enhanced'); |
| 401 | |
| 402 | $menu_order = 72; |
| 403 | |
| 404 | if (defined('PUBLISHPRESS_PERMISSIONS_MENU_GROUPING')) { |
| 405 | foreach ((array)get_option('active_plugins') as $plugin_file) { |
| 406 | if ( false !== strpos($plugin_file, 'publishpress.php') ) { |
| 407 | $menu_order = 27; |
| 408 | } |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | add_menu_page( |
| 413 | $permissions_title, |
| 414 | $permissions_title, |
| 415 | $cap_name, |
| 416 | 'pp-capabilities-roles', |
| 417 | 'cme_fakefunc', |
| 418 | 'dashicons-admin-network', |
| 419 | $menu_order |
| 420 | ); |
| 421 | |
| 422 | add_submenu_page('pp-capabilities-roles', __('Roles', 'capsman-enhanced'), __('Roles', 'capsman-enhanced'), $cap_name, 'pp-capabilities-roles', 'cme_fakefunc'); |
| 423 | add_submenu_page('pp-capabilities-roles', $permissions_title, $permissions_title, $cap_name, 'pp-capabilities', 'cme_fakefunc'); |
| 424 | add_submenu_page('pp-capabilities-roles', __('Editor Features', 'capsman-enhanced'), __('Editor Features', 'capsman-enhanced'), $cap_name, 'pp-capabilities-editor-features', 'cme_fakefunc'); |
| 425 | add_submenu_page('pp-capabilities-roles', __('Admin Features', 'capsman-enhanced'), __('Admin Features', 'capsman-enhanced'), $cap_name, 'pp-capabilities-admin-features', 'cme_fakefunc'); |
| 426 | add_submenu_page('pp-capabilities-roles', __('Admin Menus', 'capsman-enhanced'), __('Admin Menus', 'capsman-enhanced'), $cap_name, 'pp-capabilities-admin-menus', 'cme_fakefunc'); |
| 427 | add_submenu_page('pp-capabilities-roles', __('Nav Menus', 'capsman-enhanced'), __('Nav Menus', 'capsman-enhanced'), $cap_name, 'pp-capabilities-nav-menus', 'cme_fakefunc'); |
| 428 | add_submenu_page('pp-capabilities-roles', __('Backup', 'capsman-enhanced'), __('Backup', 'capsman-enhanced'), $cap_name, 'pp-capabilities-backup', 'cme_fakefunc'); |
| 429 | add_submenu_page('pp-capabilities-roles', __('Settings', 'capsman-enhanced'), __('Settings', 'capsman-enhanced'), $cap_name, 'pp-capabilities-settings', 'cme_fakefunc'); |
| 430 | |
| 431 | if (!defined('PUBLISHPRESS_CAPS_PRO_VERSION')) { |
| 432 | add_submenu_page( |
| 433 | 'pp-capabilities-roles', |
| 434 | __('Upgrade to Pro', 'capsman-enhanced'), |
| 435 | __('Upgrade to Pro', 'capsman-enhanced'), |
| 436 | 'manage_capabilities', |
| 437 | 'capsman-enhanced', |
| 438 | 'cme_fakefunc' |
| 439 | ); |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | |
| 444 | public function settingsUI() { |
| 445 | require_once(dirname(__FILE__).'/settings-ui.php'); |
| 446 | new Capabilities_Settings_UI(); |
| 447 | } |
| 448 | } |
| 449 |