Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 154 |
|
0.00% |
0 / 25 |
CRAP | n/a |
0 / 0 |
|
| zeroBS_api_rewrite_endpoint | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_api_process_pagination | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| jpcrm_api_process_search | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| jpcrm_api_process_replace_hyphens_in_json_keys | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| jpcrm_api_process_external_api_name | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| jpcrm_api_invalid_request | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_api_unauthorised_request | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_api_forbidden_request | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_api_invalid_method | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_api_teapot | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_api_check_authentication | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| jpcrm_api_check_http_method | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| zeroBSCRM_API_error | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| zeroBSCRM_API_locate_api_endpoint | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
| zeroBSCRM_API_get_api_endpoint | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
| jpcrm_is_api_request_authorised | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
56 | |||
| zeroBSCRM_getAPIEndpoint | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_generate_api_publishable_key | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_generate_api_secret_key | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| zeroBSCRM_API_api_endpoint | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
30 | |||
| hash_equals | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
| jpcrm_generate_api_creds | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
| zeroBSCRM_getAPIKey | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| zeroBSCRM_getAPISecret | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| jpcrm_api_replace_hyphens_in_json_keys_with_underscores | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | /* |
| 3 | * Jetpack CRM |
| 4 | * https://jetpackcrm.com |
| 5 | * V2.0 |
| 6 | * |
| 7 | * Copyright 2020 Automattic |
| 8 | * |
| 9 | * Date: 05/04/2017 |
| 10 | */ |
| 11 | |
| 12 | /* |
| 13 | ====================================================== |
| 14 | Breaking Checks ( stops direct access ) |
| 15 | ====================================================== */ |
| 16 | if ( ! defined( 'ZEROBSCRM_PATH' ) ) { |
| 17 | exit( 0 ); |
| 18 | } |
| 19 | /* |
| 20 | ====================================================== |
| 21 | / Breaking Checks |
| 22 | ====================================================== */ |
| 23 | |
| 24 | // } We can do this below in the templater or templates? add_action( 'wp_enqueue_scripts', 'zeroBS_portal_enqueue_stuff' ); |
| 25 | // } ... in the end we can just dump the above line into the templates before get_header() - hacky but works |
| 26 | |
| 27 | // Adds the Rewrite Endpoint for the 'clients' area of the CRM. |
| 28 | // } WH - this is dumped here now, because this whole thing is fired just AFTER init (to allow switch/on/off in main ZeroBSCRM.php) |
| 29 | |
| 30 | function zeroBS_api_rewrite_endpoint() { |
| 31 | add_rewrite_endpoint( 'zbs_api', EP_ROOT ); |
| 32 | } |
| 33 | add_action( 'init', 'zeroBS_api_rewrite_endpoint' ); |
| 34 | |
| 35 | /** |
| 36 | * Process the query and get page and items per page |
| 37 | */ |
| 38 | function jpcrm_api_process_pagination() { |
| 39 | // phpcs:disable WordPress.Security.NonceVerification.Recommended |
| 40 | $page = isset( $_GET['page'] ) ? max( (int) $_GET['page'], 1 ) : 1; |
| 41 | $per_page = isset( $_GET['perpage'] ) ? max( (int) $_GET['perpage'], 1 ) : 10; |
| 42 | $order = strtoupper( $_GET['order'] ?? '' ) === 'ASC' ? 'ASC' : 'DESC'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 43 | // phpcs:enable WordPress.Security.NonceVerification.Recommended |
| 44 | |
| 45 | return array( |
| 46 | 'page' => $page, |
| 47 | 'per_page' => $per_page, |
| 48 | 'order' => $order, |
| 49 | ); |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Check and process if there is a search in the query |
| 54 | */ |
| 55 | function jpcrm_api_process_search() { |
| 56 | return ( isset( $_GET['zbs_query'] ) ? sanitize_text_field( $_GET['zbs_query'] ) : '' ); |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * If there is a `replace_hyphens_with_underscores_in_json_keys` parameter in |
| 61 | * the request, it is returned as an int. Otherwise returns 0. |
| 62 | * |
| 63 | * @return int Parameter `replace_hyphens_with_underscores_in_json_keys` from request. 0 if it isn't set. |
| 64 | */ |
| 65 | function jpcrm_api_process_replace_hyphens_in_json_keys() { |
| 66 | return ( isset( $_GET['replace_hyphens_with_underscores_in_json_keys'] ) ? (int) $_GET['replace_hyphens_with_underscores_in_json_keys'] : 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * If there is a `external_api_name` parameter in |
| 71 | * the request, it is returned as a string. Otherwise returns the bool false. |
| 72 | * |
| 73 | * @return string|bool Parameter `external_api_name` from request. Returns false if it isn't set. |
| 74 | */ |
| 75 | function jpcrm_api_process_external_api_name() { |
| 76 | return ( isset( $_GET['external_api_name'] ) ? sanitize_text_field( wp_unslash( $_GET['external_api_name'] ) ) : false ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 77 | } |
| 78 | |
| 79 | /** |
| 80 | * Generate API invalid request error |
| 81 | */ |
| 82 | function jpcrm_api_invalid_request() { |
| 83 | $reply = array( |
| 84 | 'status' => __( 'Bad request', 'zero-bs-crm' ), |
| 85 | 'message' => __( 'The API request was invalid.', 'zero-bs-crm' ), |
| 86 | ); |
| 87 | wp_send_json_error( $reply, 400 ); |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Generate API unauthorised request error |
| 92 | */ |
| 93 | function jpcrm_api_unauthorised_request() { |
| 94 | $reply = array( |
| 95 | 'status' => __( 'Unauthorized', 'zero-bs-crm' ), |
| 96 | 'message' => __( 'Please ensure your Jetpack CRM API key and secret are correctly configured.', 'zero-bs-crm' ), |
| 97 | ); |
| 98 | wp_send_json_error( $reply, 401 ); |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Generate API forbidden request error |
| 103 | */ |
| 104 | function jpcrm_api_forbidden_request() { |
| 105 | $reply = array( |
| 106 | 'status' => __( 'Forbidden', 'zero-bs-crm' ), |
| 107 | 'message' => __( 'You do not have permission to access this resource.', 'zero-bs-crm' ), |
| 108 | ); |
| 109 | wp_send_json_error( $reply, 403 ); |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Generate API invalid method error |
| 114 | */ |
| 115 | function jpcrm_api_invalid_method() { |
| 116 | $reply = array( |
| 117 | 'status' => __( 'Method not allowed', 'zero-bs-crm' ), |
| 118 | 'message' => __( 'Please ensure you are using the proper method (e.g. POST or GET).', 'zero-bs-crm' ), |
| 119 | ); |
| 120 | wp_send_json_error( $reply, 405 ); |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Generate API teapot error |
| 125 | */ |
| 126 | function jpcrm_api_teapot() { |
| 127 | $reply = array( |
| 128 | 'status' => 'I\'m a teapot', |
| 129 | 'message' => 'As per RFC 2324 (section 2.3.2), this response is short and stout.', |
| 130 | ); |
| 131 | wp_send_json_error( $reply, 418 ); |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Check if the request is authorised via the API key/secret |
| 136 | * |
| 137 | * @return bool or error |
| 138 | */ |
| 139 | function jpcrm_api_check_authentication() { |
| 140 | |
| 141 | if ( ! jpcrm_is_api_request_authorised() ) { |
| 142 | jpcrm_api_unauthorised_request(); |
| 143 | } |
| 144 | |
| 145 | return true; |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Check if the request matches the expected HTTP methods |
| 150 | * |
| 151 | * @param array $methods_allowed List of the request HTTP methods (GET, POST, PUSH, DELETE) |
| 152 | * @return bool or error |
| 153 | */ |
| 154 | function jpcrm_api_check_http_method( $methods_allowed = array( 'GET' ) ) { |
| 155 | |
| 156 | if ( ! in_array( $_SERVER['REQUEST_METHOD'], $methods_allowed ) ) { |
| 157 | if ( $_SERVER['REQUEST_METHOD'] == 'BREW' ) { |
| 158 | jpcrm_api_teapot(); |
| 159 | } else { |
| 160 | jpcrm_api_invalid_method(); |
| 161 | } |
| 162 | } |
| 163 | return true; |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Manage an API Error response with the correct headers and encode the data to JSON |
| 168 | * |
| 169 | * @param string $errorMsg |
| 170 | * @param int $headerCode |
| 171 | */ |
| 172 | function zeroBSCRM_API_error( $errorMsg = 'Error', $header_code = 400 ) { |
| 173 | |
| 174 | // } 400 = general error |
| 175 | // } 403 = perms |
| 176 | wp_send_json( array( 'error' => $errorMsg ), $header_code ); |
| 177 | } |
| 178 | |
| 179 | // now to locate the templates... |
| 180 | // http://jeroensormani.com/how-to-add-template-files-in-your-plugin/ |
| 181 | |
| 182 | /** |
| 183 | * Locate template. |
| 184 | * |
| 185 | * Locate the called template. |
| 186 | * Search Order: |
| 187 | * 1. /templates not over-ridable |
| 188 | * |
| 189 | * @since 1.2.7 |
| 190 | * |
| 191 | * @param string $template_name Template to load. |
| 192 | * @param string $string $template_path Path to templates. |
| 193 | * @param string $default_path Default path to template files. |
| 194 | * @return string Path to the template file. |
| 195 | */ |
| 196 | function zeroBSCRM_API_locate_api_endpoint( $template_name, $template_path = '', $default_path = '' ) { |
| 197 | // Set variable to search in zerobscrm-plugin-templates folder of theme. |
| 198 | if ( ! $template_path ) { |
| 199 | $template_path = 'zerobscrm-plugin-templates/'; |
| 200 | } |
| 201 | // Set default plugin templates path. |
| 202 | if ( ! $default_path ) { |
| 203 | $default_path = ZEROBSCRM_PATH . 'api/'; // Path to the template folder |
| 204 | } |
| 205 | // Search template file in theme folder. |
| 206 | $template = locate_template( |
| 207 | array( |
| 208 | $template_path . $template_name, |
| 209 | $template_name, |
| 210 | ) |
| 211 | ); |
| 212 | // Get plugins template file. |
| 213 | if ( ! $template ) { |
| 214 | $template = $default_path . $template_name; |
| 215 | } |
| 216 | return apply_filters( 'zeroBSCRM_API_locate_api_endpoint', $template, $template_name, $template_path, $default_path ); |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Get template. |
| 221 | * |
| 222 | * Search for the template and include the file. |
| 223 | * |
| 224 | * @since 1.2.7 |
| 225 | * |
| 226 | * @see zeroBSCRM_API_get_template() |
| 227 | * |
| 228 | * @param string $template_name Template to load. |
| 229 | * @param array $args Args passed for the template file. |
| 230 | * @param string $string $template_path Path to templates. |
| 231 | * @param string $default_path Default path to template files. |
| 232 | */ |
| 233 | function zeroBSCRM_API_get_api_endpoint( $template_name, $args = array(), $tempate_path = '', $default_path = '' ) { |
| 234 | if ( is_array( $args ) && isset( $args ) ) { |
| 235 | extract( $args ); |
| 236 | } |
| 237 | $template_file = zeroBSCRM_API_locate_api_endpoint( $template_name, $tempate_path, $default_path ); |
| 238 | if ( ! file_exists( $template_file ) ) { |
| 239 | _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $template_file ) ), '1.0.0' ); |
| 240 | return; |
| 241 | } |
| 242 | include $template_file; |
| 243 | } |
| 244 | |
| 245 | // function similar to is_user_logged_in() |
| 246 | function jpcrm_is_api_request_authorised() { |
| 247 | // We should switch authentication method to "headers" not parameters - will be cleaner :) |
| 248 | |
| 249 | // the the API key/secret are currently in the URL |
| 250 | $possible_api_key = isset( $_GET['api_key'] ) ? sanitize_text_field( $_GET['api_key'] ) : ''; |
| 251 | $possible_api_secret = isset( $_GET['api_secret'] ) ? sanitize_text_field( $_GET['api_secret'] ) : ''; |
| 252 | |
| 253 | // a required value is empty, so not authorised |
| 254 | if ( empty( $possible_api_key ) || empty( $possible_api_secret ) ) { |
| 255 | return false; |
| 256 | } |
| 257 | |
| 258 | $api_key = zeroBSCRM_getAPIKey(); |
| 259 | |
| 260 | // provided key doesn't match, so not authorised |
| 261 | if ( ! hash_equals( $possible_api_key, $api_key ) ) { |
| 262 | return false; |
| 263 | } |
| 264 | |
| 265 | global $zbs; |
| 266 | $zbs->load_encryption(); |
| 267 | $hashed_possible_api_secret = $zbs->encryption->hash( $possible_api_secret ); |
| 268 | $hashed_api_secret = zeroBSCRM_getAPISecret(); |
| 269 | |
| 270 | // provided secret doesn't match, so not authorised |
| 271 | if ( ! hash_equals( $hashed_possible_api_secret, $hashed_api_secret ) ) { |
| 272 | return false; |
| 273 | } |
| 274 | |
| 275 | return true; |
| 276 | } |
| 277 | |
| 278 | function zeroBSCRM_getAPIEndpoint() { |
| 279 | return site_url( '/zbs_api/' ); // , 'https' ); |
| 280 | } |
| 281 | |
| 282 | function jpcrm_generate_api_publishable_key() { |
| 283 | global $zbs; |
| 284 | $zbs->load_encryption(); |
| 285 | $api_publishable_key = 'jpcrm_pk_' . $zbs->encryption->get_rand_hex(); |
| 286 | return $api_publishable_key; |
| 287 | } |
| 288 | |
| 289 | function jpcrm_generate_api_secret_key() { |
| 290 | global $zbs; |
| 291 | $zbs->load_encryption(); |
| 292 | $api_secret_key = 'jpcrm_sk_' . $zbs->encryption->get_rand_hex(); |
| 293 | return $api_secret_key; |
| 294 | } |
| 295 | |
| 296 | /* |
| 297 | SAME CODE AS IN PORTAL, BUT REPLACED WITH api_endpoint stuff. Templates (to return the JSON) are in /api/endpoints/ folder |
| 298 | */ |
| 299 | |
| 300 | add_filter( 'template_include', 'zeroBSCRM_API_api_endpoint', 99 ); |
| 301 | |
| 302 | function zeroBSCRM_API_api_endpoint( $template ) { |
| 303 | |
| 304 | $zbsAPIQuery = get_query_var( 'zbs_api', false ); |
| 305 | |
| 306 | // We only want to interfere where zbs_api is set :) |
| 307 | // ... as this is called for ALL page loads |
| 308 | if ( $zbsAPIQuery === false ) { |
| 309 | return $template; |
| 310 | } |
| 311 | |
| 312 | // check if API key/secret are correct, or die with 403 |
| 313 | jpcrm_api_check_authentication(); |
| 314 | |
| 315 | // Break it up if / present |
| 316 | if ( strpos( $zbsAPIQuery, '/' ) ) { |
| 317 | $zbsAPIRequest = explode( '/', $zbsAPIQuery ); |
| 318 | } else { |
| 319 | // no / in it, so must just be a 1 worder like "invoices", here just jam in array so it matches prev exploded req. |
| 320 | $zbsAPIRequest = array( $zbsAPIQuery ); |
| 321 | } |
| 322 | |
| 323 | // no endpoint was specified, so die with 400 |
| 324 | if ( empty( $zbsAPIRequest[0] ) ) { |
| 325 | jpcrm_api_invalid_request(); |
| 326 | } |
| 327 | |
| 328 | // hard-coded valid endpoints; we could at some point do this dynamically |
| 329 | $valid_api_endpoints = array( |
| 330 | 'status', |
| 331 | 'webhook', |
| 332 | 'create_customer', |
| 333 | 'create_transaction', |
| 334 | 'create_event', |
| 335 | 'create_company', |
| 336 | 'customer_search', |
| 337 | 'customers', |
| 338 | 'invoices', |
| 339 | 'quotes', |
| 340 | 'events', |
| 341 | 'companies', |
| 342 | 'transactions', |
| 343 | ); |
| 344 | |
| 345 | // invalid endpoint was specified, so die with 400 |
| 346 | if ( ! in_array( $zbsAPIRequest[0], $valid_api_endpoints ) ) { |
| 347 | jpcrm_api_invalid_request(); |
| 348 | } |
| 349 | |
| 350 | return zeroBSCRM_API_get_api_endpoint( $zbsAPIRequest[0] . '.php' ); |
| 351 | } |
| 352 | |
| 353 | if ( ! function_exists( 'hash_equals' ) ) { |
| 354 | function hash_equals( $str1, $str2 ) { |
| 355 | if ( strlen( $str1 ) != strlen( $str2 ) ) { |
| 356 | return false; |
| 357 | } else { |
| 358 | $res = (string) ( $str1 ^ $str2 ); |
| 359 | $ret = 0; |
| 360 | for ( $i = strlen( $res ) - 1; $i >= 0; $i-- ) { |
| 361 | $ret |= ord( $res[ $i ] ); |
| 362 | } |
| 363 | return ! $ret; |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | // generate new API credentials |
| 369 | function jpcrm_generate_api_creds() { |
| 370 | |
| 371 | global $zbs; |
| 372 | $new_publishable_key = jpcrm_generate_api_publishable_key(); |
| 373 | $zbs->DAL->updateSetting( 'api_key', $new_publishable_key ); |
| 374 | |
| 375 | $new_secret_key = jpcrm_generate_api_secret_key(); |
| 376 | $hashed_api_secret = $zbs->encryption->hash( $new_secret_key ); |
| 377 | $zbs->DAL->updateSetting( 'api_secret', $hashed_api_secret ); |
| 378 | |
| 379 | return array( |
| 380 | 'key' => $new_publishable_key, |
| 381 | 'secret' => $new_secret_key, |
| 382 | ); |
| 383 | } |
| 384 | |
| 385 | // each CRM is only given one API (for now) |
| 386 | function zeroBSCRM_getAPIKey() { |
| 387 | |
| 388 | global $zbs; |
| 389 | return $zbs->DAL->setting( 'api_key' ); |
| 390 | } |
| 391 | |
| 392 | // each CRM is only given one API (for now) |
| 393 | function zeroBSCRM_getAPISecret() { |
| 394 | |
| 395 | global $zbs; |
| 396 | return $zbs->DAL->setting( 'api_secret' ); |
| 397 | } |
| 398 | |
| 399 | /** |
| 400 | * Replaces hyphens in key identifiers from a json array with underscores. |
| 401 | * Some services do not accept hyphens in their key identifiers, e.g. Zapier: |
| 402 | * https://github.com/zapier/zapier-platform/blob/master/packages/schema/docs/build/schema.md#keyschema |
| 403 | * |
| 404 | * If we expand use of this, we should consider making it recursive. |
| 405 | * |
| 406 | * @param array $input_array The array with keys needing to be changed, e.g.: [ { "id":"1", "custom-price":"10" }, { "id":"2", "custom-price":"20" }, ]. |
| 407 | * @return array Array with changed keys, e.g.: [ { "id":"1", "custom_price":"10" }, { "id":"2", "custom_price":"20" }, ]. |
| 408 | */ |
| 409 | function jpcrm_api_replace_hyphens_in_json_keys_with_underscores( $input_array ) { |
| 410 | $new_array = array(); |
| 411 | foreach ( $input_array as $original_item ) { |
| 412 | $new_array[] = array_combine( |
| 413 | array_map( |
| 414 | function ( $key ) { |
| 415 | return str_replace( '-', '_', $key ); |
| 416 | }, |
| 417 | array_keys( $original_item ) |
| 418 | ), |
| 419 | $original_item |
| 420 | ); |
| 421 | } |
| 422 | return $new_array; |
| 423 | } |