| 1 | <?php |
| 2 | /** |
| 3 | * Handles the uploading, cropping and scaling of favicons. |
| 4 | * |
| 5 | * @package WordPress |
| 6 | * @subpackage Administration |
| 7 | * @since 3.4.1 |
| 8 | */ |
| 9 | |
| 10 | // Bootstrap admin and all its goodies |
| 11 | require_once('admin.php'); |
| 12 | |
| 13 | define( 'FAVICON_SIZE', 32 ); // Width (and height) of the favicon (in pixels) |
| 14 | |
| 15 | /* Upload is a 2-step process: |
| 16 | * 1. Process the uploaded file and show the crop UI |
| 17 | * 2. Manipulate the pixel data, save to PNG and ICO and write to options. |
| 18 | */ |
| 19 | |
| 20 | if ( isset( $_POST['CROP_AND_SAVE'] ) ) { |
| 21 | if ( isset( $_POST['attachment_id'] ) && is_numeric( $_POST['attachment_id'] ) ){ |
| 22 | $image_basename = process_crop_thumbnail( $_POST['attachment_id'] ); |
| 23 | |
| 24 | if ( is_wp_error( $image_basename ) ) { |
| 25 | include_once('./admin-header.php'); |
| 26 | echo '<div class="wrap">'; |
| 27 | echo '<h2>' . __( 'Image upload error' ) . '</h2>'; |
| 28 | echo '<p>' . $image_basename->get_error_message() . '</p>'; |
| 29 | echo '<p><a href="' . admin_url( 'options-general.php' ) . '">«' . __( 'Back to Settings > General' ) . '</a></p>'; |
| 30 | echo '</div><!-- .wrap -->'; |
| 31 | include_once('./admin-footer.php'); |
| 32 | } else { |
| 33 | foreach ( array( $image_basename . '.png', $image_basename . '.ico' ) as $image_filename ) |
| 34 | save_thumbnail_attachment( $image_filename, $attachment_id ); |
| 35 | |
| 36 | // And save the basename out to options. |
| 37 | update_option( 'sitefavicon', basename( $image_basename ) ); |
| 38 | |
| 39 | /** @TODO need to find a way to notify the user that the process has completed successfully - admin_notices? */ |
| 40 | wp_redirect( admin_url( 'options-general.php' ) ); |
| 41 | } |
| 42 | } else { |
| 43 | return new WP_Error( 'attachment_id_missing', 'Form submission error.' ); |
| 44 | } |
| 45 | } elseif ( isset( $_POST['REMOVE_FAVICON'] ) ) { |
| 46 | remove_thumbnail(); |
| 47 | } else { |
| 48 | /** @TODO make sure that we trap for someone just pressing "Upload image" but with no image attached */ |
| 49 | // Enqueue the JS for the cropper... |
| 50 | add_action( 'admin_enqueue_scripts', 'enqueue_cropper' ); |
| 51 | // ...and our own script for populating the crop form |
| 52 | add_action( 'admin_footer', 'cropping_js', 10, 1); |
| 53 | |
| 54 | // Process the upload and create the attachment file in the media center |
| 55 | $image_results = process_thumbnail_upload(); |
| 56 | |
| 57 | include_once('./admin-header.php'); |
| 58 | |
| 59 | // hack because image replication isn't fast enough. See https://wpcom.automattic.com/ticket/1294 |
| 60 | sleep( 2 ); |
| 61 | |
| 62 | echo '<div class="wrap">'; |
| 63 | |
| 64 | if ( is_wp_error( $image_results ) ) { |
| 65 | echo '<h2>' . __( 'Image upload error' ) . '</h2>'; |
| 66 | echo '<p>' . $image_results->get_error_message() . '</p>'; |
| 67 | echo '<p><a href="' . admin_url( 'options-general.php' ) . '">«' . __( 'Back to Settings > General' ) . '</a></p>'; |
| 68 | } else { |
| 69 | // Image upload successful. |
| 70 | // Now we can hook in our javascript and provide the width/height of our image as the default crop size |
| 71 | $crop_size = min( $image_results['width'], $image_results['height'] ); |
| 72 | echo '<script type="text/javascript">var jcrop_starting_size = ' . $crop_size . '; // Initialize jcrop crop area starting size</script>'; |
| 73 | |
| 74 | echo '<h2>' . __( 'Crop uploaded image' ) . '</h2>'; |
| 75 | echo '<p>' . __( 'Choose the part of the image you want to use for your favicon.' ) . '</p>'; |
| 76 | |
| 77 | echo '<form id="favicon-crop-form" method="post" action="' . $_SERVER['REQUEST_URI'] . '">'; // Point the form action back to this script |
| 78 | |
| 79 | echo <<<CROP_FORM |
| 80 | <input type="hidden" name="x1" id="x1" /> |
| 81 | <input type="hidden" name="y1" id="y1" /> |
| 82 | <input type="hidden" name="x2" id="x2" /> |
| 83 | <input type="hidden" name="y2" id="y2" /> |
| 84 | <input type="hidden" name="width" id="width" /> |
| 85 | <input type="hidden" name="height" id="height" /> |
| 86 | <input type="hidden" name="attachment_id" id="attachment_id" value="{$image_results['attachment_id']}" /> |
| 87 | <input type="hidden" name="scaling_factor" id="scaling_factor" value="{$image_results['scaling_factor']}" /> |
| 88 | CROP_FORM; |
| 89 | |
| 90 | echo '<img src="' . $image_results['src'] . '" id="upload" width="' . $image_results['width'] . '" height="' . $image_results['height'] . '" />'; |
| 91 | |
| 92 | echo '<p class="submit"><input type="submit" name="CROP_AND_SAVE" value="' . __( 'Crop image' ) . ' »" /></p>'; |
| 93 | echo '</form>'; |
| 94 | } |
| 95 | |
| 96 | echo '</div><!-- .wrap -->'; |
| 97 | |
| 98 | include_once('./admin-footer.php'); |
| 99 | } |
| 100 | |
| 101 | |
| 102 | /** |
| 103 | * Process the image file upload and return a WP_Error or details about the attachment image file. |
| 104 | * |
| 105 | * @return mixed WP_Error | $image_results array |
| 106 | */ |
| 107 | function process_thumbnail_upload(){ |
| 108 | $file = wp_handle_upload( $_FILES['avatarfile'], array( 'action' => 'wp_upload_favicon') ); |
| 109 | if ( isset($file['error']) ) die( $file['error'] ); |
| 110 | |
| 111 | $url = $file['url']; |
| 112 | $file = $file['file']; |
| 113 | $filename = basename($file); |
| 114 | |
| 115 | // Construct the object array |
| 116 | $object = array( |
| 117 | 'post_title' => $filename, |
| 118 | 'post_content' => $url, |
| 119 | 'post_mime_type' => 'import', |
| 120 | 'guid' => $url |
| 121 | ); |
| 122 | |
| 123 | // Save the data. Also makes replication work |
| 124 | $id = wp_insert_attachment($object, $file); |
| 125 | |
| 126 | // Retrieve the image dimensions |
| 127 | list( $orig_width, $orig_height, $type, $attr ) = getimagesize( $file ); |
| 128 | |
| 129 | // Do we need to scale down the image so we can display it nicely in the interactive Crop tool? |
| 130 | if ( $orig_width > 600 || $orig_height > 600 ) { |
| 131 | $image = wp_create_thumbnail( $file, 600 ); |
| 132 | list( $width, $height, $type, $attr ) = getimagesize( $image ); |
| 133 | |
| 134 | // Update the attachment record to reflect the newly-scaled thumbnail image |
| 135 | $thumb = basename( $image ); |
| 136 | $metadata = array( 'thumb' => $thumb ); |
| 137 | wp_update_attachment_metadata( $id, $metadata ); |
| 138 | |
| 139 | $url = str_replace( basename( $url ), $thumb, $url ); |
| 140 | |
| 141 | $scaling = $orig_width / $width; |
| 142 | } else { |
| 143 | // No scaling required; just copy original values. |
| 144 | $width = $orig_width; |
| 145 | $height = $orig_height; |
| 146 | $scaling = 1; |
| 147 | } |
| 148 | |
| 149 | // Check image file format |
| 150 | $image_type = exif_imagetype( get_attached_file( $id ) ); |
| 151 | if (! in_array( $image_type, array( IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_BMP ) ) ) |
| 152 | $error = new WP_Error( 'bad_file_format', __( 'Please only use PNG (.png), JPEG (.jpg) or BMP (.bmp) image files for favicons. ' ) ); |
| 153 | |
| 154 | // return WP_Error or the $image_results array |
| 155 | if ( $error ){ |
| 156 | return $error; |
| 157 | } else { |
| 158 | return array( |
| 159 | 'attachment_id' => $id, |
| 160 | 'src' => $url, |
| 161 | 'width' => $width, |
| 162 | 'height' => $height, |
| 163 | 'scaling_factor' => $scaling |
| 164 | ); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Create PNG and BMP image resources based on the form submission of the cropped thumbnail. |
| 170 | * |
| 171 | * @param int $attachment_id The ID of the original attachment's post record. |
| 172 | * @return mixed WP_Error | Favicon file base name (ie: fully qualified file name without any TLA file extension) |
| 173 | */ |
| 174 | function process_crop_thumbnail( $attachment_id ){ |
| 175 | $src_file = get_attached_file( $attachment_id ); |
| 176 | |
| 177 | // Highly unlikely, but let's check |
| 178 | if (! file_exists( $src_file ) ) |
| 179 | return new WP_Error( 'file_missing', __( 'Attachment image file missing (possible save error: check space on web server).' ) ); |
| 180 | |
| 181 | // Make sure we're still within accepted image types |
| 182 | $image_type = exif_imagetype( $src_file ); |
| 183 | if (! $image_type || ! in_array( $image_type, array( IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_BMP ) ) ) |
| 184 | return new WP_Error( 'bad_file_format', __( 'Please only use PNG (.png), JPEG (.jpg) or BMP (.bmp) image files for favicons. ' ) ); |
| 185 | |
| 186 | // Parse image file bytes |
| 187 | $src_image = wp_load_image( $src_file ); |
| 188 | if ( !is_resource($src_image) ) |
| 189 | return new WP_Error( 'is_not_resource', __( 'Error loading image. You got me: I\'m stumped.' ) ); |
| 190 | |
| 191 | // We crop from the original, not the medium sized, display-only thumbnail |
| 192 | $src_x = $_POST['x1'] * $_POST['scaling_factor']; |
| 193 | $src_y = $_POST['y1'] * $_POST['scaling_factor']; |
| 194 | $src_width = $_POST['width'] * $_POST['scaling_factor']; |
| 195 | $src_height = $_POST['height'] * $_POST['scaling_factor']; |
| 196 | |
| 197 | $dst_width = $dst_height = FAVICON_SIZE; |
| 198 | // Avoid upscaling |
| 199 | if ( $src_width < $dst_width || $src_height < $dst_height ) { |
| 200 | $dst_width = $src_width; |
| 201 | $dst_height = $src_height; |
| 202 | } |
| 203 | |
| 204 | $dst_image = wp_imagecreatetruecolor( $dst_width, $dst_height ); |
| 205 | if (function_exists('imageantialias')) imageantialias($dst_image, true); |
| 206 | imagealphablending($dst_image, false); |
| 207 | imagesavealpha($dst_image, true); |
| 208 | imagecopyresampled($dst_image, $src_image, 0, 0, $src_x, $src_y, $dst_width, $dst_height, $src_width, $src_height); |
| 209 | imagedestroy( $src_image ); |
| 210 | |
| 211 | |
| 212 | // Save the image in PNG and BMP formats |
| 213 | $file_info = pathinfo( $src_file ); |
| 214 | $src_basename = basename( $src_file, '.' . $file_info['extension'] ); |
| 215 | $dst_filename = str_replace( $src_basename, $src_basename . '_' . FAVICON_SIZE . 'x' . FAVICON_SIZE, $src_file ); |
| 216 | // Strip the TLA from the filename |
| 217 | $dst_filename = preg_replace( '/\\.[^\\.]+$/', '', $dst_filename ); |
| 218 | |
| 219 | $png_filename = $dst_filename . '.png'; |
| 220 | if (! imagepng( $dst_image, $png_filename, 0 ) ) |
| 221 | return new WP_Error( 'png_write_error', 'Error writing PNG favicon file.' ); |
| 222 | $ico_filename = $dst_filename . '.ico'; |
| 223 | if (!image2wbmp( $dst_image, $ico_filename, 1 ) ) |
| 224 | return new WP_Error( 'ico_write_error', 'Error writing ICO favicon file.' ); |
| 225 | |
| 226 | imagedestroy($dst_image); |
| 227 | |
| 228 | return $dst_filename; |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * Creates an attachment post record for a newly created thumbnail |
| 233 | * |
| 234 | * @param string $file_name Fully qualified file name for the image asset file. |
| 235 | * @param int $parent_attachment_id The ID of the original thumbnail's attachment post record |
| 236 | * |
| 237 | * @return int The ID of the newly-created thumbnail attachment post record |
| 238 | */ |
| 239 | function save_thumbnail_attachment( $file_name, $parent_attachment_id ){ |
| 240 | $file_info = pathinfo( $file_name ); // So we can get the TLA later on |
| 241 | |
| 242 | $file_name = apply_filters( 'wp_create_file_in_uploads', $file_name, $parent_attachment_id ); // For replication |
| 243 | |
| 244 | $parent = get_post( $parent_attachment_id ); |
| 245 | $parent_url = $parent->guid; |
| 246 | |
| 247 | // Update the attachment |
| 248 | $mimes = get_allowed_mime_types(); |
| 249 | $attachment_id = wp_insert_attachment( array( |
| 250 | 'ID' => $attachment_id, |
| 251 | 'post_title' => basename( $file_name ), |
| 252 | 'post_content' => $url, |
| 253 | 'post_mime_type' => $mimes[ $file_info['extension'] ], |
| 254 | 'guid' => $url, |
| 255 | 'context' => 'favicon' |
| 256 | ), $file_name ); |
| 257 | wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file_name ) ); |
| 258 | |
| 259 | return $attachment_id; |
| 260 | } |
| 261 | |
| 262 | function remove_thumbnail(){ |
| 263 | |
| 264 | } |
| 265 | |
| 266 | |
| 267 | /** |
| 268 | * Called in admin_enqueue_scripts to add the cropper.js script and styles |
| 269 | */ |
| 270 | function enqueue_cropper(){ |
| 271 | wp_enqueue_script( 'jcrop', 'jquery' ); |
| 272 | wp_enqueue_style('jcrop'); // We can enqueue styles within the admin_enqueue_script action hook {@link http://wpdevel.wordpress.com/2011/12/12/use-wp_enqueue_scripts-not-wp_print_styles-to-enqueue-scripts-and-styles-for-the-frontend/} |
| 273 | } |
| 274 | |
| 275 | function cropping_js(){ |
| 276 | // Purely for coding convenience and legibility |
| 277 | $favicon_size = FAVICON_SIZE; |
| 278 | |
| 279 | echo <<<CROP_JS |
| 280 | <!-- Favicon cropping --> |
| 281 | <script type="text/javascript"> |
| 282 | // Update the crop form |
| 283 | function onEndCrop( coords ) { |
| 284 | jQuery( '#x1' ).val(coords.x); |
| 285 | jQuery( '#y1' ).val(coords.y); |
| 286 | jQuery( '#x2' ).val(coords.x2); |
| 287 | jQuery( '#y2' ).val(coords.y2); |
| 288 | jQuery( '#width' ).val(coords.w); |
| 289 | jQuery( '#height' ).val(coords.h); |
| 290 | } |
| 291 | |
| 292 | // with a supplied ratio |
| 293 | jQuery(function($) { |
| 294 | if (! jcrop_starting_size) jcrop_starting_size = {$favicon_size}; // jcrop_starting_size should be set in the body once the image has been processed |
| 295 | |
| 296 | // Set up default values on the crop form |
| 297 | jQuery( '#x1' ).val(0); |
| 298 | jQuery( '#y1' ).val(0); |
| 299 | jQuery( '#x2' ).val(jcrop_starting_size); |
| 300 | jQuery( '#y2' ).val(jcrop_starting_size); |
| 301 | jQuery( '#width' ).val(jcrop_starting_size); |
| 302 | jQuery( '#height' ).val(jcrop_starting_size); |
| 303 | |
| 304 | // Initialize Jcrop |
| 305 | $('#upload').Jcrop({ |
| 306 | aspectRatio: 1, |
| 307 | setSelect: [0, 0, jcrop_starting_size, jcrop_starting_size], |
| 308 | onSelect: onEndCrop |
| 309 | }); |
| 310 | }); |
| 311 | </script> |
| 312 | CROP_JS; |
| 313 | |
| 314 | } |
| 315 | No newline at end of file |