activities_controller.php
1 month ago
auth_controller.php
3 months ago
booking_form_settings_controller.php
3 months ago
bookings_controller.php
12 hours ago
calendars_controller.php
3 months ago
carts_controller.php
12 hours ago
controller.php
3 months ago
customer_cabinet_controller.php
2 months ago
customers_controller.php
12 hours ago
dashboard_controller.php
2 months ago
default_agent_controller.php
3 months ago
events_controller.php
3 months ago
form_fields_controller.php
1 week ago
integrations_controller.php
3 months ago
invoices_controller.php
12 hours ago
manage_booking_by_key_controller.php
3 months ago
manage_order_by_key_controller.php
3 months ago
notifications_controller.php
3 months ago
orders_controller.php
12 hours ago
pro_controller.php
2 weeks ago
process_jobs_controller.php
3 months ago
processes_controller.php
1 month ago
razorpay_connect_controller.php
1 week ago
search_controller.php
3 months ago
services_controller.php
3 months ago
settings_controller.php
2 months ago
steps_controller.php
2 weeks ago
stripe_connect_controller.php
1 week ago
support_topics_controller.php
3 months ago
todos_controller.php
3 months ago
transactions_controller.php
12 hours ago
wizard_controller.php
1 week ago
controller.php
234 lines
| 1 | <?php |
| 2 | class OsController { |
| 3 | |
| 4 | protected $params, |
| 5 | $files, |
| 6 | $layout = 'admin', |
| 7 | $views_folder = LATEPOINT_VIEWS_ABSPATH_SHARED, |
| 8 | $return_format = 'html', |
| 9 | $extra_css_classes = [ 'latepoint' ]; |
| 10 | public array $fields_to_update = []; |
| 11 | |
| 12 | // if an action can only be accessed by a backend user, we need to define capabilities that are required |
| 13 | public array $controller_capabilities = [ 'settings__edit' ]; // default for controller |
| 14 | public array $action_capabilities = []; // per action |
| 15 | |
| 16 | public array $action_access = [ |
| 17 | 'customer' => [], |
| 18 | 'public' => [], |
| 19 | ]; |
| 20 | |
| 21 | public $vars; |
| 22 | public $route_name; |
| 23 | |
| 24 | |
| 25 | |
| 26 | function __construct() { |
| 27 | $this->params = $this->get_params(); |
| 28 | $this->files = $this->get_files(); |
| 29 | $this->set_layout( $this->layout ); |
| 30 | $this->vars['page_header'] = __( 'Bookings', 'latepoint' ); |
| 31 | $this->vars['breadcrumbs'][] = array( |
| 32 | 'label' => __( 'Dashboard', 'latepoint' ), |
| 33 | 'link' => OsRouterHelper::build_link( [ 'dashboard', 'index' ] ), |
| 34 | ); |
| 35 | |
| 36 | $this->load_settings(); |
| 37 | $this->vars['logged_in_customer'] = OsAuthHelper::get_logged_in_customer(); |
| 38 | } |
| 39 | |
| 40 | public function check_nonce( $action, $custom_nonce = '' ) { |
| 41 | $nonce = ! empty( $custom_nonce ) ? $custom_nonce : $this->params['_wpnonce']; |
| 42 | if ( ! wp_verify_nonce( $nonce, $action ) ) { |
| 43 | if ( $this->get_return_format() == 'json' ) { |
| 44 | $this->send_json( |
| 45 | array( |
| 46 | 'status' => LATEPOINT_STATUS_ERROR, |
| 47 | 'message' => __( 'Invalid Request', 'latepoint' ), |
| 48 | ) |
| 49 | ); |
| 50 | } else { |
| 51 | wp_die(); |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | public function can_current_user_access_action( string $action ): bool { |
| 57 | if ( in_array( $action, $this->action_access['public'] ) ) { |
| 58 | // public route |
| 59 | $can = true; |
| 60 | } elseif ( in_array( $action, $this->action_access['customer'] ) && OsAuthHelper::get_current_user()->customer ) { |
| 61 | // customer route & customer is logged in |
| 62 | $can = true; |
| 63 | } else { |
| 64 | // backend route, check for capabilities |
| 65 | $can = OsAuthHelper::get_current_user()->has_capability( $this->get_capabilities_required_for_action( $action ) ); |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Determines if a currently logged in user can access controller's action |
| 70 | * |
| 71 | * @since 4.7.0 |
| 72 | * @hook latepoint_can_current_user_access_action |
| 73 | * |
| 74 | * @param {bool} $can Decision true|false |
| 75 | * @param {string} $action Name of the action that is being called |
| 76 | * @param {LatePoint\Misc\User} $current_user Currently logged in latepoint user |
| 77 | * @returns {bool} Decision true|false |
| 78 | */ |
| 79 | return apply_filters( 'latepoint_can_current_user_access_action', $can, $action, OsAuthHelper::get_current_user() ); |
| 80 | } |
| 81 | |
| 82 | public function get_capabilities_required_for_action( $action ) { |
| 83 | return OsRolesHelper::get_capabilities_required_for_controller_action( get_class( $this ), $action ); |
| 84 | } |
| 85 | |
| 86 | function generate_css_class( $view_name ) { |
| 87 | $class_name_filtered = strtolower( preg_replace( '/^Os(\w+)Controller/i', '$1', static::class ) ); |
| 88 | return "latepoint-view-{$class_name_filtered}-{$view_name}"; |
| 89 | } |
| 90 | |
| 91 | protected function load_settings() { |
| 92 | } |
| 93 | |
| 94 | |
| 95 | public function access_not_allowed() { |
| 96 | $this->format_render( __FUNCTION__, [], [], true ); |
| 97 | exit(); |
| 98 | } |
| 99 | |
| 100 | function format_render( $view_name, $extra_vars = array(), $json_return_vars = array(), $from_shared_folder = false ) { |
| 101 | echo $this->format_render_return( $view_name, $extra_vars, $json_return_vars, $from_shared_folder ); |
| 102 | } |
| 103 | |
| 104 | // You can pass array to $view_name, ['json_view_name' => ..., 'html_view_name' => ...] |
| 105 | function format_render_return( $view_name, $extra_vars = array(), $json_return_vars = array(), $from_shared_folder = false ) { |
| 106 | $html = ''; |
| 107 | if ( $this->get_return_format() == 'json' ) { |
| 108 | if ( is_array( $view_name ) ) { |
| 109 | $view_name = $view_name['json_view_name']; |
| 110 | } |
| 111 | $response_html = $this->render( $this->get_view_uri( $view_name, $from_shared_folder ), 'none', $extra_vars ); |
| 112 | $this->send_json( |
| 113 | array_merge( |
| 114 | array( |
| 115 | 'status' => LATEPOINT_STATUS_SUCCESS, |
| 116 | 'message' => $response_html, |
| 117 | ), |
| 118 | $json_return_vars |
| 119 | ) |
| 120 | ); |
| 121 | } else { |
| 122 | if ( is_array( $view_name ) ) { |
| 123 | $view_name = $view_name['html_view_name']; |
| 124 | } |
| 125 | $this->extra_css_classes[] = $this->generate_css_class( $view_name ); |
| 126 | $this->vars['extra_css_classes'] = $this->extra_css_classes; |
| 127 | $html = $this->render( $this->get_view_uri( $view_name, $from_shared_folder ), $this->get_layout(), $extra_vars ); |
| 128 | } |
| 129 | return $html; |
| 130 | } |
| 131 | |
| 132 | function set_layout( $layout = 'admin' ) { |
| 133 | if ( isset( $this->params['layout'] ) ) { |
| 134 | $this->layout = $this->params['layout']; |
| 135 | } else { |
| 136 | $this->layout = $layout; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | function get_layout() { |
| 141 | return $this->layout; |
| 142 | } |
| 143 | |
| 144 | function set_return_format( $format = 'html' ) { |
| 145 | $this->return_format = $format; |
| 146 | } |
| 147 | |
| 148 | function get_return_format() { |
| 149 | return $this->return_format; |
| 150 | } |
| 151 | |
| 152 | function send_json( $data, $status_code = null ) { |
| 153 | if ( ! empty( $this->fields_to_update ) ) { |
| 154 | $data['fields_to_update'] = $this->fields_to_update; |
| 155 | } |
| 156 | wp_send_json( $data, $status_code ); |
| 157 | } |
| 158 | |
| 159 | function get_view_uri( $view_name, $from_shared_folder = false ) { |
| 160 | if ( $from_shared_folder ) { |
| 161 | $view_uri = LATEPOINT_VIEWS_ABSPATH_SHARED . $view_name . '.php'; |
| 162 | } else { |
| 163 | $view_uri = $this->views_folder . $view_name . '.php'; |
| 164 | } |
| 165 | return $view_uri; |
| 166 | } |
| 167 | |
| 168 | private function get_safe_layout_path( $layout ) { |
| 169 | // 1. Remove any path separators and null bytes |
| 170 | $layout = str_replace( [ '/', '\\', "\0" ], '', $layout ); |
| 171 | |
| 172 | // 2. Remove any dots to prevent directory traversal |
| 173 | $layout = str_replace( '.', '', $layout ); |
| 174 | |
| 175 | // 3. Only allow alphanumeric, underscore, and hyphen |
| 176 | $layout = preg_replace( '/[^a-zA-Z0-9_-]/', '', $layout ); |
| 177 | |
| 178 | // 4. Construct the full path |
| 179 | $layout_file = $this->add_extension( $layout, '.php' ); |
| 180 | $full_path = LATEPOINT_VIEWS_LAYOUTS_ABSPATH . $layout_file; |
| 181 | |
| 182 | // 5. Use realpath to resolve any remaining traversal attempts |
| 183 | $real_path = realpath( $full_path ); |
| 184 | $base_path = realpath( LATEPOINT_VIEWS_LAYOUTS_ABSPATH ); |
| 185 | |
| 186 | // 6. Ensure the resolved path is within the layouts directory |
| 187 | if ( $real_path && $base_path && strpos( $real_path, $base_path ) === 0 ) { |
| 188 | return $real_path; |
| 189 | } |
| 190 | |
| 191 | return false; |
| 192 | } |
| 193 | |
| 194 | // render view and if needed layout, when layout is rendered - view variable is passed to a layout file |
| 195 | function render( $view, $layout = 'none', $extra_vars = array() ) { |
| 196 | $this->vars['route_name'] = $this->route_name; |
| 197 | extract( $extra_vars ); |
| 198 | extract( $this->vars ); |
| 199 | ob_start(); |
| 200 | if ( $layout != 'none' ) { |
| 201 | $layout_path = $this->get_safe_layout_path( $layout ); |
| 202 | // rendering layout, view variable will be passed and used in layout file |
| 203 | if ( $layout_path ) { |
| 204 | include $layout_path; |
| 205 | } else { |
| 206 | __( 'Invalid layout', 'latepoint' ); |
| 207 | } |
| 208 | } else { |
| 209 | include $this->add_extension( $view, '.php' ); |
| 210 | } |
| 211 | $response_html = ob_get_clean(); |
| 212 | return $response_html; |
| 213 | } |
| 214 | |
| 215 | /* |
| 216 | Adds extension to a file string if its missing |
| 217 | */ |
| 218 | function add_extension( $string = '', $extension = '.php' ) { |
| 219 | if ( substr( $string, -strlen( $extension ) ) === $extension ) { |
| 220 | return $string; |
| 221 | } else { |
| 222 | return $string . $extension; |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | function get_files() { |
| 227 | return OsParamsHelper::get_files(); |
| 228 | } |
| 229 | |
| 230 | function get_params() { |
| 231 | return OsParamsHelper::get_params(); |
| 232 | } |
| 233 | } |
| 234 |