From abe2e67ed6965c303cedfd9571840471b28e7673 Mon Sep 17 00:00:00 2001 From: randhirexpresstech Date: Mon, 20 May 2024 13:35:56 +0530 Subject: [PATCH 1/3] Ajax, add date and ip column in failed submission admin dashboard table --- css/qsm-admin.css | 8 + js/qsm-admin.js | 119 ++++++++++++ php/admin/class-failed-submission.php | 173 +++++++++++++---- php/classes/class-qmn-quiz-manager.php | 256 +++++++++++++++---------- 4 files changed, 426 insertions(+), 130 deletions(-) diff --git a/css/qsm-admin.css b/css/qsm-admin.css index 1e1cad638..438614d3d 100644 --- a/css/qsm-admin.css +++ b/css/qsm-admin.css @@ -3436,4 +3436,12 @@ input#preferred-date-format-custom { min-width: 520px; background: #cccccc36; border: 1px solid #ccc; +} + +.qsm-pointer-events-none { + pointer-events: none; +} + +.display-none-notice { + display: none; } \ No newline at end of file diff --git a/js/qsm-admin.js b/js/qsm-admin.js index dc7c58849..109549894 100644 --- a/js/qsm-admin.js +++ b/js/qsm-admin.js @@ -3957,3 +3957,122 @@ var import_button; qsmHandleOperatorChange('email-condition', 'condition-default-value'); }(jQuery)); + + +/** + * QSM - failed submission data table action + */ +(function ($) { + function submit_failed_submission_action_notice( res ) { + if ( 'object' !== typeof res || null === res || undefined === res.message ) { + return false; + } + let noticeEl = $( '#qmn-failed-submission-table-message' ); + if ( 0 < noticeEl.length ) { + let remove_notice_type_class = 'success' === res.status ? 'notice-error' : 'notice-success'; + noticeEl.removeClass( remove_notice_type_class ); + noticeEl.addClass( 'notice-'+res.status ); + noticeEl.html( + '

'+res.message+'

' + ); + noticeEl.show(); + noticeEl.fadeOut( 9999 ); + } + } + + function submit_failed_submission_action_form( formData, ) { + + // check for required data + if ( undefined === formData || null === formData || -1 == formData.quiz_action || 0 === formData.post_ids.length ) { + submit_failed_submission_action_notice( { + status:"error", + message:"Missing form action or data" + } ); + return false; + } + + // quiz action + formData.action = 'qsm_action_failed_submission_table'; + + // Disable conatiner for further any action + let containerDiv = $("#qmn-failed-submission-conatiner"); + containerDiv.toggleClass('qsm-pointer-events-none'); + + // Actiion one by one + formData.post_ids.forEach( post_id => { + formData.post_id = post_id; + let action_link_wrap = $( '#action-link-'+post_id ); + let action_link_html = action_link_wrap.html(); + action_link_wrap.html( 'processing...'); + $.ajax({ + type: 'POST', + url: ajaxurl, + data: formData, + success: function (response) { + // notice. + submit_failed_submission_action_notice( response.data ); + + // enable click pointer + containerDiv.removeClass('qsm-pointer-events-none'); + + // add success icon + if ( response.success ) { + action_link_wrap.html( '' ); + } else { + action_link_wrap.html( action_link_html ); + } + + // Remove row if trashed + if ( 'trash' === formData.quiz_action ) { + $( '#qsm-submission-row-'+post_id ).remove(); + } + + }, + error: function ( jqXHR, textStatus, errorThrown ) { + // undo action link + action_link_wrap.html( action_link_html ); + + // enable click pointer + containerDiv.removeClass('qsm-pointer-events-none'); + + // error notice + submit_failed_submission_action_notice( { + status:"error", + message:errorThrown + } ); + } + }); + }); + + } + + // Submit Form. + $( document ).on( 'submit', '#failed-submission-action-form', function( e ) { + e.preventDefault(); + let formData = { + qmnnonce: $('#failed-submission-action-form input[name="qmnnonce"]').val(), + post_ids: [], + quiz_action: $('#failed-submission-action-form #bulk-action-selector-top').val() + }; + // Select all checkboxes with the name attribute 'post_id[]' + let checkedCheckboxes = $('#failed-submission-action-form input[type="checkbox"][name="post_id[]"]:checked'); + + // Iterate over each checked checkbox + checkedCheckboxes.each(function() { + formData.post_ids.push( $(this).val() ); + }); + + submit_failed_submission_action_form( formData ); + } ); + + // On click retrieve link + $( document ).on( 'click', '.qmn-retrieve-failed-submission-link', function( e ) { + e.preventDefault(); + + submit_failed_submission_action_form( { + qmnnonce: $('#failed-submission-action-form input[name="qmnnonce"]').val(), + post_ids: [ $(this).attr('post-id') ], + quiz_action:'retrieve' + } ); + } ); +}(jQuery)); \ No newline at end of file diff --git a/php/admin/class-failed-submission.php b/php/admin/class-failed-submission.php index 889ee75c0..55bff3dfd 100644 --- a/php/admin/class-failed-submission.php +++ b/php/admin/class-failed-submission.php @@ -22,7 +22,7 @@ class QmnFailedSubmissions extends WP_List_Table { /** - * Holds meta_key name + * meta_key name which contain failed submission data * * @var object * @since 9.0.2 @@ -30,21 +30,56 @@ class QmnFailedSubmissions extends WP_List_Table { public $meta_key = '_qmn_log_result_insert_data'; /** - * Holds table_data + * Variable to check if ip is enable + * + * @var object + * @since 9.0.2 + */ + public $ip_enabled = false; + + /** + * table_data * * @var object * @since 9.0.2 */ private $table_data = array(); + + /** + * Error log post ids + * + * @var object + * @since 9.0.2 + */ + private $posts = array(); + + /** + * Current Tab + * + * @var object + * @since 9.0.2 + */ + private $current_tab = array(); + + public function __construct() { parent::__construct( array( 'plural' => 'submissions', 'singular' => 'submission', - 'ajax' => false, + 'ajax' => true, ) ); + $this->current_tab = ( empty( $_GET['tab'] ) || 'retrieve' == sanitize_key( $_GET['tab'] ) ) ? 'retrieve' : 'processed'; + + // Get settings. + $settings = (array) get_option( 'qmn-settings' ); + + // ip_collection value 1 means it's disabled. + if ( empty( $settings ) || ! isset( $settings['ip_collection'] ) || '1' != $settings['ip_collection'] ) { + $this->ip_enabled = true; + } } /** @@ -52,34 +87,38 @@ public function __construct() { */ public function prepare_items() { // QMN Error log. - $posts = get_posts( + $this->posts = get_posts( array( 'post_type' => 'qmn_log', 'meta_key' => $this->meta_key, - 'post_status' => 'publish', + 'post_status' => 'publish', 'fields' => 'ids', 'posts_per_page' => -1, ) ); - $posts = empty( $posts ) ? array() : $posts; - $per_page = 20; - if ( ! empty( $posts ) ) { + $this->posts = empty( $this->posts ) ? array() : $this->posts; + $per_page = 20; + if ( ! empty( $this->posts ) ) { $current_page = intval( $this->get_pagenum() ) - 1; $post_start_postion = $per_page * $current_page; - foreach ( $posts as $index => $postID ) { + foreach ( $this->posts as $index => $postID ) { + if ( $post_start_postion > $index || $index >= ( $post_start_postion + $per_page ) ) { continue; } + $data = get_post_meta( $postID, $this->meta_key, true ); if ( empty( $data ) ) { continue; } + $data = maybe_unserialize( $data ); - if ( ! is_array( $data ) || ! empty( $data['deleted'] ) || empty( $data['qmn_array_for_variables'] ) ) { + if ( ! is_array( $data ) || ( 'processed' == $this->current_tab && empty( $data['processed'] ) ) || ( 'retrieve' == $this->current_tab && ! empty( $data['processed'] ) ) || empty( $data['qmn_array_for_variables'] ) ) { continue; } + $data['qmn_array_for_variables']['post_id'] = $postID; $this->table_data[] = $data['qmn_array_for_variables']; } @@ -88,7 +127,7 @@ public function prepare_items() { // pagination. $this->set_pagination_args( array( - 'total_items' => count( $posts ), + 'total_items' => count( $this->posts ), 'per_page' => $per_page, ) ); @@ -111,14 +150,59 @@ public function prepare_items() { * @return array */ public function get_columns() { - return array( - 'cb' => '', - 'post_id' => __( 'ID', 'quiz-master-next' ), - 'quiz_name' => __( 'Quiz Name', 'quiz-master-next' ), - 'user_name' => __( 'Name', 'quiz-master-next' ), - 'user_email' => __( 'Email', 'quiz-master-next' ), - 'submission_action' => __( 'Action', 'quiz-master-next' ), + $columns = array( + 'cb' => '', + 'post_id' => __( 'ID', 'quiz-master-next' ), + 'quiz_name' => __( 'Quiz Name', 'quiz-master-next' ), + 'quiz_time' => __( 'Time', 'quiz-master-next' ), + 'user_name' => __( 'Name', 'quiz-master-next' ), + 'user_email' => __( 'Email', 'quiz-master-next' ), ); + + if ( $this->ip_enabled ) { + $columns['user_ip'] = __( 'IP Address', 'quiz-master-next' ); + } + + $columns['submission_action'] = __( 'Action', 'quiz-master-next' ); + + return $columns; + } + + /** + * Gets the list of views available on this table. + * + * @return array + */ + protected function get_views() { + $views = array( + 'retrieve' => array( + 'label' => __( 'Resubmit', 'quiz-master-next' ), + ), + 'processed' => array( + 'label' => __( 'Processed', 'quiz-master-next' ), + ), + ); + + $view_links = array(); + + foreach ( $views as $view_id => $view ) { + $view_links[ $view_id ] = '' . esc_html( $view['label'] ) . ''; + } + + return $view_links; + } + + /** + * Generates content for a single row of the table. + * + * @since 9.0.2 + * + * @param object|array $item The current item + */ + public function single_row( $submission ) { + echo ''; + $this->single_row_columns( $submission ); + echo ''; } /** @@ -148,14 +232,26 @@ public function column_default( $submission, $column_name ) { case 'quiz_name': $column_value = $submission['quiz_name']; break; + case 'quiz_time': + $column_value = gmdate( 'd-m-Y', strtotime( $submission['time_taken'] ) ); + break; case 'user_name': $column_value = $submission['user_name']; break; case 'user_email': $column_value = $submission['user_email']; break; + case 'user_ip': + $column_value = $submission['user_ip']; + break; case 'submission_action': - $column_value = '' . __( 'Retrieve', 'quiz-master-next' ) . ''; + $column_value = ''; + if ( 'processed' == $this->current_tab ) { + $column_value .= ''; + } else { + $column_value .= '' . __( 'Resubmit', 'quiz-master-next' ) . ''; + } + $column_value .= ''; break; default: break; @@ -179,21 +275,30 @@ public function get_bulk_actions() { public function render_list_table() { $this->prepare_items(); - // header. - echo '
'; - // heading. - echo '

' . esc_html__( 'Failed Submissions', 'quiz-master-next' ) . '

'; - // body. - echo '
'; - echo "
"; - echo '
'; - $this->views(); - echo '
'; - echo ''; - $this->display(); - echo '
'; - echo '
'; - echo '
'; + ?> + +
+ +

+ +
+ +
+
+
+
+ views(); // render bulk action and pagination + ?> +
+ + display(); // render table + ?> +
+
+
+ = $postID ) { - continue; - } + public function process_action_failed_submission_table() { + + if ( empty( $_POST['post_id'] ) || empty( $_POST['quiz_action'] ) || ! function_exists( 'is_admin' ) || ! is_admin() || empty( $_POST['qmnnonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['qmnnonce'] ), 'qmn_failed_submission' ) ) { + wp_send_json_error( + array( + 'status' => 'error', + 'message' => __( 'Missing or incorrect input', 'quiz-master-next' ), + ) + ); + } + $post_ids = wp_unslash( $_POST['post_id'] ); + $post_ids = is_array( $post_ids ) ? array_map( 'sanitize_key', $post_ids ) : array( sanitize_key( $post_ids ) ); + $action = wp_unslash( sanitize_key( $_POST['quiz_action'] ) ); + if ( ! empty( $post_ids ) ) { + foreach ( $post_ids as $postID ) { - // Retrieve action. - if ( 'retrieve' === $action ) { - $data = get_post_meta( $postID, $this->meta_key, true ); - if ( ! empty( $data ) ) { - $data = maybe_unserialize( $data ); - if ( false !== $this->add_quiz_results( $data ) ) { - $this->delete_failed_submission( $postID, $data ); - } - } - } else if ( 'trash' === $action ) { - // Delete action - $this->delete_failed_submission( $postID, $data ); - } - } - } + $postID = intval( $postID ); - wp_safe_redirect( - add_query_arg( - array( - 'page' => 'mlw_quiz_failed_submission', - ), - admin_url( 'admin.php' ) - ) - ); - } - } + // Continue if postID not valid + if ( 0 >= $postID ) { + continue; + } + + $data = get_post_meta( $postID, $this->meta_key, true ); + + if ( empty( $data ) ) { + wp_send_json_error( + array( + 'status' => 'error', + 'message' => __( 'Details not found', 'quiz-master-next' ), + 'data' => $data, + ) + ); + } + + $data = maybe_unserialize( $data ); + + // Retrieve action. + if ( 'retrieve' === $action ) { + $res = $this->add_quiz_results( $data ); + if ( false !== $res ) { + $data['processed'] = 1; + // Mark submission processed. + update_post_meta( $postID, $this->meta_key, maybe_serialize( $data ) ); + + // return success message. + wp_send_json_success( + array( + 'res' => $res, + 'status' => 'success', + 'message' => __( 'Quiz resubmitted successfully.', 'quiz-master-next' ), + ) + ); + } else { + // return error details. + global $wpdb; + wp_send_json_error( + array( + 'status' => 'error', + 'message' => $wpdb->last_error, + ) + ); + } + } elseif ( 'trash' === $action ) { + + // Change Error log post status to trash. Error log contain failed submission data as a post meta + wp_update_post( + array( + 'ID' => $postID, + 'post_status' => 'trash', + ) + ); + + // return success message. + wp_send_json_success( + array( + 'status' => 'success', + 'message' => __( 'Quiz deleted successfully.', 'quiz-master-next' ), + ) + ); + } + } + } + + wp_send_json_error( + array( + 'status' => 'error', + 'message' => __( 'Missing input', 'quiz-master-next' ), + ) + ); + } /** * Delete failed submission log @@ -1799,69 +1847,85 @@ public function qsm_get_quiz_to_reload() { /** * Add quiz result - * + * * @since 9.0.2 * @param array $data required data ( i.e. qmn_array_for_variables, results_array, unique_id, http_referer, form_type ) for adding quiz result - * + * * @return boolean results added or not */ private function add_quiz_results( $data ) { global $wpdb; - if ( empty( $wpdb ) || empty( $data['qmn_array_for_variables'] ) || empty( $data['results_array'] ) || empty( $data['unique_id'] ) || empty( $data['http_referer'] ) || ! isset( $data['form_type'] ) ) { + if ( empty( $wpdb ) || empty( $data['qmn_array_for_variables'] ) || empty( $data['results_array'] ) || empty( $data['unique_id'] ) || empty( $data['http_referer'] ) || ! isset( $data['form_type'] ) ) { return false; } // Inserts the responses in the database. $table_name = $wpdb->prefix . 'mlw_results'; - return $wpdb->insert( - $table_name, - array( - 'quiz_id' => $data['qmn_array_for_variables']['quiz_id'], - 'quiz_name' => $data['qmn_array_for_variables']['quiz_name'], - 'quiz_system' => $data['qmn_array_for_variables']['quiz_system'], - 'point_score' => $data['qmn_array_for_variables']['total_points'], - 'correct_score' => $data['qmn_array_for_variables']['total_score'], - 'correct' => $data['qmn_array_for_variables']['total_correct'], - 'total' => $data['qmn_array_for_variables']['total_questions'], - 'name' => $data['qmn_array_for_variables']['user_name'], - 'business' => $data['qmn_array_for_variables']['user_business'], - 'email' => $data['qmn_array_for_variables']['user_email'], - 'phone' => $data['qmn_array_for_variables']['user_phone'], - 'user' => $data['qmn_array_for_variables']['user_id'], - 'user_ip' => $data['qmn_array_for_variables']['user_ip'], - 'time_taken' => $data['qmn_array_for_variables']['time_taken'], - 'time_taken_real' => gmdate( 'Y-m-d H:i:s', strtotime( $data['qmn_array_for_variables']['time_taken'] ) ), - 'quiz_results' => maybe_serialize( $data['results_array'] ), - 'deleted' => 0, - 'unique_id' => $data['unique_id'], - 'form_type' => $data['form_type'], - 'page_url' => $data['http_referer'], - 'page_name' => url_to_postid( $data['http_referer'] ) ? get_the_title( url_to_postid( $data['http_referer'] ) ) : '', - ), - array( - '%d', - '%s', - '%d', - '%f', - '%d', - '%d', - '%d', - '%s', - '%s', - '%s', - '%s', - '%d', - '%s', - '%s', - '%s', - '%s', - '%d', - '%s', - '%d', - '%s', - '%s', - ) - ); + + // Temporarily suppress error reporting + $wpdb->suppress_errors(); + + try { + $res = $wpdb->insert( + $table_name, + array( + 'quiz_id' => $data['qmn_array_for_variables']['quiz_id'], + 'quiz_name' => $data['qmn_array_for_variables']['quiz_name'], + 'quiz_system' => $data['qmn_array_for_variables']['quiz_system'], + 'point_score' => $data['qmn_array_for_variables']['total_points'], + 'correct_score' => $data['qmn_array_for_variables']['total_score'], + 'correct' => $data['qmn_array_for_variables']['total_correct'], + 'total' => $data['qmn_array_for_variables']['total_questions'], + 'name' => $data['qmn_array_for_variables']['user_name'], + 'business' => $data['qmn_array_for_variables']['user_business'], + 'email' => $data['qmn_array_for_variables']['user_email'], + 'phone' => $data['qmn_array_for_variables']['user_phone'], + 'user' => $data['qmn_array_for_variables']['user_id'], + 'user_ip' => $data['qmn_array_for_variables']['user_ip'], + 'time_taken' => $data['qmn_array_for_variables']['time_taken'], + 'time_taken_real' => gmdate( 'Y-m-d H:i:s', strtotime( $data['qmn_array_for_variables']['time_taken'] ) ), + 'quiz_results' => maybe_serialize( $data['results_array'] ), + 'deleted' => 0, + 'unique_id' => $data['unique_id'], + 'form_type' => $data['form_type'], + 'page_url' => $data['http_referer'], + 'page_name' => url_to_postid( $data['http_referer'] ) ? get_the_title( url_to_postid( $data['http_referer'] ) ) : '', + ), + array( + '%d', + '%s', + '%d', + '%f', + '%d', + '%d', + '%d', + '%s', + '%s', + '%s', + '%s', + '%d', + '%s', + '%s', + '%s', + '%s', + '%d', + '%s', + '%d', + '%s', + '%s', + ) + ); + if ( false === $res ) { + // Throw exception + throw new Exception( 'Database insert failed.' ); + } + // If insert is successful, return response + return $res; + } catch ( Exception $e ) { + return false; + } + + return false; } /** From 431aea81968c3d69d7c29394c550980c5658a220 Mon Sep 17 00:00:00 2001 From: randhirexpresstech Date: Mon, 20 May 2024 14:00:28 +0530 Subject: [PATCH 2/3] add comments in failed submission class --- php/admin/class-failed-submission.php | 43 +++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/php/admin/class-failed-submission.php b/php/admin/class-failed-submission.php index 55bff3dfd..1cdb59395 100644 --- a/php/admin/class-failed-submission.php +++ b/php/admin/class-failed-submission.php @@ -84,6 +84,10 @@ public function __construct() { /** * Prepares the list of items for displaying. + * + * @since 9.0.2 + * + * @return void */ public function prepare_items() { // QMN Error log. @@ -146,8 +150,10 @@ public function prepare_items() { /** * Gets a list of columns. - * - * @return array + * + * @since 9.0.2 + * + * @return array columns list */ public function get_columns() { $columns = array( @@ -171,7 +177,9 @@ public function get_columns() { /** * Gets the list of views available on this table. * - * @return array + * @since 9.0.2 + * + * @return array tabs link */ protected function get_views() { $views = array( @@ -208,7 +216,9 @@ public function single_row( $submission ) { /** * Gets a list of hidden columns. * - * @return array + * @since 9.0.2 + * + * @return array hidden column name */ public function get_hidden_columns() { return array( @@ -216,6 +226,13 @@ public function get_hidden_columns() { ); } + /** + * Checkbox to select submissions. + * + * @since 9.0.2 + * + * @return html input checkbox + */ public function column_cb( $submission ) { return sprintf( ' ', @@ -223,6 +240,13 @@ public function column_cb( $submission ) { ); } + /** + * Column value + * + * @since 9.0.2 + * + * @return string specific column value + */ public function column_default( $submission, $column_name ) { $column_value = ''; switch ( $column_name ) { @@ -260,6 +284,13 @@ public function column_default( $submission, $column_name ) { return $column_value; } + /** + * Bulk action + * + * @since 9.0.2 + * + * @return array actions + */ public function get_bulk_actions() { return array( 'retrieve' => __( 'Retrieve', 'quiz-master-next' ), @@ -270,7 +301,9 @@ public function get_bulk_actions() { /** * Render page with this table * - * @param class $store + * @since 9.0.2 + * + * @return HTML failed submission page */ public function render_list_table() { From 861394b8c831973bda4cdcbd461c27178e211b82 Mon Sep 17 00:00:00 2001 From: randhirexpresstech Date: Mon, 20 May 2024 14:02:56 +0530 Subject: [PATCH 3/3] correct comments in failed submission class --- php/admin/class-failed-submission.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/php/admin/class-failed-submission.php b/php/admin/class-failed-submission.php index 1cdb59395..0b59acfcf 100644 --- a/php/admin/class-failed-submission.php +++ b/php/admin/class-failed-submission.php @@ -205,7 +205,9 @@ protected function get_views() { * * @since 9.0.2 * - * @param object|array $item The current item + * @param object|array $submission The current item + * + * @return void */ public function single_row( $submission ) { echo '';