Changeset 3266307
- Timestamp:
- 04/03/2025 11:44:59 AM (12 months ago)
- Location:
- quentn-wp/trunk
- Files:
-
- 5 edited
-
includes/class-quentn-wp-access-overview-list.php (modified) (3 diffs)
-
includes/class-quentn-wp-rest-api-controller.php (modified) (18 diffs)
-
includes/class-quentn-wp.php (modified) (1 diff)
-
quentn-wp.php (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
quentn-wp/trunk/includes/class-quentn-wp-access-overview-list.php
r3073163 r3266307 215 215 public function get_quentn_restrictions( $per_page = 5, $page_number = 1, $page_id ) { 216 216 global $wpdb; 217 $sql = "SELECT * FROM " . $wpdb->prefix . TABLE_QUENTN_RESTRICTIONS. " where page_id='". $page_id."'"; 218 //set search 219 if ( ! empty( $_REQUEST['s'] ) ) { 220 $sql .= " and email LIKE '%" . esc_sql( $_REQUEST['s'] )."%'"; 217 218 $sql = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}" . TABLE_QUENTN_RESTRICTIONS . " WHERE page_id = %d", $page_id ); 219 220 if (!empty($_REQUEST['s'])) { 221 $search = '%' . $wpdb->esc_like($_REQUEST['s']) . '%'; 222 $sql .= $wpdb->prepare(" AND email LIKE %s", $search); 221 223 } 222 224 … … 298 300 public function record_count($page_id) { 299 301 global $wpdb; 300 $sql = "SELECT COUNT(*) FROM ".$wpdb->prefix . TABLE_QUENTN_RESTRICTIONS. " where page_id='".$page_id."'"; 301 if ( ! empty( $_REQUEST['s'] ) ) { 302 $sql .= " and email LIKE '%". esc_sql( $_REQUEST['s'] )."%'"; 302 $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->prefix}" . TABLE_QUENTN_RESTRICTIONS . " WHERE page_id = %d", $page_id ); 303 if (!empty($_REQUEST['s'])) { 304 $search = '%' . $wpdb->esc_like($_REQUEST['s']) . '%'; 305 $sql .= $wpdb->prepare(" AND email LIKE %s", $search); 303 306 } 304 307 … … 387 390 if( ! empty( $delete_restrict_pages_ids ) ) { 388 391 //delete multiple accesses 389 $query = "DELETE FROM ".$wpdb->prefix . TABLE_QUENTN_RESTRICTIONS." where CONCAT_WS('|', page_id, email) IN ('".implode("', '", $delete_restrict_pages_ids)."')"; 392 $placeholders = implode(',', array_fill(0, count($delete_restrict_pages_ids), '%s')); 393 $query = $wpdb->prepare( 394 "DELETE FROM {$wpdb->prefix}" . TABLE_QUENTN_RESTRICTIONS . " 395 WHERE CONCAT_WS('|', page_id, email) IN ($placeholders)", 396 $delete_restrict_pages_ids 397 ); 390 398 //$wpdb->query( $wpdb->query( $query ) ); 391 399 $num_records_deleted = $wpdb->query( $query ); -
quentn-wp/trunk/includes/class-quentn-wp-rest-api-controller.php
r3073163 r3266307 16 16 private $namespace; 17 17 18 /**18 /** 19 19 * The first URL segment after core prefix 20 20 * … … 79 79 private $get_tracking; 80 80 81 /**81 /** 82 82 * The base URL for route to get quentn logs 83 83 * … … 88 88 private $get_logs; 89 89 90 /**90 /** 91 91 * The base URL for route to get page access 92 92 * … … 97 97 private $get_page_access; 98 98 99 /**99 /** 100 100 * The base URL for route to get a page restriction settings 101 101 * … … 220 220 )); 221 221 222 //register route to get list of all pages having quentn restrictions active222 //register route to get list of all pages having quentn restrictions active 223 223 register_rest_route( $this->namespace_v2, $this->get_page_restrictions, array( 224 224 // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended. … … 308 308 )); 309 309 310 //register route to get logs310 //register route to get logs 311 311 register_rest_route( $this->namespace, $this->get_logs, array( 312 312 // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended. … … 329 329 )); 330 330 331 //register route to get page access332 register_rest_route( $this->namespace, $this->get_page_access, array(333 // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.334 'methods' => \WP_REST_Server::CREATABLE,335 // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.336 'callback' => array( $this, 'quentn_get_page_access' ),337 // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.338 'permission_callback' => array( $this, 'quentn_check_credentials' ),339 340 'args' => array(341 'data' => array(342 'required' => true,343 'type' => 'string',344 ),345 'vu' => array(346 'required' => true,347 'type' => 'integer',348 ),349 ),350 ));351 352 //register route to get page restriction settings353 register_rest_route( $this->namespace, $this->page_restriction_settings, array(354 // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.355 'methods' => \WP_REST_Server::CREATABLE,356 // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.357 'callback' => array( $this, 'quentn_get_page_restriction_settings' ),358 // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.359 'permission_callback' => array( $this, 'quentn_check_credentials' ),360 361 'args' => array(362 'data' => array(363 'required' => true,364 'type' => 'string',365 ),366 'vu' => array(367 'required' => true,368 'type' => 'integer',369 ),370 ),371 ));331 //register route to get page access 332 register_rest_route( $this->namespace, $this->get_page_access, array( 333 // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended. 334 'methods' => \WP_REST_Server::CREATABLE, 335 // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class. 336 'callback' => array( $this, 'quentn_get_page_access' ), 337 // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint. 338 'permission_callback' => array( $this, 'quentn_check_credentials' ), 339 340 'args' => array( 341 'data' => array( 342 'required' => true, 343 'type' => 'string', 344 ), 345 'vu' => array( 346 'required' => true, 347 'type' => 'integer', 348 ), 349 ), 350 )); 351 352 //register route to get page restriction settings 353 register_rest_route( $this->namespace, $this->page_restriction_settings, array( 354 // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended. 355 'methods' => \WP_REST_Server::CREATABLE, 356 // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class. 357 'callback' => array( $this, 'quentn_get_page_restriction_settings' ), 358 // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint. 359 'permission_callback' => array( $this, 'quentn_check_credentials' ), 360 361 'args' => array( 362 'data' => array( 363 'required' => true, 364 'type' => 'string', 365 ), 366 'vu' => array( 367 'required' => true, 368 'type' => 'integer', 369 ), 370 ), 371 )); 372 372 } 373 373 … … 382 382 public function quentn_grant_access( $request ) { 383 383 global $wpdb; 384 384 385 $request_body = json_decode( $request->get_body(), true ); 385 //decode and save request in array 386 $quentn_page_timer_permission = json_decode( base64_decode( $request_body['data'] ), true ); 387 $emails = $quentn_page_timer_permission['data']['email'] ?? array(); 388 $pages = $quentn_page_timer_permission['data']['page'] ?? array(); 389 390 //set values and place holders to insert into database in one query 391 $values = array(); 392 $place_holders = array(); 386 $quentn_data = json_decode( base64_decode( $request_body['data'] ), true ); 387 388 $emails = isset( $quentn_data['data']['email'] ) ? $quentn_data['data']['email'] : array(); 389 $pages = isset( $quentn_data['data']['page'] ) ? $quentn_data['data']['page'] : array(); 390 391 $placeholders = []; 392 $values = []; 393 $now = time(); 394 393 395 foreach ( $emails as $email ) { 394 if ( $email == "" ) { //email cannot be empty 396 $email = sanitize_email( $email ); 397 if ( empty( $email ) ) { 395 398 continue; 396 399 } 400 401 $email_hash = hash( 'sha256', $email ); 402 397 403 foreach ( $pages as $page ) { 398 array_push( $values, $page, $email, hash( 'sha256', $email ), time() ); 399 $place_holders[] = "('%d', '%s', '%s', '%d')"; 400 } 401 } 402 403 //insert into database 404 $query = "INSERT INTO ".$wpdb->prefix . TABLE_QUENTN_RESTRICTIONS." ( page_id, email, email_hash, created_at ) VALUES "; 405 $query .= implode( ', ', $place_holders ); 406 $wpdb->query( $wpdb->prepare( "$query ON DUPLICATE KEY UPDATE created_at= ".time(), $values ) ); 407 do_action( 'quentn_access_granted', $emails, $pages, QUENTN_WP_ACCESS_ADDED_BY_API ); 404 $page = intval( $page ); 405 $placeholders[] = "(%d, %s, %s, %d)"; 406 array_push( $values, $page, $email, $email_hash, $now ); 407 } 408 } 409 410 if ( empty( $placeholders ) ) { 411 return new WP_Error( 'no_values', __( 'No valid values to grant access.', 'quentn-wp' ), array( 'status' => 400 ) ); 412 } 413 414 $table = $wpdb->prefix . TABLE_QUENTN_RESTRICTIONS; 415 $sql = "INSERT INTO $table (page_id, email, email_hash, created_at) VALUES "; 416 $sql .= implode( ', ', $placeholders ); 417 $sql .= " ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)"; 418 419 $prepared = $wpdb->prepare( $sql, $values ); 420 $wpdb->query( $prepared ); 421 422 do_action( 'quentn_access_granted', $emails, $pages, QUENTN_WP_ACCESS_ADDED_BY_API ); 423 408 424 return rest_ensure_response( esc_html__( 'Permissions Timer Successfully Updated', 'quentn-wp' ) ); 409 425 } … … 421 437 422 438 $request_body = json_decode( $request->get_body(), true ); 423 424 $quentn_page_timer_permission = json_decode ( base64_decode( $request_body['data'] ), true ); 425 426 $emails = isset( $quentn_page_timer_permission['data']['email'] ) ? $quentn_page_timer_permission['data']['email'] : array(); 427 $pages = isset( $quentn_page_timer_permission['data']['page'] ) ? $quentn_page_timer_permission['data']['page'] : array(); 428 429 //set values and place holders to insert into database in one query 430 $values = array(); 439 $quentn_data = json_decode( base64_decode( $request_body['data'] ), true ); 440 441 $emails = isset( $quentn_data['data']['email'] ) ? $quentn_data['data']['email'] : array(); 442 $pages = isset( $quentn_data['data']['page'] ) ? $quentn_data['data']['page'] : array(); 443 444 // Collect safe page-email pairs 445 $conditions = []; 446 $values = []; 447 431 448 foreach ( $emails as $email ) { 449 $email = sanitize_email( $email ); 432 450 foreach ( $pages as $page ) { 433 $pageid_email = trim( $page )."|".trim( $email ); 434 array_push( $values, $pageid_email ); 435 } 436 } 437 438 //delete permissions 439 $query = "DELETE FROM ".$wpdb->prefix . TABLE_QUENTN_RESTRICTIONS." where CONCAT_WS('|', page_id, email) IN ('".implode("','", $values)."')"; 440 $affected_rows = $wpdb->query( $query ); 441 if ( $affected_rows ) { 442 do_action( 'quentn_access_revoked', $emails, $pages, QUENTN_WP_ACCESS_REVOKED_BY_API ); 443 } 451 $page = intval( $page ); 452 $conditions[] = "(page_id = %d AND email = %s)"; 453 $values[] = $page; 454 $values[] = $email; 455 } 456 } 457 458 if ( empty( $conditions ) ) { 459 return new WP_Error( 'no_values', __( 'No valid values to revoke access.', 'quentn-wp' ), array( 'status' => 400 ) ); 460 } 461 462 // Build secure query 463 $where = implode( ' OR ', $conditions ); 464 $query = "DELETE FROM {$wpdb->prefix}" . TABLE_QUENTN_RESTRICTIONS . " WHERE $where"; 465 466 $prepared_query = $wpdb->prepare( $query, $values ); 467 $affected_rows = $wpdb->query( $prepared_query ); 468 469 if ( $affected_rows ) { 470 do_action( 'quentn_access_revoked', $emails, $pages, QUENTN_WP_ACCESS_REVOKED_BY_API ); 471 } 444 472 445 473 return rest_ensure_response( esc_html__( 'Permissions Timer Successfully Updated', 'quentn-wp' ) ); … … 461 489 if( get_post_meta( $id->ID, '_quentn_post_restrict_meta', true ) ) { 462 490 $restricted_pages[] = array( 463 "page_id" => $id->ID,464 "page_title" => $id->post_title,465 );466 } 467 } 468 //todo remove json_encode function491 "page_id" => $id->ID, 492 "page_title" => $id->post_title, 493 ); 494 } 495 } 496 //todo remove json_encode function 469 497 return rest_ensure_response( json_encode( $restricted_pages ) ); 470 498 } 471 499 472 /**500 /** 473 501 * Get list of all pages where quentn restrictions are applied 474 502 * 475 503 * @since 1.2.8 476 504 * @access public 477 * @param WP_REST_Request $request The current request object.478 * @return WP_Error|WP_REST_Response505 * @param WP_REST_Request $request The current request object. 506 * @return WP_Error|WP_REST_Response 479 507 */ 480 508 public function quentn_get_restricted_pages_v2( $request ) { 481 $request_body = json_decode( $request->get_body(), true ); 482 $request_body_data = json_decode( base64_decode( $request_body['data'] ), true ); 483 $request_data = $request_body_data['data']; 484 $args = array( 485 'post_type' => 'page', 486 'meta_key' => '_quentn_post_restrict_meta' 487 ); 488 489 $limit = ! empty( $request_data['limit'] ) ? $request_data['limit'] : 50; // get all posts if not mentioned 490 $args['posts_per_page'] = $limit; 491 492 if ( ! empty( $request_data['order_by'] ) ) { 493 $args['orderby'] = $request_data['order_by']; 494 } 495 if ( ! empty( $request_data['sort'] ) ) { 496 $args['order'] = $request_data['sort']; 497 } 498 if ( ! empty( $request_data['offset'] ) ) { 499 $args['offset'] = $request_data['offset']; 500 } 501 502 //get all restricted pages 503 $restricted_pages_query = new WP_Query( $args ); 504 $restricted_pages = []; 505 if ( $restricted_pages_query->have_posts() ) { 506 507 //get list of total access of restricted pages 508 $page_ids = array_column( $restricted_pages_query->posts, 'ID' ); 509 global $wpdb; 510 $sql = "SELECT page_id, COUNT(*) as totoal_access FROM ". $wpdb->prefix . TABLE_QUENTN_RESTRICTIONS. " where page_id IN (".implode(",",$page_ids).") GROUP BY page_id"; 511 $rows = $wpdb->get_results( $sql ); 512 $pages_access_links = array(); 513 514 foreach ( $rows as $row ) { 515 $pages_access_links[$row->page_id] = $row->totoal_access; 516 } 517 518 foreach( $restricted_pages_query->posts as $restricted_page ) { 519 $quentn_post_restrict_meta = get_post_meta( $restricted_page->ID, '_quentn_post_restrict_meta', true ); 520 $restricted_pages[] = array( 521 "page_id" => $restricted_page->ID, 522 "page_title" => $restricted_page->post_title, 523 "page_public_url" => get_page_link( $restricted_page->ID ), 524 "restriction_type" => ! empty( $quentn_post_restrict_meta['countdown'] ) ? 'countdown' : 'access', 525 "access_links" => ( isset( $pages_access_links[$restricted_page->ID] ) ) ? $pages_access_links[$restricted_page->ID] : 0 , 526 ); 527 } 528 } 529 530 $response = [ 531 'success' => true, 532 'total' => count( $restricted_pages ), 533 'limit' => $limit, 534 'offset' => ! empty( $request_data['offset'] ) ? $request_data['offset'] : 0, 535 'order_by' => ! empty( $request_data['order_by'] ) ? $request_data['order_by'] : 'date', 536 'sort' => ! empty( $request_data['sort'] ) ? $request_data['sort'] : 'DESC', 537 'data' => $restricted_pages, 538 ]; 539 return rest_ensure_response( $response ); 509 global $wpdb; 510 511 $request_body = json_decode( $request->get_body(), true ); 512 $request_body_data = json_decode( base64_decode( $request_body['data'] ), true ); 513 $request_data = $request_body_data['data']; 514 515 // Sanitize and validate parameters 516 $allowed_orderby = [ 'title', 'date', 'modified', 'menu_order' ]; 517 $allowed_sort = [ 'ASC', 'DESC' ]; 518 519 $limit = isset( $request_data['limit'] ) ? absint( $request_data['limit'] ) : 50; 520 $offset = isset( $request_data['offset'] ) ? absint( $request_data['offset'] ) : 0; 521 $order_by = ( isset( $request_data['order_by'] ) && in_array( $request_data['order_by'], $allowed_orderby ) ) ? $request_data['order_by'] : 'date'; 522 $sort = ( isset( $request_data['sort'] ) && in_array( strtoupper( $request_data['sort'] ), $allowed_sort ) ) ? strtoupper( $request_data['sort'] ) : 'DESC'; 523 524 525 $args = [ 526 'post_type' => 'page', 527 'meta_key' => '_quentn_post_restrict_meta', 528 'orderby' => $order_by, 529 'order' => $sort, 530 'offset' => $offset, 531 'posts_per_page' => $limit, 532 ]; 533 534 // Query restricted pages 535 $restricted_pages_query = new WP_Query( $args ); 536 $restricted_pages = []; 537 538 if ( $restricted_pages_query->have_posts() ) { 539 $page_ids = array_column( $restricted_pages_query->posts, 'ID' ); 540 541 $pages_access_links = []; 542 543 if ( ! empty( $page_ids ) ) { 544 $placeholders = implode( ',', array_fill( 0, count( $page_ids ), '%d' ) ); 545 $sql = $wpdb->prepare( 546 "SELECT page_id, COUNT(*) as totoal_access 547 FROM {$wpdb->prefix}" . TABLE_QUENTN_RESTRICTIONS . " 548 WHERE page_id IN ($placeholders) 549 GROUP BY page_id", 550 $page_ids 551 ); 552 $rows = $wpdb->get_results( $sql ); 553 554 foreach ( $rows as $row ) { 555 $pages_access_links[ $row->page_id ] = $row->totoal_access; 556 } 557 } 558 559 foreach ( $restricted_pages_query->posts as $restricted_page ) { 560 $quentn_post_restrict_meta = get_post_meta( $restricted_page->ID, '_quentn_post_restrict_meta', true ); 561 562 $restricted_pages[] = [ 563 'page_id' => $restricted_page->ID, 564 'page_title' => $restricted_page->post_title, 565 'page_public_url' => get_page_link( $restricted_page->ID ), 566 'restriction_type' => ! empty( $quentn_post_restrict_meta['countdown'] ) ? 'countdown' : 'access', 567 'access_links' => $pages_access_links[ $restricted_page->ID ] ?? 0, 568 ]; 569 } 570 } 571 $response = [ 572 'success' => true, 573 'total' => count( $restricted_pages ), 574 'limit' => $limit, 575 'offset' => $offset, 576 'order_by' => $order_by, 577 'sort' => $sort, 578 'data' => $restricted_pages, 579 ]; 580 581 return rest_ensure_response( $response ); 540 582 } 541 583 … … 550 592 $wp_roles = new WP_Roles(); 551 593 $all_roles = $wp_roles->get_names(); 552 //todo remove json_encode function594 //todo remove json_encode function 553 595 return rest_ensure_response( json_encode( $all_roles ) ); 554 596 } … … 587 629 update_user_meta( $user_id, $meta_key, $meta_value ); 588 630 } 589 do_action( 'quentn_user_updated', $qn_userdata['user_email'], $user_id );631 do_action( 'quentn_user_updated', $qn_userdata['user_email'], $user_id ); 590 632 } else { 591 633 //no default role set … … 596 638 if ( ! is_wp_error( $user_id ) ) { 597 639 update_user_meta( $user_id, 'quentn_last_login', 0 ); 598 do_action( 'quentn_user_created', $qn_userdata['user_email'], $user_id );640 do_action( 'quentn_user_created', $qn_userdata['user_email'], $user_id ); 599 641 } 600 642 } … … 611 653 $new_roles = $request_data['data']['roles']['add_roles']; 612 654 foreach ( $new_roles as $new_role ) { 613 $new_user->add_role( trim( $new_role ) ); 614 do_action( 'quentn_user_role_added', $new_user->user_email, $user_id, trim( $new_role ) ); 655 $role_to_add = trim( $new_role ); 656 657 // Skip if the role is 'administrator' 658 if ( strtolower($role_to_add) === 'administrator' ) { 659 continue; 660 } 661 662 $new_user->add_role( $role_to_add ); 663 do_action( 'quentn_user_role_added', $new_user->user_email, $user_id, trim( $new_role ) ); 615 664 } 616 665 } … … 621 670 foreach ( $remove_roles as $remove_role ) { 622 671 $new_user->remove_role( trim( $remove_role ) ); 623 do_action( 'quentn_user_role_removed', $new_user->user_email, $user_id, trim( $remove_role ) );672 do_action( 'quentn_user_role_removed', $new_user->user_email, $user_id, trim( $remove_role ) ); 624 673 } 625 674 } … … 656 705 } 657 706 658 /** 659 * Get list of logs 660 * 661 * @since 1.2.8 662 * @access public 663 * @param WP_REST_Request $request The current request object. 664 * @return WP_Error|WP_REST_Response 665 */ 666 public function quentn_get_logs( $request ) { 667 $request_body = json_decode( $request->get_body(), true ); 668 $request_body_data = json_decode( base64_decode( $request_body['data'] ), true ); 669 $request_data = $request_body_data['data']; 670 $conditions = []; 671 $response = []; 672 673 global $wpdb; 674 $sql = "SELECT * FROM " . $wpdb->prefix . TABLE_QUENTN_LOG ; 675 676 if ( ! empty( $request_data['events'] ) ) { 677 $conditions[] = "event IN (" . implode(',', array_map('intval', $request_data['events'] ) ) . ")"; 678 } 679 680 if ( ! empty( $request_data['emails'] ) ) { 681 $escaped_emails = array_map( function( $val ) use ( $wpdb ) { 682 return $wpdb->prepare('%s', $val); 683 }, $request_data['emails'] ); 684 $conditions[] = "email IN (" . implode(',', $escaped_emails) . ")"; 685 } 686 687 if ( ! empty( $request_data['pages'] ) ) { 688 $conditions[] = "page_id IN (" . implode(',', array_map('intval', $request_data['pages'] ) ) . ")"; 689 } 690 691 if ( ! empty( $request_data['from'] ) ) { 692 $conditions[] = "created_at >= ". intval( $request_data['from'] ); 693 } 694 695 if ( ! empty( $request_data['to'] ) ) { 696 $conditions[] = "created_at <= ". intval( $request_data['to'] ); 697 } 698 699 if ( ! empty( $conditions ) ) { 700 $sql .= " WHERE " . implode( " AND ", $conditions ); 701 } 702 703 //order by 704 $order_by = ! empty( $request_data['order_by'] ) ? $request_data['order_by'] : 'created_at'; 705 $sort_by = ! empty( $request_data['sort'] ) ? $request_data['sort'] : 'desc'; 706 $sql .= " order by ". $order_by. " ". $sort_by; 707 708 //limit 709 $limit = ! empty( $request_data['limit'] ) ? intval( $request_data['limit'] ) : 50; 710 $offset = ! empty( $request_data['offset'] ) ? intval( $request_data['offset'] ) : 0; 711 $sql .= " limit ". $offset . ", " . $limit; 712 713 $results = $wpdb->get_results( $sql, 'ARRAY_A' ); 714 if ( $wpdb->last_error ) { 715 return new WP_Error( 'log_call_failed', $wpdb->last_error ); 716 } 717 718 //prepare response data key 719 $logs = []; 720 foreach ( $results as $log ) { 721 if ( ! empty( $log['page_id'] ) ) { 722 $log['page_title'] = get_the_title( $log['page_id'] ); 723 $log['page_public_url'] = get_page_link( $log['page_id'] ); 724 } else { 725 $log['page_title'] = ''; 726 $log['page_public_url'] = ''; 727 } 728 729 $logs[] = $log; 730 } 731 732 include_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/config.php'; 733 if ( ! empty( $request_data['events'] ) ) { 734 $requested_events = []; 735 foreach ( $request_data['events'] as $event ) { 736 $requested_events[$event] = $events[ $event ]; 737 } 738 } 739 $response = [ 740 'success' => true, 741 'total' => count( $results ), 742 'limit' => $limit, 743 'offset' => $offset, 744 'order_by' => $order_by, 745 'sort' => $sort_by, 746 'events' => $events, 747 'requested_events' => $requested_events, 748 'data' => $logs, 749 ]; 750 751 return rest_ensure_response( $response ); 752 } 753 /** 754 * Get list of get page access 755 * 756 * @since 1.2.8 757 * @access public 758 * param WP_REST_Request $request The current request object. 759 * @return WP_Error|WP_REST_Response 760 */ 761 public function quentn_get_page_access( $request ) { 762 $request_body = json_decode( $request->get_body(), true ); 763 $request_body_data = json_decode( base64_decode( $request_body['data'] ), true ); 764 $request_data = $request_body_data['data']; 765 766 $page_id = $request->get_param('pid'); 767 768 global $wpdb; 769 $sql = "SELECT email, email_hash, created_at FROM " . $wpdb->prefix . TABLE_QUENTN_RESTRICTIONS. " where page_id='". $page_id . "'"; 770 771 //order by 772 $order_by = ! empty( $request_data['order_by'] ) ? $request_data['order_by'] : 'email'; 773 $sort_by = ! empty( $request_data['sort'] ) ? $request_data['sort'] : 'desc'; 774 $sql .= " order by ". $order_by. " ". $sort_by; 775 776 //limit 777 $limit = ! empty( $request_data['limit'] ) ? intval( $request_data['limit'] ) : 50; 778 $offset = ! empty( $request_data['offset'] ) ? intval( $request_data['offset'] ) : 0; 779 $sql .= " limit ". $offset . ", " . $limit; 780 781 $results = $wpdb->get_results( $sql, 'ARRAY_A' ); 782 if ( $wpdb->last_error ) { 783 return new WP_Error( 'page_access_call_failed', $wpdb->last_error ); 784 } 785 786 //prepare response data 787 $page_accesses = []; 788 $separator = ( parse_url( get_page_link( $page_id ), PHP_URL_QUERY ) ) ? '&' : '?'; 789 foreach ( $results as $page_access ) { 790 $page_access['access_link'] = get_page_link( $page_id ) . $separator.'qntn_wp=' . $page_access['email_hash']; 791 unset( $page_access['email_hash'] ); //email not included in response 792 $page_accesses[] = $page_access; 793 } 794 795 $response = [ 796 'success' => true, 797 'page_id' => $page_id, 798 'page_title' => get_the_title( $page_id ), 799 'page_public_url' => get_page_link( $page_id ), 800 'total' => count( $page_accesses ), 801 'limit' => $limit, 802 'offset' => $offset, 803 'order_by' => $order_by, 804 'sort' => $sort_by, 805 'data' => $page_accesses, 806 ]; 807 808 return rest_ensure_response( $response ); 809 } 810 811 /** 812 * Get list of get page restriction settings 813 * 814 * @since 1.2.8 815 * @access public 816 * @param WP_REST_Request $request The current request object. 817 * @return WP_Error|WP_REST_Response 818 */ 819 public function quentn_get_page_restriction_settings( $request ) { 820 821 $page_id = $request->get_param('pid'); 822 823 $restricted_data = get_post_meta( $page_id, '_quentn_post_restrict_meta', true ); 824 825 $response[] = true; 826 $response = [ 827 'success' => true, 828 'page_id' => $page_id, 829 'page_title' => get_the_title( $page_id ), 830 'page_public_url' => get_page_link( $page_id ), 831 ]; 832 $response['restriction_enabled'] = boolval( $restricted_data['status'] ); 833 if ( ! empty( $restricted_data ) ) { 834 $response['restriction_type'] = ! empty( $restricted_data['countdown'] ) ? 'countdown' : 'access'; 835 $response['countdown_type'] = $restricted_data['countdown_type']; 836 $response['countdown_absolute_date'] = $restricted_data['absolute_date']; 837 $response['countdown_relative_settings'] = [ 838 'hours' => $restricted_data['hours'], 839 'minutes' => $restricted_data['minutes'], 840 'seconds' => $restricted_data['seconds'], 841 ]; 842 $response['countdown_relative_start_type'] = $restricted_data['access_mode'] == 'permission_granted_mode' ? 'permission_granted' : 'first_visit'; 843 $response['display_countdown'] = $restricted_data['display_countdown_default_status']; 844 $response['countdown_top_page'] = $restricted_data['quentn_countdown_stick_on_top']; 845 $response['redirection_type'] = $restricted_data['redirection_type'] == 'restricted_message' ? 'message' : 'url'; 846 $response['redirection_url'] = $restricted_data['redirect_url']; 847 $response['redirection_message'] = $restricted_data['error_message']; 848 } 849 850 return rest_ensure_response( $response ); 851 } 707 /** 708 * Get list of logs 709 * 710 * @since 1.2.8 711 * @access public 712 * @param WP_REST_Request $request The current request object. 713 * @return WP_Error|WP_REST_Response 714 */ 715 public function quentn_get_logs( $request ) { 716 global $wpdb; 717 718 $request_body = json_decode( $request->get_body(), true ); 719 $request_body_data = json_decode( base64_decode( $request_body['data'] ), true ); 720 721 if ( ! isset( $request_body_data['data'] ) || ! is_array( $request_body_data['data'] ) ) { 722 return new WP_Error( 'invalid_data', __( 'Malformed data structure.', 'quentn-wp' ), [ 'status' => 400 ] ); 723 } 724 $request_data = $request_body_data['data']; 725 726 $conditions = []; 727 $values = []; 728 729 $sql = "SELECT * FROM {$wpdb->prefix}" . TABLE_QUENTN_LOG; 730 731 // Safe filtering 732 if ( ! empty( $request_data['events'] ) ) { 733 $event_ids = array_map( 'intval', $request_data['events'] ); 734 $placeholders = implode( ',', array_fill( 0, count( $event_ids ), '%d' ) ); 735 $conditions[] = "event IN ($placeholders)"; 736 $values = array_merge( $values, $event_ids ); 737 } 738 739 if ( ! empty( $request_data['emails'] ) ) { 740 $email_placeholders = implode( ',', array_fill( 0, count( $request_data['emails'] ), '%s' ) ); 741 $conditions[] = "email IN ($email_placeholders)"; 742 $values = array_merge( $values, $request_data['emails'] ); 743 } 744 745 if ( ! empty( $request_data['pages'] ) ) { 746 $page_ids = array_map( 'intval', $request_data['pages'] ); 747 $placeholders = implode( ',', array_fill( 0, count( $page_ids ), '%d' ) ); 748 $conditions[] = "page_id IN ($placeholders)"; 749 $values = array_merge( $values, $page_ids ); 750 } 751 752 if ( ! empty( $request_data['from'] ) ) { 753 $conditions[] = "created_at >= %d"; 754 $values[] = intval( $request_data['from'] ); 755 } 756 757 if ( ! empty( $request_data['to'] ) ) { 758 $conditions[] = "created_at <= %d"; 759 $values[] = intval( $request_data['to'] ); 760 } 761 762 if ( ! empty( $conditions ) ) { 763 $sql .= " WHERE " . implode( " AND ", $conditions ); 764 } 765 766 // Validate and whitelist sort fields 767 $allowed_order_by = [ 'created_at', 'event', 'email', 'page_id' ]; 768 $allowed_sort = [ 'ASC', 'DESC' ]; 769 770 $order_by = ( isset( $request_data['order_by'] ) && in_array( $request_data['order_by'], $allowed_order_by ) ) ? $request_data['order_by'] : 'created_at'; 771 $sort_by = ( isset( $request_data['sort'] ) && in_array( strtoupper( $request_data['sort'] ), $allowed_sort ) ) ? strtoupper( $request_data['sort'] ) : 'DESC'; 772 773 $sql .= " ORDER BY $order_by $sort_by"; 774 775 // Limit + offset 776 $limit = ! empty( $request_data['limit'] ) ? absint( $request_data['limit'] ) : 50; 777 $offset = ! empty( $request_data['offset'] ) ? absint( $request_data['offset'] ) : 0; 778 779 $sql .= " LIMIT %d OFFSET %d"; 780 $values[] = $limit; 781 $values[] = $offset; 782 783 // Prepare and execute query 784 $prepared_sql = $wpdb->prepare( $sql, $values ); 785 $results = $wpdb->get_results( $prepared_sql, 'ARRAY_A' ); 786 787 if ( $wpdb->last_error ) { 788 return new WP_Error( 'log_call_failed', $wpdb->last_error ); 789 } 790 791 // Process results 792 $logs = []; 793 foreach ( $results as $log ) { 794 if ( ! empty( $log['page_id'] ) ) { 795 $log['page_title'] = get_the_title( $log['page_id'] ); 796 $log['page_public_url'] = get_page_link( $log['page_id'] ); 797 } else { 798 $log['page_title'] = ''; 799 $log['page_public_url'] = ''; 800 } 801 802 $logs[] = $log; 803 } 804 805 // Load event labels 806 include_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/config.php'; 807 808 $requested_events = []; 809 if ( ! empty( $request_data['events'] ) ) { 810 foreach ( $request_data['events'] as $event ) { 811 if ( isset( $events[ $event ] ) ) { 812 $requested_events[ $event ] = $events[ $event ]; 813 } 814 } 815 } 816 817 $response = [ 818 'success' => true, 819 'total' => count( $results ), 820 'limit' => $limit, 821 'offset' => $offset, 822 'order_by' => $order_by, 823 'sort' => $sort_by, 824 'events' => $events, 825 'requested_events' => $requested_events, 826 'data' => $logs, 827 ]; 828 829 return rest_ensure_response( $response ); 830 } 831 832 /** 833 * Get list of get page access 834 * 835 * @since 1.2.8 836 * @access public 837 * param WP_REST_Request $request The current request object. 838 * @return WP_Error|WP_REST_Response 839 */ 840 public function quentn_get_page_access( $request ) { 841 $request_body = json_decode( $request->get_body(), true ); 842 $request_body_data = json_decode( base64_decode( $request_body['data'] ), true ); 843 $request_data = $request_body_data['data']; 844 $page_id = $request->get_param('pid'); 845 846 global $wpdb; 847 $table_name = $wpdb->prefix . TABLE_QUENTN_RESTRICTIONS; 848 849 // Validation against allowed values 850 $allowed_order_by = ['email', 'email_hash', 'created_at']; 851 $order_by = in_array($request_data['order_by'], $allowed_order_by) ? $request_data['order_by'] : 'created_at'; 852 853 $allowed_sorts = ['asc', 'desc']; 854 $sort_by = in_array(strtolower($request_data['sort']), $allowed_sorts) ? $request_data['sort'] : 'desc'; 855 856 $limit = isset($request_data['limit']) ? intval($request_data['limit']) : 50; 857 $offset = isset($request_data['offset']) ? intval($request_data['offset']) : 0; 858 859 $sql = "SELECT email, email_hash, created_at FROM $table_name WHERE page_id = %d ORDER BY $order_by $sort_by LIMIT %d, %d"; 860 861 $results = $wpdb->get_results($wpdb->prepare($sql, $page_id, $offset, $limit), ARRAY_A); 862 if ($wpdb->last_error) { 863 return new WP_Error('page_access_call_failed', $wpdb->last_error); 864 } 865 866 867 //prepare response data 868 $page_accesses = []; 869 $separator = ( parse_url( get_page_link( $page_id ), PHP_URL_QUERY ) ) ? '&' : '?'; 870 foreach ( $results as $page_access ) { 871 $page_access['access_link'] = get_page_link( $page_id ) . $separator.'qntn_wp=' . $page_access['email_hash']; 872 unset( $page_access['email_hash'] ); //email not included in response 873 $page_accesses[] = $page_access; 874 } 875 876 $response = [ 877 'success' => true, 878 'page_id' => $page_id, 879 'page_title' => get_the_title( $page_id ), 880 'page_public_url' => get_page_link( $page_id ), 881 'total' => count( $page_accesses ), 882 'limit' => $limit, 883 'offset' => $offset, 884 'order_by' => $order_by, 885 'sort' => $sort_by, 886 'data' => $page_accesses, 887 ]; 888 889 return rest_ensure_response( $response ); 890 } 891 892 /** 893 * Get list of get page restriction settings 894 * 895 * @since 1.2.8 896 * @access public 897 * @param WP_REST_Request $request The current request object. 898 * @return WP_Error|WP_REST_Response 899 */ 900 public function quentn_get_page_restriction_settings( $request ) { 901 902 $page_id = absint( $request->get_param('pid') ); 903 904 if ( ! $page_id ) { 905 return new WP_Error( 'invalid_page_id', __( 'Page ID is required.', 'quentn-wp' ), [ 'status' => 400 ] ); 906 } 907 908 $restricted_data = get_post_meta( $page_id, '_quentn_post_restrict_meta', true ); 909 910 $response[] = true; 911 $response = [ 912 'success' => true, 913 'page_id' => $page_id, 914 'page_title' => get_the_title( $page_id ), 915 'page_public_url' => get_page_link( $page_id ), 916 ]; 917 $response['restriction_enabled'] = boolval( $restricted_data['status'] ); 918 if ( ! empty( $restricted_data ) ) { 919 $response['restriction_type'] = ! empty( $restricted_data['countdown'] ) ? 'countdown' : 'access'; 920 $response['countdown_type'] = $restricted_data['countdown_type']; 921 $response['countdown_absolute_date'] = $restricted_data['absolute_date']; 922 $response['countdown_relative_settings'] = [ 923 'hours' => $restricted_data['hours'], 924 'minutes' => $restricted_data['minutes'], 925 'seconds' => $restricted_data['seconds'], 926 ]; 927 $response['countdown_relative_start_type'] = $restricted_data['access_mode'] == 'permission_granted_mode' ? 'permission_granted' : 'first_visit'; 928 $response['display_countdown'] = $restricted_data['display_countdown_default_status']; 929 $response['countdown_top_page'] = $restricted_data['quentn_countdown_stick_on_top']; 930 $response['redirection_type'] = $restricted_data['redirection_type'] == 'restricted_message' ? 'message' : 'url'; 931 $response['redirection_url'] = $restricted_data['redirect_url']; 932 $response['redirection_message'] = $restricted_data['error_message']; 933 } 934 935 return rest_ensure_response( $response ); 936 } 852 937 853 938 /** … … 896 981 } 897 982 898 899 983 /** 900 984 * Varify quentn request … … 906 990 */ 907 991 public function quentn_check_credentials( $request ) { 908 909 992 $request_body = json_decode( $request->get_body(), true ); 910 993 911 $api_key = ( get_option('quentn_app_key') ) ? get_option('quentn_app_key') : ''; 912 913 //check time validation for request 994 // Basic input validation 995 if ( !isset( $request_body['vu'] ) || !isset( $request_body['hash'] ) ) { 996 return new WP_Error( 'missing_params', esc_html__( 'Required parameters missing', 'quentn-wp' ), array( 'status' => 400 ) ); 997 } 998 999 $api_key = get_option( 'quentn_app_key', '' ); 1000 1001 // Return error if API key is empty (critical security check) 1002 if ( empty( $api_key ) ) { 1003 return new WP_Error( 'api_key_not_set', esc_html__( 'API key is not configured', 'quentn-wp' ), array( 'status' => 401 ) ); 1004 } 1005 1006 // validate time 914 1007 if( $request_body['vu'] <= time() ) { 915 1008 return new WP_Error( 'time_expired', esc_html__( 'Time has expired', 'quentn-wp' ), array( 'status' => 401 ) ); 916 1009 } 917 1010 918 919 if( isset( $request_body['data'] ) ) { 920 $hash = hash( 'sha256', $request_body['data'].$request_body['vu'].$api_key ); 921 } else { 922 $hash = hash( 'sha256', $request_body['vu'].$api_key ); 923 } 924 925 if ( $hash != $request_body['hash'] ) { 926 return new WP_Error( 'invalid_key', esc_html__( 'Incorrect Api Key', 'quentn-wp' ), array( 'status' => 401 ) ); 1011 // Calculate expected hash 1012 $components = [ $request_body['vu'], $api_key ]; 1013 if ( isset( $request_body['data'] ) ) { 1014 array_unshift( $components, $request_body['data'] ); 1015 } 1016 1017 // Use hash_equals() for timing-safe comparison 1018 $expected_hash = hash( 'sha256', implode( '', $components ) ); 1019 if ( !hash_equals( $expected_hash, $request_body['hash'] ) ) { 1020 return new WP_Error( 'invalid_key', esc_html__( 'Authentication failed', 'quentn-wp' ), array( 'status' => 401 ) ); 927 1021 } 928 1022 -
quentn-wp/trunk/includes/class-quentn-wp.php
r3073163 r3266307 75 75 $this->version = QUENTN_WP_VERSION; 76 76 } else { 77 $this->version = '1.2. 8';77 $this->version = '1.2.9'; 78 78 } 79 79 $this->plugin_name = 'quentn-wp'; -
quentn-wp/trunk/quentn-wp.php
r3073163 r3266307 35 35 define( "QUENTN_WP_PLUGIN_DIR", plugin_dir_path( __FILE__ ) ); 36 36 define( "QUENTN_WP_PLUGIN_URL", plugin_dir_url( __FILE__ ) ); 37 define( 'QUENTN_WP_VERSION', '1.2. 8' );37 define( 'QUENTN_WP_VERSION', '1.2.9' ); 38 38 define( 'QUENTN_WP_DB_VERSION', '1.1' ); 39 39 -
quentn-wp/trunk/readme.txt
r3073185 r3266307 3 3 Tags: Quentn, countdown, page restriction, email, marketing automation 4 4 Requires at least: 4.6.0 5 Tested up to: 6. 46 Stable tag: 1.2. 85 Tested up to: 6.7.2 6 Stable tag: 1.2.9 7 7 Requires PHP: 5.6.0 8 8 License: GPLv2 or later … … 67 67 == Changelog == 68 68 69 = 1.2.9 = 70 * Security Fix: Fixed SQL injection vulnerabilities 71 * Security Fix: Hardened input validation for all admin operations 72 * Security Fix: Improved data sanitization and escaping 73 69 74 = 1.2.8 = 70 75 * Add: Log option to trace different user quentn related activities. … … 164 169 == Upgrade Notice == 165 170 171 = 1.2.9 - Security Update = 172 This is a critical security update. It addresses potential SQL injection vulnerabilities found in previous versions. Please update immediately to ensure your site remains secure. 173 166 174 = 1.2.8 = 167 175 Thanks for using Quentn Plugin! Please update the plugin to add log quentn activities. It will also fix any namespace conflict with other plugins.
Note: See TracChangeset
for help on using the changeset viewer.