PluginProbe
Search & Replace Everything by WPCode – Find and Replace Media, Text, Links, and More / 1.0.7
Search & Replace Everything by WPCode – Find and Replace Media, Text, Links, and More v1.0.7
trunk 1.0.0 1.0.1 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
search-replace-wpcode / includes / class-wsrw-image-replace.php

class-wsrw-image-replace.php in Search & Replace Everything by WPCode – Find and Replace Media, Text, Links, and More 1.0.7, at includes/class-wsrw-image-replace.php

349 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This class handles the main logic for replacing images.
4 *
5 * @package Search_Replace_WPCode
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Class WSRW_Image_Replace
15 */
16 class WSRW_Image_Replace {
17
18 /**
19 * The old file path.
20 *
21 * @var string
22 */
23 private $old_file_path;
24
25 /**
26 * WSRW_Image_Replace constructor.
27 */
28 public function __construct() {
29 add_action( 'rest_api_init', array( $this, 'register_routes' ) );
30
31 add_filter( 'attachment_fields_to_edit', array( $this, 'add_button_to_edit_media_modal_fields_area' ), 10, 2 );
32 add_filter( 'media_row_actions', array( $this, 'add_button_to_media_row_actions' ), 10, 2 );
33
34 add_filter( 'wp_get_attachment_image_src', array( $this, 'get_attachment_url' ), 10, 4 );
35
36 add_action( 'add_meta_boxes_attachment', array( $this, 'add_meta_boxes' ) );
37 }
38
39 /**
40 * Filters the attachment image source result.
41 *
42 * @param array|false $image {
43 * Array of image data, or boolean false if no image is available.
44 *
45 * @type string $0 Image source URL.
46 * @type int $1 Image width in pixels.
47 * @type int $2 Image height in pixels.
48 * @type bool $3 Whether the image is a resized image.
49 * }
50 *
51 * @param int $attachment_id Image attachment ID.
52 * @param string|int[] $size Requested image size. Can be any registered image size name, or
53 * an array of width and height values in pixels (in that order).
54 * @param bool $icon Whether the image should be treated as an icon.
55 *
56 * @since 4.3.0
57 */
58 public function get_attachment_url( $image, $attachment_id, $size, $icon ) {
59 if ( ! is_admin() && ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) ) {
60 return $image;
61 }
62 // Let's check if the attachment has been replaced using the _wsrw_replaced meta.
63 $replaced = get_post_meta( $attachment_id, '_wsrw_replaced', true );
64 // If the replaced timestamp is past 24h let's just skip this.
65 if ( ! empty( $replaced ) && $replaced < strtotime( '-1 day' ) ) {
66 return $image;
67 }
68
69 if ( is_array( $image ) ) {
70 $image[0] = add_query_arg( 'wsr', $replaced, $image[0] );
71 }
72
73 return $image;
74 }
75
76 /**
77 * Handle the image upload.
78 *
79 * @param WP_REST_Request $request The request object.
80 *
81 * @return WP_REST_Response
82 */
83 public function handle_image_upload( $request ) {
84 $files = $request->get_file_params();
85
86 $nonce = $request->get_header( 'X-WP-Nonce' );
87 if ( empty( $nonce ) ) {
88 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_key( $_POST['_wpnonce'] ) : '';
89 }
90
91 if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
92 return new WP_REST_Response(
93 array(
94 'success' => false,
95 'message' => esc_html__( 'Invalid nonce', 'search-replace-wpcode' ),
96 ),
97 403
98 );
99 }
100
101 if ( empty( $files['file'] ) ) {
102 return new WP_REST_Response(
103 array(
104 'success' => false,
105 'message' => esc_html__( 'No file uploaded', 'search-replace-wpcode' ),
106 ),
107 400
108 );
109 }
110 $file = $files['file'];
111 $media_id = isset( $_POST['media_id'] ) ? absint( $_POST['media_id'] ) : 0;
112
113 // Let's check if the user is allowed to upload the file format.
114 $allowed_mime_types = get_allowed_mime_types();
115 $allowed_mime_types = apply_filters( 'upload_mimes', $allowed_mime_types );
116 $ext = pathinfo( $file['name'], PATHINFO_EXTENSION );
117 $mime_type = wp_check_filetype( $file['name'], $allowed_mime_types );
118 if ( ! in_array( $mime_type['type'], $allowed_mime_types, true ) ) {
119 return new WP_REST_Response(
120 array(
121 'success' => false,
122 'message' => esc_html__( 'File type not allowed', 'search-replace-wpcode' ),
123 ),
124 400
125 );
126 }
127
128 // Let's grab the path of the current image using the media_id.
129 $attachment = get_post( $media_id );
130 $old_file_path = get_attached_file( $media_id, true );
131
132 // Let's first delete all the thumbnails for the old image.
133 $metadata = wp_get_attachment_metadata( $media_id );
134 $backup_sizes = get_post_meta( $media_id, '_wp_attachment_backup_sizes', true );
135 wp_delete_attachment_files( $media_id, $metadata, $backup_sizes, $old_file_path );
136
137 // Let's upload the new file in the same directory as the old file with the same exact name.
138 $new_file_path = $old_file_path;
139 if ( ! function_exists( 'wp_handle_upload' ) ) {
140 require_once ABSPATH . 'wp-admin/includes/file.php';
141 }
142 $this->old_file_path = $old_file_path;
143 // Let's grab the original upload time from the post publish date.
144 $time = strtotime( $attachment->post_date );
145 // Time should be formatted in yyyy/mm so that we use the same directory.
146 $time = gmdate( 'Y/m', $time );
147 // Let's move the file to the new location using wp_handle_upload.
148 $move_file = wp_handle_upload(
149 $file,
150 array(
151 'test_form' => false,
152 'unique_filename_callback' => array( $this, 'unique_filename_callback' ),
153 ),
154 $time
155 );
156
157 if ( ! $move_file || isset( $move_file['error'] ) ) {
158 return new WP_REST_Response(
159 array(
160 'success' => false,
161 'message' => $move_file['error'] ?? esc_html__( 'An error occurred while uploading the file', 'search-replace-wpcode' ),
162 ),
163 500
164 );
165 }
166
167 // Let's make sure the media file is included before calling generate attachment metada.
168 require_once ABSPATH . 'wp-admin/includes/image.php';
169
170 add_filter( 'big_image_size_threshold', '__return_false' );
171 // Let's update the attachment metadata.
172 $attachment_data = wp_generate_attachment_metadata( $media_id, $new_file_path );
173 wp_update_attachment_metadata( $media_id, $attachment_data );
174
175 // Add post meta to mark this has been replaced.
176 update_post_meta( $media_id, '_wsrw_replaced', time() );
177
178 $new_media = wp_get_attachment_image_src( $media_id, 'large' );
179
180 $message = '<p>' . esc_html__( 'File uploaded successfully', 'search-replace-wpcode' ) . '</p>';
181
182 $message .= '<p><strong style="color:red;">' . esc_html__( 'Please note that the source file has been replaced. If you see the old file, please clear your browser cache.', 'search-replace-wpcode' ) . '</strong></p>';
183
184 $response = array(
185 'success' => true,
186 'message' => $message,
187 );
188
189 if ( wp_attachment_is_image( $media_id ) ) {
190 $response['image_url'] = $new_media[0] ?? wp_get_attachment_url( $media_id );
191 }
192
193 return new WP_REST_Response(
194 $response,
195 200
196 );
197 }
198
199 /**
200 * Register the REST API routes.
201 */
202 public function register_routes() {
203 register_rest_route(
204 'wsrw/v1',
205 '/upload-image',
206 array(
207 'methods' => 'POST',
208 'callback' => array( $this, 'handle_image_upload' ),
209 'permission_callback' => function () {
210 return current_user_can( 'upload_files' );
211 },
212 )
213 );
214 }
215
216 /**
217 * Add a button to the edit media modal fields area.
218 *
219 * @param array $form_fields The form fields.
220 * @param WP_Post $post The post object.
221 *
222 * @return array
223 */
224 public function add_button_to_edit_media_modal_fields_area( $form_fields, $post ) {
225 if ( ! $this->can_user_replace_image( $post ) ) {
226 return $form_fields;
227 }
228
229 if ( ! wp_attachment_is_image( $post ) ) {
230 return $form_fields;
231 }
232
233 $form_fields['wsrw-replace-button'] = array(
234 'label' => '',
235 'input' => 'html',
236 'html' => '<a href="' . esc_url( self::get_replace_page_url( $post ) ) . '" class="button-secondary button-large" title="' . esc_attr__( 'Replace the source file for this image', 'search-replace-wpcode' ) . '">' . esc_html_x( 'Replace Source File', 'action for a single image', 'search-replace-wpcode' ) . '</a>',
237 'show_in_modal' => true,
238 'show_in_edit' => false,
239 'helps' => esc_html__( 'Directly replace the original file\'s source without creating a duplicate.', 'search-replace-wpcode' ),
240 );
241
242 return $form_fields;
243 }
244
245 /**
246 * Get the URL for the replace page.
247 *
248 * @param WP_Post $post The post object.
249 *
250 * @return string
251 */
252 public static function get_replace_page_url( $post ) {
253 return wp_nonce_url( admin_url( 'admin.php?page=wsrw-search-replace&view=replace_media&media_id=' . $post->ID ), 'wsrw_replace_media' );
254 }
255
256 /**
257 * Add a button to the media row actions.
258 *
259 * @param array $actions The actions array.
260 * @param WP_Post $post The post object.
261 *
262 * @return array
263 */
264 public function add_button_to_media_row_actions( $actions, $post ) {
265 if ( ! $this->can_user_replace_image( $post ) ) {
266 return $actions;
267 }
268
269 $actions['wsrw-replace'] = '<a href="' . esc_url( self::get_replace_page_url( $post ) ) . '" title="' . esc_attr__( 'Replace the source file.', 'search-replace-wpcode' ) . '">' . esc_html_x( 'Replace Source File', 'action in the list of attachments', 'search-replace-wpcode' ) . '</a>';
270
271 return $actions;
272
273 }
274
275 /**
276 * Permissions check for a specific attachment
277 *
278 * @param WP_Post $post The post object to check for.
279 *
280 * @return bool
281 */
282 public function can_user_replace_image( $post ) {
283 return current_user_can( 'upload_files' ) && current_user_can( 'edit_post', $post->ID );
284 }
285
286 /**
287 * Add meta boxes for the replace media page.
288 *
289 * @param WP_Post $post The post object.
290 *
291 * @return void
292 */
293 public function add_meta_boxes( $post ) {
294 if ( ! $this->can_user_replace_image( $post ) ) {
295 return;
296 }
297
298 add_meta_box(
299 'wsrw-replace',
300 __( 'Replace Source File', 'search-replace-wpcode' ),
301 array(
302 $this,
303 'replace_meta_box',
304 ),
305 'attachment',
306 'side',
307 'low'
308 );
309 }
310
311 /**
312 * Output the meta box for the replace media page.
313 *
314 * @param WP_Post $post The post object.
315 *
316 * @return void
317 */
318 public function replace_meta_box( $post ) {
319 ?>
320 <p>
321 <a href="<?php echo esc_url( self::get_replace_page_url( $post ) ); ?>" class="button-secondary button-large"><?php esc_html_e( 'Replace Source File', 'search-replace-wpcode' ); ?></a>
322 </p>
323 <p>
324 <?php esc_html_e( 'Use the button above to begin the source file replacement process.', 'search-replace-wpcode' ); ?>
325 </p>
326 <?php
327 }
328
329 /**
330 * Override the unique filename callback so we override the original file.
331 *
332 * @param string $dir The directory path.
333 * @param string $filename The filename.
334 * @param string $ext The file extension.
335 *
336 * @return string
337 */
338 public function unique_filename_callback( $dir, $filename, $ext = '' ) {
339 if ( isset( $this->old_file_path ) ) {
340 return basename( $this->old_file_path );
341 } else {
342 return $filename;
343 }
344 }
345
346 }
347
348 new WSRW_Image_Replace();
349