PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / trunk
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization vtrunk
4.2.14 4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 All 135 releases
optimole-wp / inc / media_rename / attachment_edit.php

attachment_edit.php in Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization trunk, at inc/media_rename/attachment_edit.php

431 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Attachment edit class.
4 */
5
6 /**
7 * Optml_Attachment_Edit
8 *
9 * @since 4.0.0
10 */
11 class Optml_Attachment_Edit {
12 /**
13 * Initialize the attachment edit class.
14 *
15 * @return void
16 */
17 public function init() {
18 add_filter( 'attachment_fields_to_edit', [ $this, 'add_attachment_fields' ], 10, 2 );
19 add_filter( 'attachment_fields_to_save', [ $this, 'prepare_attachment_filename' ], 10, 2 );
20
21 add_action( 'edit_attachment', [ $this, 'save_attachment_filename' ] );
22 add_action( 'optml_after_attachment_url_replace', [ $this, 'bust_cache_on_rename' ], 10, 3 );
23 add_action( 'optml_attachment_replaced', [ $this, 'bust_cache_on_replace' ] );
24 add_action( 'wp_ajax_optml_replace_file', [ $this, 'replace_file' ] );
25
26 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
27 add_filter( 'media_row_actions', [ $this, 'add_replace_rename_action' ], 10, 2 );
28 }
29
30 /**
31 * Add Replace or Rename action in media library list view.
32 *
33 * @param string[] $actions Array of row action links.
34 * @param WP_Post $post The post object.
35 * @return string[]
36 */
37 public function add_replace_rename_action( $actions, $post ) {
38 if ( get_post_type( $post->ID ) !== 'attachment' ) {
39 return $actions;
40 }
41
42 $edit_url = admin_url( 'post.php?post=' . $post->ID . '&action=edit' );
43 $actions['replace_rename'] = sprintf(
44 '<a href="%s" aria-label="%s">%s</a>',
45 esc_url( $edit_url ),
46 esc_attr__( 'Replace or Rename', 'optimole-wp' ),
47 esc_html__( 'Replace or Rename', 'optimole-wp' )
48 );
49
50 return $actions;
51 }
52
53 /**
54 * Enqueue scripts.
55 *
56 * @param string $hook The hook.
57 */
58 public function enqueue_scripts( $hook ) {
59 if ( $hook !== 'post.php' && $hook !== 'upload.php' ) {
60 return;
61 }
62
63 if ( $hook === 'post.php' ) {
64 $id = (int) sanitize_text_field( $_GET['post'] );
65
66 if ( ! $id ) {
67 return;
68 }
69
70 if ( ! current_user_can( 'edit_post', $id ) ) {
71 return;
72 }
73
74 if ( get_post_type( $id ) !== 'attachment' ) {
75 return;
76 }
77
78 $mime_type = get_post_mime_type( $id );
79
80 $max_file_size = wp_max_upload_size();
81 // translators: %s is the max file size in MB.
82 $max_file_size_error = sprintf( __( 'File size is too large. Max file size is %sMB', 'optimole-wp' ), $max_file_size / 1024 / 1024 );
83
84 wp_enqueue_style( 'optml-attachment-edit', OPTML_URL . 'assets/css/single-attachment.css', [], OPTML_VERSION );
85
86 wp_register_script( 'optml-attachment-edit', OPTML_URL . 'assets/js/single-attachment.js', [ 'jquery' ], OPTML_VERSION, true );
87 wp_localize_script(
88 'optml-attachment-edit',
89 'OMAttachmentEdit',
90 [
91 'ajaxURL' => admin_url( 'admin-ajax.php' ),
92 'maxFileSize' => $max_file_size,
93 'attachmentId' => $id,
94 'mimeType' => $mime_type,
95 'nonce' => wp_create_nonce( 'optml_replace_media_nonce' ),
96 'i18n' => [
97 'maxFileSizeError' => $max_file_size_error,
98 'replaceFileError' => __( 'Error replacing file', 'optimole-wp' ),
99 ],
100 ]
101 );
102 wp_enqueue_script( 'optml-attachment-edit' );
103 } elseif ( $hook === 'upload.php' ) {
104 wp_enqueue_script(
105 'optml-modal-attachment',
106 OPTML_URL . 'assets/js/modal-attachment.js',
107 [ 'jquery', 'media-views', 'media-models' ],
108 OPTML_VERSION,
109 true
110 );
111
112 wp_localize_script(
113 'optml-modal-attachment',
114 'OptimoleModalAttachment',
115 [
116 'editPostURL' => admin_url( 'post.php' ),
117 'i18n' => [
118 'replaceOrRename' => __( 'Replace or Rename', 'optimole-wp' ),
119 ],
120 ]
121 );
122 }
123 }
124
125 /**
126 * Add fields to attachment edit form.
127 *
128 * @param array $form_fields Array of form fields.
129 * @param WP_Post $post The post object.
130 *
131 * @return array Modified form fields.
132 */
133 public function add_attachment_fields( $form_fields, $post ) {
134 if ( ! function_exists( 'get_current_screen' ) ) {
135 return $form_fields;
136 }
137
138 $screen = get_current_screen();
139
140 $attachment = new Optml_Attachment_Model( $post->ID );
141
142 if ( ! $attachment->can_be_renamed_or_replaced() ) {
143 return $form_fields;
144 }
145
146 if ( ! isset( $screen ) ) {
147 return $form_fields;
148 }
149
150 if ( $screen->parent_base !== 'upload' ) {
151 return $form_fields;
152 }
153
154 $form_fields['optml_rename_file'] = [
155 'label' => __( 'Rename attached file', 'optimole-wp' ),
156 'input' => 'html',
157 'html' => $this->get_rename_field( $attachment ),
158 ];
159
160 $form_fields['optml_replace_file'] = [
161 'label' => __( 'Replace file', 'optimole-wp' ),
162 'input' => 'html',
163 'html' => $this->get_replace_field( $attachment ),
164 ];
165
166 $form_fields['optml_footer_row'] = [
167 'label' => '',
168 'input' => 'html',
169 'html' => $this->get_footer_html(),
170 ];
171
172 $form_fields['optml_spacer_row'] = [
173 'label' => '',
174 'input' => 'html',
175 'html' => '<div></div>',
176 ];
177
178 return $form_fields;
179 }
180
181 /**
182 * Get the rename field HTML.
183 *
184 * @param \Optml_Attachment_Model $attachment The attachment model.
185 *
186 * @return string The HTML.
187 */
188 private function get_rename_field( \Optml_Attachment_Model $attachment ) {
189 $file_name_no_ext = $attachment->get_filename_no_ext();
190 $file_ext = $attachment->get_extension();
191
192 $html = '';
193
194 $html .= '<div class="optml-rename-media-container">';
195
196 $html .= '<div class="optml-rename-input">';
197 $html .= '<input type="text" id="optml_rename_file" name="optml_rename_file" placeholder="' . esc_attr( $file_name_no_ext ) . '">';
198 $html .= '<span class="optml-file-ext">.' . esc_html( $file_ext ) . '</span>';
199 $html .= '</div>';
200
201 $html .= '<button type="button" disabled class="button optml-btn primary" id="optml-rename-file-btn">' . __( 'Rename', 'optimole-wp' ) . '</button>';
202 $html .= '</div>';
203
204 $html .= '<input type="hidden" name="optml_current_ext" value="' . esc_attr( $file_ext ) . '">';
205
206 wp_nonce_field( 'optml_rename_media_nonce', 'optml_rename_nonce' );
207
208 return $html;
209 }
210
211 /**
212 * Get the replace field HTML.
213 *
214 * @param \Optml_Attachment_Model $attachment The attachment model.
215 *
216 * @return string The HTML.
217 */
218 private function get_replace_field( \Optml_Attachment_Model $attachment ) {
219 $file_ext = $attachment->get_extension();
220 $file_ext = in_array( $file_ext, [ 'jpg', 'jpeg' ], true ) ? [ '.jpg', '.jpeg' ] : [ '.' . $file_ext ];
221 $html = '<div class="optml-replace-section">';
222 $html .= '<div class="optml-replace-input">';
223 $html .= '<label for="optml-replace-file-field" id="optml-file-drop-area">';
224 $html .= '<span class="label-text">' . __( 'Click to select a file or drag & drop here', 'optimole-wp' ) . ' (' . implode( ',', $file_ext ) . ')</span>';
225 $html .= '<div class="optml-replace-file-preview"></div>';
226 $html .= '</label>';
227
228 $html .= '<input type="file" class="hidden" id="optml-replace-file-field" name="optml-replace-file-field" accept="' . implode( ',', $file_ext ) . '">';
229
230 $html .= '<div class="optml-replace-file-actions">';
231 $html .= '<button disabled type="button" class="button optml-btn primary" id="optml-replace-file-btn">' . __( 'Replace file', 'optimole-wp' ) . '</button>';
232 $html .= '<button disabled type="button" class="button optml-btn destructive" id="optml-replace-clear-btn">' . __( 'Clear', 'optimole-wp' ) . '</button>';
233 $html .= $this->get_svg_loader();
234 $html .= '<p class="optml-description">' . __( 'This will replace the current file with the new one. This action cannot be undone.', 'optimole-wp' ) . '</p>';
235 $html .= '</div>';
236
237 $html .= '<div class="optml-replace-file-error hidden"></div>';
238
239 $html .= '</div>';
240
241 return $html;
242 }
243
244 /**
245 * Get the footer HTML.
246 *
247 * @return string The HTML.
248 */
249 private function get_footer_html() {
250 $html = '';
251 $html .= '<div class="optml-logo-contianer">';
252 $html .= '<img src="' . OPTML_URL . 'assets/img/logo.svg" alt="' . __( 'Optimole logo', 'optimole-wp' ) . '"/>';
253 // translators: %s is the 'Optimole'.
254 $html .= '<span>' . sprintf( __( 'Powered by %s', 'optimole-wp' ), '<strong>Optimole</strong>' ) . '</span>';
255 $html .= '</div>';
256
257 return $html;
258 }
259
260
261 /**
262 * Prepare the new filename before saving.
263 *
264 * @param array $post_data Array of post data.
265 * @param array $attachment Array of attachment data.
266 *
267 * @return array Modified post data.
268 */
269 public function prepare_attachment_filename( array $post_data, array $attachment ) {
270 if ( ! current_user_can( 'edit_post', $post_data['ID'] ) ) {
271 return $post_data;
272 }
273
274 if ( ! isset( $post_data['post_type'] ) || $post_data['post_type'] !== 'attachment' ) {
275 return $post_data;
276 }
277
278 if ( ! isset( $post_data['optml_rename_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $post_data['optml_rename_nonce'] ), 'optml_rename_media_nonce' ) ) {
279 return $post_data;
280 }
281
282 $new_name = sanitize_text_field( $post_data['optml_rename_file'] );
283
284 $new_name = trim( $new_name );
285
286 if ( empty( $new_name ) ) {
287 return $post_data;
288 }
289
290 if ( strlen( $new_name ) > 100 ) {
291 return $post_data;
292 }
293
294 /**
295 * Store filename for later, it will be used to rename the attachment.
296 *
297 * We do it this way because we don't want to rename the attachment immediately, during the attachment fields update as it will break things.
298 *
299 * @see Optml_Attachment_Edit::save_attachment_filename()
300 */
301 update_post_meta( $post_data['ID'], '_optml_pending_rename', $new_name );
302
303 return $post_data;
304 }
305
306 /**
307 * Save the new filename when attachment is updated
308 *
309 * @param int $post_id The post ID.
310 */
311 public function save_attachment_filename( $post_id ) {
312 $new_filename = get_post_meta( $post_id, '_optml_pending_rename', true );
313
314 if ( empty( $new_filename ) ) {
315 return;
316 }
317
318 // Delete the meta so we don't rename again
319 delete_post_meta( $post_id, '_optml_pending_rename' );
320
321 $renamer = new Optml_Attachment_Rename( $post_id, $new_filename );
322 $status = $renamer->rename();
323
324 if ( is_wp_error( $status ) ) {
325 wp_die( $status->get_error_message() );
326 }
327 }
328
329 /**
330 * Replace the file
331 */
332 public function replace_file() {
333 if ( ! check_ajax_referer( 'optml_replace_media_nonce', 'optml_replace_nonce', false ) ) {
334 wp_send_json_error( __( 'Security check failed', 'optimole-wp' ) );
335 }
336
337 $id = absint( $_POST['attachment_id'] ?? 0 );
338
339 if ( ! $id ) {
340 wp_send_json_error( __( 'Invalid attachment ID', 'optimole-wp' ) );
341 }
342
343 if ( get_post_type( $id ) !== 'attachment' ) {
344 wp_send_json_error( __( 'Invalid attachment ID', 'optimole-wp' ) );
345 }
346
347 if ( ! current_user_can( 'edit_post', $id ) ) {
348 wp_send_json_error( __( 'You are not allowed to replace this file', 'optimole-wp' ) );
349 }
350
351 if ( ! isset( $_FILES['file'] ) ) {
352 wp_send_json_error( __( 'No file uploaded', 'optimole-wp' ) );
353 }
354
355 $file_info = wp_check_filetype_and_ext( $_FILES['file']['tmp_name'], $_FILES['file']['name'] );
356
357 if ( empty( $file_info['type'] ) ) {
358 wp_send_json_error( __( 'Could not determine uploaded file type', 'optimole-wp' ) );
359 }
360
361 $original_mime = get_post_mime_type( $id );
362 if ( $file_info['type'] !== $original_mime ) {
363 wp_send_json_error( __( 'The uploaded file type does not match the original file type.', 'optimole-wp' ) );
364 }
365
366 $replacer = new Optml_Attachment_Replace( $id, $_FILES['file'] );
367
368 $replaced = $replacer->replace();
369
370 $is_error = is_wp_error( $replaced );
371
372 $response = [
373 'success' => ! $is_error,
374 'message' => $is_error ? $replaced->get_error_message() : __( 'File replaced successfully', 'optimole-wp' ),
375 ];
376
377 wp_send_json( $response );
378 }
379
380 /**
381 * Bust cached assets when an attachment is renamed.
382 *
383 * @param int $attachment_id The attachment ID.
384 * @param string $old_url The old attachment URL.
385 * @param string $new_url The new attachment URL.
386 */
387 public function bust_cache_on_rename( $attachment_id, $old_url, $new_url ) {
388 $this->clear_cache();
389 }
390
391 /**
392 * Bust cached assets when an attachment is replaced.
393 *
394 * @param int $attachment_id The attachment ID.
395 *
396 * @return void
397 */
398 public function bust_cache_on_replace( $attachment_id ) {
399 $this->clear_cache();
400 }
401
402 /**
403 * Clear the cache for third-party plugins.
404 *
405 * @return void
406 */
407 private function clear_cache() {
408 if (
409 class_exists( '\ThemeIsle\GutenbergBlocks\Server\Dashboard_Server' ) &&
410 is_callable( [ '\ThemeIsle\GutenbergBlocks\Server\Dashboard_Server', 'regenerate_styles' ] )
411 ) {
412 \ThemeIsle\GutenbergBlocks\Server\Dashboard_Server::regenerate_styles();
413 }
414
415 if ( did_action( 'elementor/loaded' ) ) {
416 if ( class_exists( '\Elementor\Plugin' ) ) {
417 \Elementor\Plugin::instance()->files_manager->clear_cache();
418 }
419 }
420 }
421
422 /**
423 * Get the SVG loader.
424 *
425 * @return string The SVG loader.
426 */
427 private function get_svg_loader() {
428 return '<svg style="display: none;" class="optml-svg-loader" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>';
429 }
430 }
431