<?php /* * Copyright (c) 2023 LatePoint LLC. All rights reserved. */ class OsRolesHelper { public static array $capabilities_for_controllers; public static function get_capabilities_for_all_controllers(){ if(isset(self::$capabilities_for_controllers)) return self::$capabilities_for_controllers; $capabilities = include(LATEPOINT_CONFIG_ABSPATH . 'capabilities_for_controllers.php'); /** * Get array of capabilities for all controllers * * @since 4.7.0 * @hook latepoint_capabilities_for_controllers * * @param {array} $capabilities array of controllers with their default and per action capablities * @returns {array} The filtered array of controllers with capability information */ self::$capabilities_for_controllers = apply_filters('latepoint_capabilities_for_controllers', $capabilities); return self::$capabilities_for_controllers; } public static function build_capability_name_from_model_action(string $model_class, string $action): string{ $capability_names_for_models = [ 'OsActivityModel' => 'activity', 'OsAgentModel' => 'agent', 'OsAgentMetaModel' => 'agent', 'OsOrderModel' => 'booking', 'OsOrderIntentModel' => 'booking', 'OsOrderMetaModel' => 'booking', 'OsBookingModel' => 'booking', 'OsBookingMetaModel' => 'booking', 'OsConnectorModel' => 'connection', 'OsCustomerModel' => 'customer', 'OsCustomerMetaModel' => 'customer', 'OsLocationModel' => 'location', 'OsLocationCategoryModel' => 'location', 'OsServiceModel' => 'service', 'OsServiceExtraModel' => 'service', 'OsServiceMetaModel' => 'service', 'OsBundleModel' => 'bundle', 'OsServiceCategoryModel' => 'service', 'OsTransactionModel' => 'transaction', 'OsInvoiceModel' => 'invoice', 'OsProcessJobModel' => 'settings', 'OsStepSettingsModel' => 'settings', 'OsProcessModel' => 'settings', 'OsSettingsModel' => 'settings', 'OsWorkPeriodModel' => 'settings', ]; /** * Get array of key => value connections between model name and capability name * * @since 4.7.0 * @hook latepoint_capability_names_for_models * * @param {array} $capability_names_for_models array of key => value pairs of model name => capability name * @returns {array} The filtered array of value pairs */ $capability_names_for_models = apply_filters('latepoint_capability_names_for_models', $capability_names_for_models); if(isset($capability_names_for_models[$model_class])){ $capability = $capability_names_for_models[$model_class].'__'.$action; $all_actions = self::get_all_available_actions_list(); if(in_array($capability, $all_actions)){ return $capability; }else{ $capability = $capability_names_for_models[$model_class].'__edit'; if(in_array($capability, $all_actions)) return $capability; } } return ''; } public static function can_user_make_action_on_model_record(OsModel $model, string $action){ if(OsAuthHelper::get_current_user()->backend_user_type == LATEPOINT_USER_TYPE_ADMIN) return true; // admins are allowed to do everything // check if customer is logged in and tries to edit records that belong to them if(OsAuthHelper::is_customer_logged_in()){ // if it's customer - they can edit their customer record and also their bookings if($model instanceof OsBookingModel && $model->customer_id == OsAuthHelper::get_logged_in_customer_id()){ return true; } if($model instanceof OsCustomerModel && $model->id == OsAuthHelper::get_logged_in_customer_id()){ return true; } } // if customer is not logged in or logged in customer doesn't have rights to access this record, check if backend user has if(self::can_user_perform_model_action(get_class($model), $action)){ switch(get_class($model)){ case 'OsBookingModel': if(OsAuthHelper::get_current_user()->are_all_records_allowed('agent')) return true; $allowed_ids = OsAuthHelper::get_current_user()->get_allowed_records('agent'); if($allowed_ids && in_array($model->agent_id, $allowed_ids)) return true; break; case 'OsCustomerModel': // check if this customer has any bookings with allowed agents if(OsAuthHelper::get_current_user()->are_all_records_allowed('agent')) return true; $allowed_ids = OsAuthHelper::get_current_user()->get_allowed_records('agent'); $bookings = new OsBookingModel(); if($allowed_ids){ if($allowed_ids && in_array($model->agent_id, $allowed_ids)) return true; $has_bookings_with_allowed_agents = $bookings->select('id')->where(['agent_id' => $allowed_ids, 'customer_id' => $model->id])->set_limit(1)->get_results(); if(!empty($has_bookings_with_allowed_agents)){ return true; } } break; } } return false; } public static function can_user_perform_model_action(string $model_class, string $action): bool{ $capability = self::build_capability_name_from_model_action($model_class, $action); $can = !empty($capability) ? self::can_user($capability) : true; return $can; } /** * * Get capabilities required to access specific action of the controller * * @param string $controller_name class name of the controller * @param string $action action name * @return array */ public static function get_capabilities_required_for_controller_action(string $controller_name, string $action): array{ $required_capabilities = ['settings__edit']; // default capabilities, if not set on controller/action level $capabilities_for_controllers = self::get_capabilities_for_all_controllers(); if(isset($capabilities_for_controllers[$controller_name])){ // try to get capabilities that are specific for action, if not get default for controller if(isset($capabilities_for_controllers[$controller_name]['per_action'][$action])){ $required_capabilities = $capabilities_for_controllers[$controller_name]['per_action'][$action]; }elseif(isset($capabilities_for_controllers[$controller_name]['default'])){ $required_capabilities = $capabilities_for_controllers[$controller_name]['default']; } } /** * Get array of capabilities required to access controller's action * * @since 4.7.0 * @hook latepoint_get_capabilities_for_controller_action * * @param {array} $required_capabilities array of required capabilities * @returns {array} The filtered array of required capabilities */ return apply_filters('latepoint_get_capabilities_for_controller_action', $required_capabilities, $controller_name, $action); } public static function save_capabilities_list_for_agent_role(array $capabilities){ return OsSettingsHelper::save_setting_by_name('agent_role_capabilities', wp_json_encode($capabilities)); } public static function get_capabilities_list_for_agent_role(){ $capabilities = json_decode(OsSettingsHelper::get_settings_value('agent_role_capabilities', ''), true); if(empty($capabilities)) $capabilities = self::get_default_capabilities_list_for_agent_role(); /** * Get array of permitted actions for agent role * * @since 4.7.0 * @hook latepoint_get_capabilities_list_for_agent_role * * @param {array} $capabilities array of actions permitted to admin user type * @returns {array} The filtered array of permitted actions */ return apply_filters('latepoint_get_capabilities_list_for_agent_role', $capabilities); } public static function get_all_available_actions_list_grouped(){ $actions = self::get_all_available_actions_list(); $groups = []; foreach($actions as $action){ $object = explode('__', $action); $groups[$object[0]][] = $object[1]; } return $groups; } public static function save_from_params(array $role_params){ $role = \LatePoint\Misc\Role::generate_from_params($role_params); if(empty($role->wp_role)) return new WP_Error('invalid_wp_role', __('WP Role invalid', 'latepoint')); switch($role->user_type){ case LATEPOINT_USER_TYPE_AGENT: if(self::save_capabilities_list_for_agent_role($role->get_capabilities())){ return true; }else{ return new WP_Error('error_saving_role', __('WP Agent role can not be saved', 'latepoint')); } break; case LATEPOINT_USER_TYPE_CUSTOM: $roles = self::get_custom_roles(); $roles[$role->wp_role] = $role->as_array_to_save(); if(OsSettingsHelper::save_setting_by_name('custom_roles', wp_json_encode($roles))){ // register in WP (it's ok to register it on every save, we remove it first and then add, to make sure display name is updated) $role->register_in_wp(); return true; }else{ return new WP_Error('error_saving_role', __('WP Role can not be saved', 'latepoint')); } break; default: return new WP_Error('invalid_role_type', __('Invalid role type', 'latepoint')); break; } } public static function get_wp_roles_list($remove_admins = true) : array{ // this makes sure that the default customer role is created, before showing a list of roles OsSettingsHelper::get_default_wp_role_for_new_customers(); global $wp_roles; if (!isset($wp_roles)) { $wp_roles = new WP_Roles(); } $roles = $wp_roles->get_names(); if($remove_admins){ unset($roles['administrator']); unset($roles['editor']); unset($roles['shop_manager']); unset($roles['sc_shop_manager']); } /** * List of WP roles that you can assign to a new customer * * @since 5.2.0 * @hook latepoint_wp_roles_list_for_customer * * @param {array} $roles list of roles in array * @param {array} $remove_admins whether to remove admin roles or not * @returns {array} The filtered list of roles */ return apply_filters('latepoint_wp_roles_list_for_customer', $roles, $remove_admins); } public static function register_customer_role(){ // Register Customer Role add_role( LATEPOINT_WP_CUSTOMER_ROLE, __('LatePoint Customer', 'latepoint'), array( 'read' => true ) ); } public static function register_roles_in_wp(){ // Register Agent Role $role = \LatePoint\Misc\Role::get_from_wp_role(LATEPOINT_WP_AGENT_ROLE); $role->register_in_wp(); self::register_customer_role(); // Register Custom Roles $custom_roles = self::get_custom_roles(true); if($custom_roles){ foreach($custom_roles as $custom_role){ $custom_role->register_in_wp(); } } } public static function get_model_types_for_allowed_records(){ return ['agent', 'service', 'location']; } public static function generate_role_id(){ return 'role_'.OsUtilHelper::random_text('alnum', 8); } public static function get_custom_roles($as_objects = false): array{ $roles = json_decode(\OsSettingsHelper::get_settings_value('custom_roles', ''), true) ?? []; if($as_objects){ $roles_objects = []; foreach($roles as $role_params){ $roles_objects[$role_params['wp_role']] = \LatePoint\Misc\Role::generate_from_params($role_params); } return $roles_objects; }else{ return $roles; } } public static function save_custom_roles(array $roles){ return \OsSettingsHelper::save_setting_by_name('custom_roles', wp_json_encode($roles)); } public static function delete(string $wp_role): bool{ // can't delete default roles (admin and agent) if(in_array($wp_role, [LATEPOINT_WP_ADMIN_ROLE, LATEPOINT_WP_AGENT_ROLE])) return false; // get all custom roles to make sure the one that needs to be deleted is custom role $custom_roles = self::get_custom_roles(); if(isset($custom_roles[$wp_role])){ unset($custom_roles[$wp_role]); self::save_custom_roles($custom_roles); remove_role($wp_role); } return true; } public static function get_default_roles(): array{ $roles = []; $roles[] = new \LatePoint\Misc\Role(LATEPOINT_USER_TYPE_ADMIN); $roles[] = new \LatePoint\Misc\Role(LATEPOINT_USER_TYPE_AGENT); return $roles; } public static function description_for_action($action_code){ $action_descriptions = [ 'resource_schedule' => __('Edit custom schedule of individual agent, location or service.', 'latepoint'), 'settings' => __('Access to all settings pages, including general schedule and booking steps.', 'latepoint'), 'connection' => __('Ability to connect agents to services and locations.', 'latepoint'), 'chat' => __('Ability to send messages to customers (available with chat addon).', 'latepoint'), ]; /** * List of detailed descriptions for available actions * * @since 4.7.2 * @hook latepoint_roles_action_descriptions * * @param {array} $action_descriptions Array of descriptions for available actions * @param {string} $action_code Programmatic code of the action being filtered * @returns {array} The filtered array of action descriptions */ $action_descriptions = apply_filters('latepoint_roles_action_descriptions', $action_descriptions, $action_code); return $action_descriptions[$action_code] ?? ''; } public static function name_for_action($action_code){ $action_names = [ 'chat' => __('Chat', 'latepoint'), 'activity' => __('Activity Logs', 'latepoint'), 'agent' => __('Agents', 'latepoint'), 'service' => __('Services', 'latepoint'), 'bundle' => __('Bundle', 'latepoint'), 'location' => __('Locations', 'latepoint'), 'booking' => __('Bookings & Orders', 'latepoint'), 'customer' => __('Customers', 'latepoint'), 'transaction' => __('Payments', 'latepoint'), 'invoice' => __('Invoice', 'latepoint'), 'order' => __('Order', 'latepoint'), 'resource_schedule' => __('Resource Schedules', 'latepoint'), 'settings' => __('Settings', 'latepoint'), 'connection' => __('Connections', 'latepoint'), 'edit' => __('Edit', 'latepoint'), 'delete' => __('Delete', 'latepoint'), 'view' => __('View', 'latepoint'), 'create' => __('Create', 'latepoint'), ]; /** * List of human-friendly names for available actions * * @since 4.7.2 * @hook latepoint_roles_action_names * * @param {array} $action_names Array of names for available actions * @param {string} $action_code Programmatic code of the action being filtered * @returns {array} The filtered array of action names */ $action_names = apply_filters('latepoint_roles_action_names', $action_names, $action_code); return $action_names[$action_code] ?? $action_code; } public static function get_default_capabilities_list_for_agent_role(){ $capabilities = [ 'agent__view', 'agent__edit' , 'booking__view', 'booking__delete' ,'booking__create', 'booking__edit', 'customer__view', 'customer__delete' ,'customer__create', 'customer__edit', 'transaction__view', 'transaction__delete' ,'transaction__create', 'transaction__edit', 'invoice__view', 'invoice__delete' ,'invoice__create', 'invoice__edit', 'activity__view', 'activity__delete' ,'activity__create', 'activity__edit', 'chat__edit', 'resource_schedule__edit' ]; /** * Default list of permitted actions available for agent user type * * @since 4.7.0 * @hook latepoint_roles_get_default_capabilities_list_for_agent_role * * @param {array} $capabilities array of permitted actions available to agent user type by default * @returns {array} The filtered array of permitted actions */ return apply_filters('latepoint_roles_get_default_capabilities_list_for_agent_role', $capabilities); } public static function get_all_available_actions_list(){ $actions = [ 'agent__view', 'agent__delete' ,'agent__create', 'agent__edit' , 'service__view', 'service__delete' ,'service__create', 'service__edit' , 'bundle__view', 'bundle__delete' ,'bundle__create', 'bundle__edit' , 'location__view', 'location__delete' ,'location__create', 'location__edit' , 'service_extra__view', 'service_extra__delete' ,'service_extra__create', 'service_extra__edit' , 'booking__view', 'booking__delete' ,'booking__create', 'booking__edit' , 'customer__view', 'customer__delete' ,'customer__create', 'customer__edit' , 'transaction__view', 'transaction__delete' ,'transaction__create', 'transaction__edit' , 'invoice__view', 'invoice__delete' ,'invoice__create', 'invoice__edit' , 'activity__view', 'activity__delete' ,'activity__create', 'activity__edit' , 'chat__edit', 'resource_schedule__edit', 'settings__edit', 'connection__edit' ]; /** * All available actions to be attached to a user role * * @since 4.7.0 * @hook latepoint_roles_get_all_available_actions_list * * @param {array} $actions array of actions that can be attached to a user role * @returns {array} The filtered array of actions */ return apply_filters('latepoint_roles_get_all_available_actions_list', $actions); } /** * @param \LatePoint\Misc\Role $role * @return \LatePoint\Misc\User[] */ public static function get_users_for_role(\LatePoint\Misc\Role $role): array{ $users = []; $wp_users = []; switch($role->user_type){ case LATEPOINT_USER_TYPE_ADMIN: $wp_users = get_users(['role__in' => LATEPOINT_WP_ADMIN_ROLE]); break; case LATEPOINT_USER_TYPE_AGENT: $wp_users = get_users(['role' => LATEPOINT_WP_AGENT_ROLE]); break; case LATEPOINT_USER_TYPE_CUSTOM: if($role->wp_role) $wp_users = get_users(['role' => $role->wp_role]); break; } foreach($wp_users as $wp_user){ $users[] = \LatePoint\Misc\User::load_from_wp_user($wp_user); } return $users; } public static function get_user_roles($user): array { return []; } /** * Checks if currently logged in user has certain capabilities * * @param array|string $capabilities array or string of capabilities that you want to check if logged in user has or not * @return bool */ public static function can_user($capabilities) :bool{ if (OsAuthHelper::get_current_user()) { return OsAuthHelper::get_current_user()->has_capability( $capabilities ); } return false; } public static function get_allowed_records(string $model_type, $load_from_db = false){ return OsAuthHelper::get_current_user()->get_allowed_records($model_type, $load_from_db); } public static function are_all_records_allowed(string $model_type = '', $load_from_db = false): bool{ return OsAuthHelper::get_current_user()->are_all_records_allowed($model_type, $load_from_db); } /* * Pass filter object or an array of arguments for a query to be filtered based on what logged in user is allowed to access */ public static function filter_allowed_records_from_arguments_or_filter($args_or_filter){ $model_types = ['agent', 'location', 'service']; foreach($model_types as $model_type){ if(!OsAuthHelper::get_current_user()->are_all_records_allowed($model_type)){ $prop = $model_type.'_id'; // get value that needs to be filtered by allowed records $value = ($args_or_filter instanceof \LatePoint\Misc\Filter) ? $args_or_filter->$prop ?? [] : $args_or_filter[$prop] ?? []; if(empty($value)){ // no value is set - limit it to allowed records $value = OsAuthHelper::get_current_user()->get_allowed_records($model_type); }else{ // value is set - make sure it's in the allowed records list, if not - set to allowed records $allowed_from_set = array_intersect(is_array($value) ? $value : [$value], OsAuthHelper::get_current_user()->get_allowed_records($model_type)); $value = empty($allowed_from_set) ? OsAuthHelper::get_current_user()->get_allowed_records($model_type) : $allowed_from_set; } if($args_or_filter instanceof \LatePoint\Misc\Filter){ $args_or_filter->$prop = $value; }elseif(is_array($args_or_filter)){ $args_or_filter[$prop] = $value; } } } return $args_or_filter; } }