Skip to content

Commit a64c685

Browse files
authored
Merge pull request #4 from omnisend/image
Image comparison
2 parents 4700103 + 8763191 commit a64c685

File tree

3 files changed

+281
-19
lines changed

3 files changed

+281
-19
lines changed

assets/css/admin.css

+87
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,91 @@
215215
to {
216216
transform: rotate(360deg);
217217
}
218+
}
219+
220+
.stl-modal-content.stl-modal-wide {
221+
max-width: 1200px;
222+
width: 90%;
223+
}
224+
225+
/* Image comparison */
226+
.stl-image-comparison {
227+
margin: 20px 0;
228+
}
229+
230+
.stl-image-comparison h3 {
231+
margin-top: 0;
232+
margin-bottom: 15px;
233+
padding-bottom: 10px;
234+
border-bottom: 1px solid #eee;
235+
}
236+
237+
.stl-image-container {
238+
display: flex;
239+
flex-wrap: wrap;
240+
gap: 20px;
241+
justify-content: space-between;
242+
}
243+
244+
.stl-image-side {
245+
flex: 0 0 calc(50% - 10px);
246+
max-width: calc(50% - 10px);
247+
box-sizing: border-box;
248+
border: 1px solid #ddd;
249+
padding: 15px;
250+
background: #f9f9f9;
251+
}
252+
253+
.stl-image-side h4 {
254+
margin-top: 0;
255+
margin-bottom: 15px;
256+
text-align: center;
257+
padding-bottom: 10px;
258+
border-bottom: 1px solid #eee;
259+
}
260+
261+
.stl-image-wrapper {
262+
text-align: center;
263+
margin-bottom: 15px;
264+
min-height: 150px;
265+
display: flex;
266+
align-items: center;
267+
justify-content: center;
268+
background: #fff;
269+
border: 1px solid #eee;
270+
padding: 10px;
271+
}
272+
273+
.stl-image-wrapper img {
274+
max-width: 100%;
275+
max-height: 400px;
276+
height: auto;
277+
}
278+
279+
.stl-image-missing {
280+
padding: 30px;
281+
text-align: center;
282+
background: #f5f5f5;
283+
border: 1px dashed #ddd;
284+
color: #888;
285+
font-style: italic;
286+
}
287+
288+
.stl-image-side p {
289+
margin: 10px 0 0;
290+
text-align: center;
291+
font-weight: bold;
292+
}
293+
294+
/* Responsive adjustments for image comparison */
295+
@media screen and (max-width: 782px) {
296+
.stl-image-container {
297+
flex-direction: column;
298+
}
299+
300+
.stl-image-side {
301+
flex: 0 0 100%;
302+
max-width: 100%;
303+
margin-bottom: 20px;
304+
}
218305
}

assets/js/admin.js

+72-18
Original file line numberDiff line numberDiff line change
@@ -138,35 +138,89 @@
138138
success: function(response) {
139139
if (response.success) {
140140
if (response.data.is_binary) {
141-
// Show binary file info
142-
var html = '<div class="stl-binary-info">';
143-
html += '<p>This is a binary file. Diff cannot be displayed.</p>';
144-
145-
if (response.data.staging_exists && response.data.production_exists) {
146-
html += '<p>File exists in both staging and production environments.</p>';
147-
html += '<p>Staging file size: ' + formatBytes(response.data.staging_size) + '</p>';
148-
html += '<p>Production file size: ' + formatBytes(response.data.production_size) + '</p>';
149-
} else if (response.data.staging_exists) {
150-
html += '<p>File exists only in staging environment.</p>';
151-
html += '<p>File size: ' + formatBytes(response.data.staging_size) + '</p>';
141+
if (response.data.is_image) {
142+
// Show image comparison
143+
var html = '<div class="stl-image-comparison">';
144+
html += '<h3>Image Comparison</h3>';
145+
146+
html += '<div class="stl-image-container">';
147+
148+
// Production image
149+
html += '<div class="stl-image-side">';
150+
html += '<h4>Production Version</h4>';
151+
if (response.data.production_exists) {
152+
html += '<div class="stl-image-wrapper">';
153+
html += '<img src="' + response.data.production_url + '" alt="Production: ' + response.data.file_name + '">';
154+
html += '</div>';
155+
html += '<p>File size: ' + formatBytes(response.data.production_size) + '</p>';
156+
} else {
157+
html += '<div class="stl-image-missing">Image does not exist in production</div>';
158+
}
159+
html += '</div>';
160+
161+
// Staging image
162+
html += '<div class="stl-image-side">';
163+
html += '<h4>Staging Version</h4>';
164+
if (response.data.staging_exists) {
165+
html += '<div class="stl-image-wrapper">';
166+
html += '<img src="' + response.data.staging_url + '" alt="Staging: ' + response.data.file_name + '">';
167+
html += '</div>';
168+
html += '<p>File size: ' + formatBytes(response.data.staging_size) + '</p>';
169+
} else {
170+
html += '<div class="stl-image-missing">Image does not exist in staging</div>';
171+
}
172+
html += '</div>';
173+
174+
html += '</div>'; // Close stl-image-container
175+
html += '</div>'; // Close stl-image-comparison
176+
177+
$fileDiffModal.find('.stl-modal-body').html(html);
178+
179+
// Make the modal wider for image comparison
180+
$fileDiffModal.find('.stl-modal-content').addClass('stl-modal-wide');
152181
} else {
153-
html += '<p>File exists only in production environment.</p>';
154-
html += '<p>File size: ' + formatBytes(response.data.production_size) + '</p>';
182+
// Show binary file info for non-image binary files
183+
var html = '<div class="stl-binary-info">';
184+
html += '<p>This is a binary file. Diff cannot be displayed.</p>';
185+
186+
if (response.data.staging_exists && response.data.production_exists) {
187+
html += '<p>File exists in both staging and production environments.</p>';
188+
html += '<p>Staging file size: ' + formatBytes(response.data.staging_size) + '</p>';
189+
html += '<p>Production file size: ' + formatBytes(response.data.production_size) + '</p>';
190+
} else if (response.data.staging_exists) {
191+
html += '<p>File exists only in staging environment.</p>';
192+
html += '<p>File size: ' + formatBytes(response.data.staging_size) + '</p>';
193+
} else {
194+
html += '<p>File exists only in production environment.</p>';
195+
html += '<p>File size: ' + formatBytes(response.data.production_size) + '</p>';
196+
}
197+
198+
html += '</div>';
199+
200+
$fileDiffModal.find('.stl-modal-body').html(html);
201+
202+
// Reset modal width
203+
$fileDiffModal.find('.stl-modal-content').removeClass('stl-modal-wide');
155204
}
156-
157-
html += '</div>';
158-
159-
$fileDiffModal.find('.stl-modal-body').html(html);
160205
} else {
161-
// Show diff
206+
// Show diff for text files
162207
$fileDiffModal.find('.stl-modal-body').html('<div class="stl-diff">' + response.data.diff + '</div>');
208+
209+
// Reset modal width
210+
$fileDiffModal.find('.stl-modal-content').removeClass('stl-modal-wide');
163211
}
164212
} else {
165213
$fileDiffModal.find('.stl-modal-body').html('<div class="notice notice-error"><p>' + response.data.message + '</p></div>');
214+
215+
// Reset modal width
216+
$fileDiffModal.find('.stl-modal-content').removeClass('stl-modal-wide');
166217
}
167218
},
168219
error: function() {
169220
$fileDiffModal.find('.stl-modal-body').html('<div class="notice notice-error"><p>Error fetching file diff.</p></div>');
221+
222+
// Reset modal width
223+
$fileDiffModal.find('.stl-modal-content').removeClass('stl-modal-wide');
170224
}
171225
});
172226
});

includes/class-stl-file-comparer.php

+122-1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ private function __construct() {
118118

119119
// Register AJAX handlers
120120
add_action( 'wp_ajax_stl_get_file_diff', array( $this, 'ajax_get_file_diff' ) );
121+
add_action( 'wp_ajax_stl_view_file', array( $this, 'ajax_view_file' ) );
121122
}
122123

123124
/**
@@ -289,14 +290,30 @@ public function get_file_diff( $file ) {
289290
return false;
290291
}
291292

293+
// Check if file is an image
294+
if ( $this->is_image_file( $file ) ) {
295+
return array(
296+
'is_binary' => true,
297+
'is_image' => true,
298+
'staging_exists' => $staging_exists,
299+
'production_exists' => $production_exists,
300+
'staging_size' => $staging_exists ? filesize( $staging_path ) : 0,
301+
'production_size' => $production_exists ? filesize( $production_path ) : 0,
302+
'staging_url' => $staging_exists ? $this->get_file_url( $file, 'staging' ) : '',
303+
'production_url' => $production_exists ? $this->get_file_url( $file, 'production' ) : '',
304+
'file_name' => basename( $file ),
305+
);
306+
}
307+
292308
// Get file contents
293309
$staging_content = $staging_exists ? file_get_contents( $staging_path ) : '';
294310
$production_content = $production_exists ? file_get_contents( $production_path ) : '';
295311

296-
// If file is binary, don't show diff
312+
// If file is binary (but not image), don't show diff
297313
if ( $this->is_binary_file( $file ) ) {
298314
return array(
299315
'is_binary' => true,
316+
'is_image' => false,
300317
'staging_exists' => $staging_exists,
301318
'production_exists' => $production_exists,
302319
'staging_size' => $staging_exists ? filesize( $staging_path ) : 0,
@@ -327,6 +344,7 @@ public function get_file_diff( $file ) {
327344

328345
return array(
329346
'is_binary' => false,
347+
'is_image' => false,
330348
'staging_exists' => $staging_exists,
331349
'production_exists' => $production_exists,
332350
'diff' => $diff_table,
@@ -362,6 +380,75 @@ public function ajax_get_file_diff() {
362380
wp_send_json_success( $diff );
363381
}
364382

383+
/**
384+
* AJAX handler for viewing files from staging
385+
*/
386+
public function ajax_view_file() {
387+
// Check nonce
388+
if ( ! check_ajax_referer( 'stl_view_file_nonce', 'nonce', false ) ) {
389+
wp_die( __( 'Security check failed.', 'staging2live' ) );
390+
}
391+
392+
// Check if user has permissions
393+
if ( ! current_user_can( 'manage_options' ) ) {
394+
wp_die( __( 'You do not have permission to view this file.', 'staging2live' ) );
395+
}
396+
397+
// Get file path and environment
398+
$file = isset( $_GET['file'] ) ? sanitize_text_field( wp_unslash( $_GET['file'] ) ) : '';
399+
$env = isset( $_GET['env'] ) ? sanitize_text_field( wp_unslash( $_GET['env'] ) ) : 'production';
400+
401+
if ( empty( $file ) ) {
402+
wp_die( __( 'No file specified.', 'staging2live' ) );
403+
}
404+
405+
// Get the actual file path
406+
$file_path = ( $env === 'staging' ) ? $this->staging_root . $file : $this->production_root . $file;
407+
408+
// Check if file exists
409+
if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
410+
wp_die( __( 'File not found or not readable.', 'staging2live' ) );
411+
}
412+
413+
// Get file extension
414+
$extension = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
415+
416+
// Set content type based on file extension
417+
switch ( $extension ) {
418+
case 'jpg':
419+
case 'jpeg':
420+
$content_type = 'image/jpeg';
421+
break;
422+
case 'png':
423+
$content_type = 'image/png';
424+
break;
425+
case 'gif':
426+
$content_type = 'image/gif';
427+
break;
428+
case 'webp':
429+
$content_type = 'image/webp';
430+
break;
431+
case 'svg':
432+
$content_type = 'image/svg+xml';
433+
break;
434+
case 'ico':
435+
$content_type = 'image/x-icon';
436+
break;
437+
case 'bmp':
438+
$content_type = 'image/bmp';
439+
break;
440+
default:
441+
$content_type = 'application/octet-stream';
442+
break;
443+
}
444+
445+
// Output file contents
446+
header( 'Content-Type: ' . $content_type );
447+
header( 'Content-Length: ' . filesize( $file_path ) );
448+
readfile( $file_path );
449+
exit;
450+
}
451+
365452
/**
366453
* Get all files in a directory recursively
367454
*
@@ -472,6 +559,40 @@ private function get_relative_path( $path, $base ) {
472559
return ltrim( str_replace( $base, '', $path ), '/' );
473560
}
474561

562+
/**
563+
* Check if a file is an image based on extension
564+
*
565+
* @param string $file File path
566+
* @return bool True if image, false otherwise
567+
*/
568+
private function is_image_file( $file ) {
569+
$image_extensions = array('jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'svg');
570+
$extension = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
571+
return in_array( $extension, $image_extensions );
572+
}
573+
574+
/**
575+
* Get URL for a file in staging or production
576+
*
577+
* @param string $file Relative file path
578+
* @param string $environment 'staging' or 'production'
579+
* @return string URL to the file
580+
*/
581+
private function get_file_url( $file, $environment = 'production' ) {
582+
if ( $environment === 'staging' ) {
583+
// For staging, we'll use a special endpoint to serve the file
584+
$nonce = wp_create_nonce( 'stl_view_file_nonce' );
585+
return admin_url( 'admin-ajax.php?action=stl_view_file&file=' . urlencode( $file ) . '&env=staging&nonce=' . $nonce );
586+
} else {
587+
// For production, we can use the site URL
588+
if ( strpos( $file, 'wp-content/' ) === 0 ) {
589+
return content_url( str_replace( 'wp-content/', '', $file ) );
590+
} else {
591+
return site_url( $file );
592+
}
593+
}
594+
}
595+
475596
/**
476597
* Check if a file is binary based on extension
477598
*

0 commit comments

Comments
 (0)