PluginProbe
Document Embedder – let visitors read files without downloading / 2.1.2
Document Embedder – let visitors read files without downloading v2.1.2
2.3.1 2.3.0 2.2.1 2.2.0 2.1.2 2.1.1 trunk 1.0 1.1 1.2 1.3 1.7.4 1.7.6 1.7.9 1.8.1 1.8.2 1.8.3 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 2.0.0 2.0.1 2.0.2 All 30 releases
document-emberdder / includes / features / class-bplde-ajaxcall.php

class-bplde-ajaxcall.php in Document Embedder – let visitors read files without downloading 2.1.2, at includes/features/class-bplde-ajaxcall.php

305 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * BPLDE AJAX Call Controller.
4 *
5 * @package DocumentEmbedder
6 */
7
8 namespace BPLDE\Model;
9
10 use BPLDE\Helper\Functions;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 if ( ! class_exists( 'AJAXCall' ) ) {
17 class AJAXCall {
18 protected static $_instance = null;
19
20 public static function instance() {
21 if ( self::$_instance === null ) {
22 self::$_instance = new self();
23 }
24 return self::$_instance;
25 }
26
27 public function __construct() {
28 // 1. Leads REST / Export
29 add_action( 'wp_ajax_de_export_leads_csv', [$this, 'handle_export_csv'] );
30
31 // 2. Document Library
32 add_action( 'wp_ajax_bplde_save_document_library', [$this, 'bplde_save_document_library'] );
33 add_action( 'wp_ajax_bplde_get_single', [$this, 'bplde_get_single'] );
34 add_action( 'wp_ajax_bplde_delete_document_library', [$this, 'bplde_delete_document_library'] );
35 add_action( 'wp_ajax_bplde_get_all', [$this, 'bplde_get_all'] );
36
37 // 3. Track download
38 add_action( 'wp_ajax_de_track_download', [$this, 'de_track_download'] );
39 add_action( 'wp_ajax_nopriv_de_track_download', [$this, 'de_track_download'] );
40
41 // 4. Get Doc Meta
42 add_action( 'wp_ajax_pdfp_get_doc_meta', [$this, 'single_doc_callback'] );
43 }
44
45 // --- Leads CSV Export Handlers ---
46 public function handle_export_csv() {
47 if ( ! current_user_can( 'manage_options' ) ) {
48 wp_die( 'Unauthorized' );
49 }
50
51 $doc_id = isset( $_GET['doc_id'] ) ? intval( $_GET['doc_id'] ) : 0;
52 if ( ! $doc_id ) {
53 wp_die( 'Invalid document ID' );
54 }
55
56 check_admin_referer( 'de_export_leads_csv', 'nonce' );
57
58 global $wpdb;
59 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
60 $leads = $wpdb->get_results( $wpdb->prepare(
61 "SELECT id, name, email, document_id, document_title, downloaded_at, ip_address FROM {$wpdb->prefix}docembedder_leads WHERE document_id = %d ORDER BY downloaded_at DESC",
62 $doc_id
63 ), ARRAY_A );
64
65 header( 'Content-Type: text/csv' );
66 header( 'Content-Disposition: attachment; filename="doc-leads-' . $doc_id . '-' . gmdate( 'Y-m-d' ) . '.csv"' );
67 header( 'Pragma: no-cache' );
68 header( 'Expires: 0' );
69
70 $output = fopen( 'php://output', 'w' );
71 fputcsv( $output, ['ID', 'Name', 'Email', 'Document ID', 'Document Title', 'Downloaded At', 'IP Address'] );
72
73 foreach ( $leads as $lead ) {
74 fputcsv( $output, $lead );
75 }
76
77 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- php://output has no WP_Filesystem equivalent
78 fclose( $output );
79 exit;
80 }
81
82 // --- Document Library Handlers ---
83 public function bplde_save_document_library() {
84 check_ajax_referer( 'bplde_nonce', 'nonce' );
85
86 if ( ! current_user_can( 'edit_posts' ) ) {
87 wp_send_json_error( ['message' => 'Unauthorized.'] );
88 }
89
90 $id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0;
91 $title = isset( $_POST['title'] ) ? sanitize_text_field( wp_unslash( $_POST['title'] ) ) : 'Untitled';
92 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- recursive sanitization applied via $sanitize_settings() below
93 $settings = isset( $_POST['settings'] ) ? json_decode( wp_unslash( $_POST['settings'] ), true ) : [];
94
95 // Deep validation & recursive sanitization of settings array
96 $sanitize_settings = function( $array ) use ( &$sanitize_settings ) {
97 if ( ! is_array( $array ) ) {
98 return sanitize_text_field( $array );
99 }
100 $clean = [];
101 foreach ( $array as $key => $val ) {
102 $clean_key = preg_replace( '/[^a-zA-Z0-9_\-]/', '', $key );
103 if ( is_array( $val ) ) {
104 $clean[$clean_key] = $sanitize_settings( $val );
105 } else {
106 if ( filter_var( $val, FILTER_VALIDATE_URL ) ) {
107 $clean[$clean_key] = esc_url_raw( $val );
108 } elseif ( is_numeric( $val ) ) {
109 $clean[$clean_key] = $val;
110 } elseif ( is_bool( $val ) ) {
111 $clean[$clean_key] = $val;
112 } else {
113 $clean[$clean_key] = sanitize_text_field( $val );
114 }
115 }
116 }
117 return $clean;
118 };
119 $settings = $sanitize_settings( $settings );
120
121 $post_data = [
122 'post_title' => $title,
123 'post_type' => 'document_library',
124 'post_status' => 'publish',
125 ];
126
127 if ( $id > 0 ) {
128 if ( ! current_user_can( 'edit_post', $id ) ) {
129 wp_send_json_error( ['message' => 'Unauthorized to edit this document.'] );
130 }
131
132 $post_data['ID'] = $id;
133 $result = wp_update_post( $post_data, true );
134 } else {
135 $result = wp_insert_post( $post_data, true );
136 }
137
138 if ( is_wp_error( $result ) ) {
139 wp_send_json_error( ['message' => $result->get_error_message()] );
140 } else {
141 update_post_meta( $result, 'bplde_settings', $settings );
142
143 wp_send_json_success( [
144 'id' => $result,
145 'settings' => $settings,
146 'created' => get_the_date( 'Y/m/d \a\t g:i a', $result )
147 ] );
148 }
149 }
150
151 public function bplde_get_single() {
152 check_ajax_referer( 'bplde_nonce', 'nonce' );
153
154 $id = intval( $_GET['id'] ?? 0 );
155
156 if ( ! $id ) {
157 wp_send_json_error( ['message' => 'Invalid ID'] );
158 }
159
160 $post = get_post( $id );
161
162 if ( ! $post ) {
163 wp_send_json_error( ['message' => 'Post not found'] );
164 }
165
166 if ( ! current_user_can( 'edit_post', $id ) ) {
167 wp_send_json_error( ['message' => 'Unauthorized.'] );
168 }
169
170 $settings = get_post_meta( $id, 'bplde_settings', true );
171
172 wp_send_json_success( [
173 'id' => $id,
174 'title' => $post->post_title,
175 'settings' => $settings,
176 'created' => get_the_date( 'Y/m/d \a\t g:i a', $id )
177 ] );
178 }
179
180 public function bplde_get_all() {
181 check_ajax_referer( 'bplde_nonce', 'nonce' );
182
183 if ( ! current_user_can( 'edit_posts' ) ) {
184 wp_send_json_error( ['message' => 'Unauthorized.'] );
185 }
186
187 $query = new \WP_Query( [
188 'post_type' => 'document_library',
189 'post_status' => 'publish',
190 'posts_per_page' => -1,
191 'author' => get_current_user_id(),
192 ] );
193
194 $items = [];
195 foreach ( $query->posts as $post ) {
196 $settings = get_post_meta( $post->ID, 'bplde_settings', true );
197
198 $items[] = [
199 'id' => $post->ID,
200 'title' => $post->post_title,
201 'settings' => $settings,
202 'created' => get_the_date( 'Y/m/d \a\t g:i a', $post )
203 ];
204 }
205
206 wp_send_json_success( $items );
207 }
208
209 public function bplde_delete_document_library() {
210 check_ajax_referer( 'bplde_nonce', 'nonce' );
211
212 $id = intval( $_POST['id'] ?? 0 );
213 if ( ! $id ) {
214 wp_send_json_error( ['message' => 'Invalid ID'] );
215 }
216
217 if ( ! current_user_can( 'delete_post', $id ) ) {
218 wp_send_json_error( ['message' => 'Unauthorized to delete this document.'] );
219 }
220
221 wp_delete_post( $id, true );
222 wp_send_json_success();
223 }
224
225 // --- Track Download Handler ---
226 public function de_track_download() {
227 check_ajax_referer( 'de_track_download_nonce', 'nonce' );
228
229 $document_id = isset( $_POST['document_id'] ) ? intval( $_POST['document_id'] ) : 0;
230 if ( $document_id > 0 ) {
231 global $wpdb;
232
233 // Record in leads table for IP tracking
234 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- direct insert to custom table
235 $inserted = $wpdb->insert(
236 $wpdb->prefix . 'docembedder_leads',
237 [
238 'name' => 'Anonymous',
239 'email' => 'anonymous@direct.download',
240 'document_id' => $document_id,
241 'document_title' => get_the_title( $document_id ),
242 'downloaded_at' => current_time( 'mysql' ),
243 'ip_address' => Functions::get_client_ip()
244 ],
245 ['%s', '%s', '%d', '%s', '%s', '%s']
246 );
247
248 if ( $inserted === false ) {
249 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
250 error_log( "DE DEBUG: DB Insert FAILED for Direct Tracking. Error: " . $wpdb->last_error );
251 } else {
252 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
253 error_log( "DE DEBUG: DB Insert SUCCESS for Direct Tracking. ID: " . $wpdb->insert_id );
254 }
255
256 // Use a custom token instead of a WP nonce to avoid UID-dependency during redirect
257 $ip = Functions::get_client_ip();
258 $token = wp_hash( $document_id . '|' . $ip . '|' . 'de_download', 'nonce' );
259
260 $count = (int) get_post_meta( $document_id, '_de_download_count', true );
261 update_post_meta( $document_id, '_de_download_count', $count + 1 );
262
263 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
264 error_log( "DE DEBUG: de_track_download called. IP: $ip. Doc: $document_id. Token: $token" );
265
266 wp_send_json_success( [
267 'count' => $count + 1,
268 'nonce' => $token
269 ] );
270 }
271 wp_send_json_error( 'Invalid document ID' );
272 }
273
274 // --- Get Doc Meta Handlers ---
275 public function single_doc_callback() {
276 $id = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : false;
277 $nonce = isset( $_GET['ppv_nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['ppv_nonce'] ) ) : false;
278 $user = wp_get_current_user();
279 if ( ! $id || get_post_status( $id ) != 'publish' || ! wp_verify_nonce( $nonce, 'ppv_secret_nonce' ) || ! in_array( 'administrator', $user->roles ) ) {
280 return false;
281 }
282
283 $data = $this->get_data( $id );
284
285 echo wp_json_encode( $data );
286 die();
287 }
288
289 private function get_data( $id ) {
290 $width = Functions::meta( $id, 'width', ['width' => '100', 'unit' => '%'] );
291 $height = Functions::meta( $id, 'height', ['height' => 600, 'unit' => 'px'] );
292
293 return [
294 'url' => Functions::meta( $id, 'doc', '' ),
295 'width' => $width['width'] . $width['unit'],
296 'height' => $height['height'] . $height['unit'],
297 'showName' => Functions::meta( $id, 'showName' ),
298 'title' => get_the_title( $id )
299 ];
300 }
301 }
302
303 AJAXCall::instance();
304 }
305