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