* @return void */ public function ajax_delete_redirect() { $this->verify_request(); if ( ! $this->security->check_pin_session( 'delete' ) ) { wp_send_json_error( 'pin_required' ); } global $wpdb; $id = absint( $_POST['id'] ?? 0 ); $redirect = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->redirects->get_table()} WHERE id = %d", $id ), ARRAY_A ); $result = $wpdb->delete( $this->redirects->get_table(), array( 'id' => $id ), array( '%d' ) ); if ( $result ) { $this->audit->log( 'redirect_deleted', 'redirect', $id, $redirect ); wp_send_json_success( __( 'Przekierowanie usunięte', '404-to-301-monitor-pro' ) ); } else { wp_send_json_error( __( 'Błąd podczas usuwania', '404-to-301-monitor-pro' ) ); } } /** * AJAX: Bulk delete redirects. * * @since 3.0.0 * @return void */ public function ajax_bulk_delete_redirects() { $this->verify_request(); if ( ! $this->security->check_pin_session( 'bulk_delete' ) ) { wp_send_json_error( 'pin_required' ); } $ids = isset( $_POST['ids'] ) ? array_map( 'absint', (array) $_POST['ids'] ) : array(); $ids = array_filter( $ids ); if ( empty( $ids ) ) { wp_send_json_error( __( 'Nie wybrano przekierowań', '404-to-301-monitor-pro' ) ); } global $wpdb; $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->redirects->get_table()} WHERE id IN ($placeholders)", $ids ) ); if ( $deleted ) { $this->audit->log( 'bulk_delete_redirects', 'redirect', 0, null, array( 'count' => $deleted ) ); /* translators: %d: number of deleted redirects */ wp_send_json_success( sprintf( __( 'Usunięto %d przekierowań', '404-to-301-monitor-pro' ), $deleted ) ); } else { wp_send_json_error( __( 'Błąd podczas usuwania', '404-to-301-monitor-pro' ) ); } } /** * AJAX: Bulk activate/deactivate redirects. * * @since 3.0.0 * @return void */ public function ajax_bulk_toggle_redirects() { $this->verify_request(); $ids = isset( $_POST['ids'] ) ? array_map( 'absint', (array) $_POST['ids'] ) : array(); $ids = array_filter( $ids ); $action = sanitize_text_field( wp_unslash( $_POST['toggle_action'] ?? '' ) ); if ( empty( $ids ) || ! in_array( $action, array( 'activate', 'deactivate' ), true ) ) { wp_send_json_error( __( 'Nieprawidłowe dane', '404-to-301-monitor-pro' ) ); } global $wpdb; $is_active = 'activate' === $action ? 1 : 0; $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $updated = $wpdb->query( $wpdb->prepare( "UPDATE {$this->redirects->get_table()} SET is_active = %d WHERE id IN ($placeholders)", array_merge( array( $is_active ), $ids ) ) ); if ( false !== $updated ) { $this->audit->log( 'bulk_toggle_redirects', 'redirect', 0, null, array( 'action' => $action, 'count' => $updated ) ); /* translators: %d: number of redirects */ $msg = 'activate' === $action ? sprintf( __( 'Aktywowano %d przekierowań', '404-to-301-monitor-pro' ), $updated ) : sprintf( __( 'Dezaktywowano %d przekierowań', '404-to-301-monitor-pro' ), $updated ); wp_send_json_success( $msg ); } else { wp_send_json_error( __( 'Błąd podczas aktualizacji', '404-to-301-monitor-pro' ) ); } } /** * AJAX: Duplicate a redirect. * * @since 3.0.0 * @return void */ public function ajax_duplicate_redirect() { $this->verify_request(); global $wpdb; $id = absint( $_POST['id'] ?? 0 ); $redirect = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->redirects->get_table()} WHERE id = %d", $id ), ARRAY_A ); if ( ! $redirect ) { wp_send_json_error( __( 'Przekierowanie nie istnieje', '404-to-301-monitor-pro' ) ); } unset( $redirect['id'] ); $redirect['source_url'] .= '-copy'; $redirect['source_hash'] = $this->security->hash_url( $redirect['source_url'] ); $redirect['created_by'] = get_current_user_id(); $redirect['created_at'] = current_time( 'mysql' ); $redirect['updated_at'] = current_time( 'mysql' ); $redirect['hit_count'] = 0; $result = $wpdb->insert( $this->redirects->get_table(), $redirect ); if ( $result ) { $this->audit->log( 'redirect_duplicated', 'redirect', $wpdb->insert_id ); wp_send_json_success( array( 'id' => $wpdb->insert_id, 'message' => __( 'Przekierowanie skopiowane', '404-to-301-monitor-pro' ) ) ); } else { wp_send_json_error( __( 'Błąd podczas kopiowania', '404-to-301-monitor-pro' ) ); } } /* ------------------------------------------------------------------ */ /* 404 MANAGEMENT */ /* ------------------------------------------------------------------ */ /** * AJAX: Delete a single 404 entry. * * @since 3.0.0 * @return void */ public function ajax_delete_404() { $this->verify_request(); global $wpdb; $id = absint( $_POST['id'] ?? 0 ); $result = $wpdb->delete( $wpdb->prefix . '404_monitor_pro', array( 'id' => $id ), array( '%d' ) ); if ( $result ) { wp_send_json_success( __( 'Wpis usunięty', '404-to-301-monitor-pro' ) ); } else { wp_send_json_error( __( 'Błąd podczas usuwania', '404-to-301-monitor-pro' ) ); } } /** * AJAX: Bulk delete all 404 entries. * * @since 3.0.0 * @return void */ public function ajax_bulk_delete_404() { $this->verify_request(); if ( ! $this->security->check_pin_session( 'bulk_delete' ) ) { wp_send_json_error( 'pin_required' ); } $confirm = sanitize_text_field( wp_unslash( $_POST['confirm_truncate'] ?? '' ) ); if ( 'DELETE_ALL_LOGS' !== $confirm ) { wp_send_json_error( __( 'Brak potwierdzenia operacji', '404-to-301-monitor-pro' ) ); } global $wpdb; $deleted = $wpdb->query( "DELETE FROM {$wpdb->prefix}404_monitor_pro" ); $this->audit->log( 'bulk_delete_404', '404_logs', 0, null, array( 'count' => $deleted ) ); wp_send_json_success( __( 'Wszystkie wpisy usunięte', '404-to-301-monitor-pro' ) ); } /** * AJAX: Bulk delete selected 404 entries. * * @since 3.0.0 * @return void */ public function ajax_bulk_delete_selected_404() { $this->verify_request(); $ids = isset( $_POST['ids'] ) ? array_map( 'absint', (array) $_POST['ids'] ) : array(); $ids = array_filter( $ids ); if ( empty( $ids ) ) { wp_send_json_error( __( 'Nie wybrano wpisów', '404-to-301-monitor-pro' ) ); } global $wpdb; $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}404_monitor_pro WHERE id IN ($placeholders)", $ids ) ); if ( $deleted ) { /* translators: %d: number of deleted entries */ wp_send_json_success( sprintf( __( 'Usunięto %d wpisów', '404-to-301-monitor-pro' ), $deleted ) ); } else { wp_send_json_error( __( 'Błąd podczas usuwania', '404-to-301-monitor-pro' ) ); } } /** * AJAX: Bulk resolve (mark as resolved) 404 entries. * * @since 3.0.0 * @return void */ public function ajax_bulk_resolve_404() { $this->verify_request(); $ids = isset( $_POST['ids'] ) ? array_map( 'absint', (array) $_POST['ids'] ) : array(); $ids = array_filter( $ids ); if ( empty( $ids ) ) { wp_send_json_error( __( 'Nie wybrano wpisów', '404-to-301-monitor-pro' ) ); } global $wpdb; $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $updated = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}404_monitor_pro SET status = 'resolved' WHERE id IN ($placeholders)", $ids ) ); if ( false !== $updated ) { /* translators: %d: number of resolved entries */ wp_send_json_success( sprintf( __( 'Oznaczono %d jako rozwiązane', '404-to-301-monitor-pro' ), $updated ) ); } else { wp_send_json_error( __( 'Błąd podczas aktualizacji', '404-to-301-monitor-pro' ) ); } } /* ------------------------------------------------------------------ */ /* EXPORT / IMPORT */ /* ------------------------------------------------------------------ */ /** * AJAX: Export redirects to CSV. * * @since 3.0.0 * @return void */ public function ajax_export_redirects() { if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'monitor_404_nonce' ) ) { wp_die( esc_html__( 'Nieprawidłowy token', '404-to-301-monitor-pro' ) ); } if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Brak uprawnień', '404-to-301-monitor-pro' ) ); } global $wpdb; $redirects = $wpdb->get_results( "SELECT source_url, target_url, redirect_type, match_type, query_string_handling, device_type, is_active, hit_count FROM {$this->redirects->get_table()} ORDER BY created_at DESC", ARRAY_A ); $filename = 'redirects-' . gmdate( 'Y-m-d' ) . '.csv'; header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename=' . $filename ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); $output = fopen( 'php://output', 'w' ); // Add BOM for Excel UTF-8 compatibility. fwrite( $output, "\xEF\xBB\xBF" ); fputcsv( $output, array( 'Source URL', 'Target URL', 'Type', 'Match Type', 'Query Handling', 'Device', 'Active', 'Hits' ) ); foreach ( $redirects as $redirect ) { fputcsv( $output, $redirect ); } fclose( $output ); exit; } /** * AJAX: Export 404 logs to CSV. * * @since 3.0.0 * @return void */ public function ajax_export_404() { if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'monitor_404_nonce' ) ) { wp_die( esc_html__( 'Nieprawidłowy token', '404-to-301-monitor-pro' ) ); } if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Brak uprawnień', '404-to-301-monitor-pro' ) ); } global $wpdb; $table = $wpdb->prefix . '404_monitor_pro'; // Support filtered export. $where = array( '1=1' ); $args = array(); $filter_bots = isset( $_GET['filter_bots'] ) ? intval( $_GET['filter_bots'] ) : -1; $filter_cat = isset( $_GET['filter_category'] ) ? sanitize_text_field( wp_unslash( $_GET['filter_category'] ) ) : ''; $filter_status = isset( $_GET['filter_status'] ) ? sanitize_text_field( wp_unslash( $_GET['filter_status'] ) ) : ''; if ( $filter_bots >= 0 ) { $where[] = 'is_bot = %d'; $args[] = $filter_bots; } if ( ! empty( $filter_cat ) ) { $where[] = 'bot_category = %s'; $args[] = $filter_cat; } if ( ! empty( $filter_status ) ) { $where[] = 'status = %s'; $args[] = $filter_status; } $where_sql = implode( ' AND ', $where ); if ( ! empty( $args ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $results = $wpdb->get_results( $wpdb->prepare( "SELECT url, referer, user_agent, ip_address, hit_count, is_bot, bot_name, bot_category, status, first_detected, last_detected FROM {$table} WHERE {$where_sql} ORDER BY last_detected DESC", $args ), ARRAY_A ); } else { $results = $wpdb->get_results( "SELECT url, referer, user_agent, ip_address, hit_count, is_bot, bot_name, bot_category, status, first_detected, last_detected FROM {$table} ORDER BY last_detected DESC", ARRAY_A ); } $filename = '404-errors-' . gmdate( 'Y-m-d' ) . '.csv'; header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename=' . $filename ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); $output = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions fwrite( $output, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions fputcsv( $output, array( 'URL', 'Referer', 'User Agent', 'IP', 'Hits', 'Bot', 'Bot Name', 'Category', 'Status', 'First Detected', 'Last Detected' ) ); foreach ( $results as $row ) { fputcsv( $output, $row ); } fclose( $output ); // phpcs:ignore WordPress.WP.AlternativeFunctions exit; } /** * AJAX: Import redirects from CSV. * * @since 3.0.0 * @return void */ public function ajax_import_redirects() { $this->verify_request(); if ( ! $this->security->check_pin_session( 'import' ) ) { wp_send_json_error( 'pin_required' ); } if ( ! isset( $_FILES['file'] ) ) { wp_send_json_error( __( 'Brak pliku', '404-to-301-monitor-pro' ) ); } $file_info = wp_check_filetype( $_FILES['file']['name'] ); if ( 'csv' !== $file_info['ext'] ) { wp_send_json_error( __( 'Dozwolone są tylko pliki CSV', '404-to-301-monitor-pro' ) ); } $file = $_FILES['file']['tmp_name']; $handle = fopen( $file, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions if ( false === $handle ) { wp_send_json_error( __( 'Nie można otworzyć pliku', '404-to-301-monitor-pro' ) ); } global $wpdb; $imported = 0; $skipped = 0; $header = true; $current_time = current_time( 'mysql' ); while ( ( $data = fgetcsv( $handle, 2048, ',' ) ) !== false ) { if ( $header ) { $header = false; continue; } if ( count( $data ) < 3 ) { ++$skipped; continue; } $source = $this->security->sanitize_url( $data[0] ); $target = esc_url_raw( $data[1] ); $type = isset( $data[2] ) ? absint( $data[2] ) : 301; $match_type = isset( $data[3] ) ? sanitize_text_field( $data[3] ) : 'exact'; $query_handling = isset( $data[4] ) ? sanitize_text_field( $data[4] ) : 'ignore'; $device_type = isset( $data[5] ) ? sanitize_text_field( $data[5] ) : 'all'; if ( ! in_array( $type, array( 301, 302, 307, 308 ), true ) ) { $type = 301; } if ( ! in_array( $match_type, array( 'exact', 'regex', 'contains' ), true ) ) { $match_type = 'exact'; } if ( ! in_array( $query_handling, array( 'ignore', 'append', 'replace' ), true ) ) { $query_handling = 'ignore'; } if ( ! in_array( $device_type, array( 'all', 'mobile', 'desktop' ), true ) ) { $device_type = 'all'; } if ( $this->security->validate_redirect_url( $target ) ) { $wpdb->insert( $this->redirects->get_table(), array( 'source_url' => $source, 'source_hash' => $this->security->hash_url( $source ), 'target_url' => $target, 'redirect_type' => $type, 'match_type' => $match_type, 'query_string_handling' => $query_handling, 'device_type' => $device_type, 'is_active' => 1, 'created_by' => get_current_user_id(), 'created_at' => $current_time, 'updated_at' => $current_time, ), array( '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%d', '%d', '%s', '%s' ) ); ++$imported; } else { ++$skipped; } } fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions $this->audit->log( 'import_redirects', 'redirect', 0, null, array( 'imported' => $imported, 'skipped' => $skipped ) ); wp_send_json_success( array( 'imported' => $imported, 'skipped' => $skipped ) ); } /** * AJAX: Import redirects from another plugin (Redirection). * * @since 3.0.0 * @return void */ public function ajax_import_from_plugin() { $this->verify_request(); if ( ! $this->security->check_pin_session( 'import' ) ) { wp_send_json_error( 'pin_required' ); } $plugin = sanitize_text_field( wp_unslash( $_POST['plugin'] ?? '' ) ); global $wpdb; $imported = 0; $current_time = current_time( 'mysql' ); switch ( $plugin ) { case 'redirection': $source_table = $wpdb->prefix . 'redirection_items'; $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $source_table ) ); if ( ! $table_exists ) { wp_send_json_error( __( 'Tabela Redirection nie znaleziona', '404-to-301-monitor-pro' ) ); } $redirects = $wpdb->get_results( $wpdb->prepare( "SELECT url, action_data, action_code FROM {$source_table} WHERE status = %s", 'enabled' ), ARRAY_A ); foreach ( $redirects as $redirect ) { $source = $this->security->sanitize_url( $redirect['url'] ); $target = esc_url_raw( $redirect['action_data'] ); $type = absint( $redirect['action_code'] ); if ( $this->security->validate_redirect_url( $target ) ) { $wpdb->insert( $this->redirects->get_table(), array( 'source_url' => $source, 'source_hash' => $this->security->hash_url( $source ), 'target_url' => $target, 'redirect_type' => $type, 'match_type' => 'exact', 'is_active' => 1, 'created_by' => get_current_user_id(), 'created_at' => $current_time, 'updated_at' => $current_time, ), array( '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%s', '%s' ) ); ++$imported; } } break; default: wp_send_json_error( __( 'Nieobsługiwany plugin', '404-to-301-monitor-pro' ) ); } $this->audit->log( 'import_from_plugin', 'redirect', 0, null, array( 'plugin' => $plugin, 'imported' => $imported ) ); wp_send_json_success( array( 'imported' => $imported ) ); } /** * AJAX: Verify PIN. * * @since 3.0.0 * @return void */ public function ajax_verify_pin() { $this->verify_request(); $pin = sanitize_text_field( wp_unslash( $_POST['pin'] ?? '' ) ); if ( $this->security->verify_pin( $pin ) ) { $this->security->set_pin_verified(); wp_send_json_success( __( 'PIN zweryfikowany', '404-to-301-monitor-pro' ) ); } else { wp_send_json_error( __( 'Nieprawidłowy PIN', '404-to-301-monitor-pro' ) ); } } } https://techwebsite.pl/post-sitemap.xml 2026-02-26T05:37:28+00:00 https://techwebsite.pl/post-sitemap2.xml 2026-02-26T05:37:28+00:00 https://techwebsite.pl/page-sitemap.xml 2026-02-16T13:01:47+00:00 https://techwebsite.pl/product-sitemap.xml 2025-11-24T13:40:06+00:00 https://techwebsite.pl/category-sitemap.xml 2026-02-26T05:37:28+00:00 https://techwebsite.pl/post_tag-sitemap.xml 2024-10-31T18:02:25+00:00 https://techwebsite.pl/product_cat-sitemap.xml 2025-11-24T13:40:06+00:00 https://techwebsite.pl/product_tag-sitemap.xml 2025-03-10T07:44:54+00:00