wp/wp-admin/custom-header.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
--- a/wp/wp-admin/custom-header.php	Tue Jun 09 11:14:17 2015 +0000
+++ b/wp/wp-admin/custom-header.php	Mon Oct 14 17:39:30 2019 +0200
@@ -10,15 +10,13 @@
  * The custom header image class.
  *
  * @since 2.1.0
- * @package WordPress
- * @subpackage Administration
  */
 class Custom_Image_Header {
 
 	/**
 	 * Callback for administration header.
 	 *
-	 * @var callback
+	 * @var callable
 	 * @since 2.1.0
 	 */
 	public $admin_header_callback;
@@ -26,7 +24,7 @@
 	/**
 	 * Callback for header div.
 	 *
-	 * @var callback
+	 * @var callable
 	 * @since 3.0.0
 	 */
 	public $admin_image_div_callback;
@@ -36,11 +34,13 @@
 	 *
 	 * @var array
 	 * @since 3.0.0
-	 * @access private
 	 */
 	public $default_headers = array();
 
 	/**
+	 * Used to trigger a success message when settings updated and set to true.
+	 *
+	 * @since 3.0.0
 	 * @var bool
 	 */
 	private $updated;
@@ -49,8 +49,8 @@
 	 * Constructor - Register administration header callback.
 	 *
 	 * @since 2.1.0
-	 * @param callback $admin_header_callback
-	 * @param callback $admin_image_div_callback Optional custom image div output callback.
+	 * @param callable $admin_header_callback
+	 * @param callable $admin_image_div_callback Optional custom image div output callback.
 	 */
 	public function __construct($admin_header_callback, $admin_image_div_callback = '') {
 		$this->admin_header_callback = $admin_header_callback;
@@ -120,8 +120,8 @@
 
 		get_current_screen()->set_help_sidebar(
 			'<p><strong>' . __( 'For more information:' ) . '</strong></p>' .
-			'<p>' . __( '<a href="https://codex.wordpress.org/Appearance_Header_Screen" target="_blank">Documentation on Custom Header</a>' ) . '</p>' .
-			'<p>' . __( '<a href="https://wordpress.org/support/" target="_blank">Support Forums</a>' ) . '</p>'
+			'<p>' . __( '<a href="https://codex.wordpress.org/Appearance_Header_Screen">Documentation on Custom Header</a>' ) . '</p>' .
+			'<p>' . __( '<a href="https://wordpress.org/support/">Support Forums</a>' ) . '</p>'
 		);
 	}
 
@@ -228,6 +228,8 @@
 	 * Process the default headers
 	 *
 	 * @since 3.0.0
+	 *
+	 * @global array $_wp_default_headers
 	 */
 	public function process_default_headers() {
 		global $_wp_default_headers;
@@ -255,6 +257,9 @@
 	 * Random image option is on by default if no header has been set.
 	 *
 	 * @since 3.0.0
+	 *
+	 * @param string $type The header type. One of 'default' (for the Uploaded Images control)
+	 *                     or 'uploaded' (for the Uploaded Images control).
 	 */
 	public function show_header_selector( $type = 'default' ) {
 		if ( 'default' == $type ) {
@@ -267,7 +272,7 @@
 		if ( 1 < count( $headers ) ) {
 			echo '<div class="random-header">';
 			echo '<label><input name="default-header" type="radio" value="random-' . $type . '-image"' . checked( is_random_header_image( $type ), true, false ) . ' />';
-			echo __( '<strong>Random:</strong> Show a different image on each page.' );
+			_e( '<strong>Random:</strong> Show a different image on each page.' );
 			echo '</label>';
 			echo '</div>';
 		}
@@ -276,13 +281,13 @@
 		foreach ( $headers as $header_key => $header ) {
 			$header_thumbnail = $header['thumbnail_url'];
 			$header_url = $header['url'];
-			$header_desc = empty( $header['description'] ) ? '' : $header['description'];
+			$header_alt_text = empty( $header['alt_text'] ) ? '' : $header['alt_text'];
 			echo '<div class="default-header">';
 			echo '<label><input name="default-header" type="radio" value="' . esc_attr( $header_key ) . '" ' . checked( $header_url, get_theme_mod( 'header_image' ), false ) . ' />';
 			$width = '';
 			if ( !empty( $header['attachment_id'] ) )
 				$width = ' width="230"';
-			echo '<img src="' . set_url_scheme( $header_thumbnail ) . '" alt="' . esc_attr( $header_desc ) .'" title="' . esc_attr( $header_desc ) . '"' . $width . ' /></label>';
+			echo '<img src="' . set_url_scheme( $header_thumbnail ) . '" alt="' . esc_attr( $header_alt_text ) .'"' . $width . ' /></label>';
 			echo '</div>';
 		}
 		echo '<div class="clear"></div></div>';
@@ -315,7 +320,6 @@
 			}
 		}
 		?>
-
 <script type="text/javascript">
 (function($){
 	var default_color = '<?php echo $default_color; ?>',
@@ -445,7 +449,7 @@
 ?>
 
 <div class="wrap">
-<h2><?php _e( 'Custom Header' ); ?></h2>
+<h1><?php _e( 'Custom Header' ); ?></h1>
 
 <?php if ( current_user_can( 'customize' ) ) { ?>
 <div class="notice notice-info hide-if-no-customize">
@@ -508,7 +512,7 @@
 </tr>
 <?php endif; ?>
 
-<?php if ( current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
+<?php if ( current_user_can( 'upload_files' ) && current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
 <tr>
 <th scope="row"><?php _e( 'Select Image' ); ?></th>
 <td>
@@ -518,16 +522,48 @@
 		printf( __( 'Images of exactly <strong>%1$d &times; %2$d pixels</strong> will be used as-is.' ) . '<br />', get_theme_support( 'custom-header', 'width' ), get_theme_support( 'custom-header', 'height' ) );
 	} elseif ( current_theme_supports( 'custom-header', 'flex-height' ) ) {
 		if ( ! current_theme_supports( 'custom-header', 'flex-width' ) )
-			printf( __( 'Images should be at least <strong>%1$d pixels</strong> wide.' ) . ' ', get_theme_support( 'custom-header', 'width' ) );
+			printf(
+				/* translators: %s: size in pixels */
+				__( 'Images should be at least %s wide.' ) . ' ',
+				sprintf(
+					/* translators: %d: custom header width */
+					'<strong>' . __( '%d pixels' ) . '</strong>',
+					get_theme_support( 'custom-header', 'width' )
+				)
+			);
 	} elseif ( current_theme_supports( 'custom-header', 'flex-width' ) ) {
 		if ( ! current_theme_supports( 'custom-header', 'flex-height' ) )
-			printf( __( 'Images should be at least <strong>%1$d pixels</strong> tall.' ) . ' ', get_theme_support( 'custom-header', 'height' ) );
+			printf(
+				/* translators: %s: size in pixels */
+				__( 'Images should be at least %s tall.' ) . ' ',
+				sprintf(
+					/* translators: %d: custom header height */
+					'<strong>' . __( '%d pixels' ) . '</strong>',
+					get_theme_support( 'custom-header', 'height' )
+				)
+			);
 	}
 	if ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) {
 		if ( current_theme_supports( 'custom-header', 'width' ) )
-			printf( __( 'Suggested width is <strong>%1$d pixels</strong>.' ) . ' ', get_theme_support( 'custom-header', 'width' ) );
+			printf(
+				/* translators: %s: size in pixels */
+				__( 'Suggested width is %s.' ) . ' ',
+				sprintf(
+					/* translators: %d: custom header width */
+					'<strong>' . __( '%d pixels' ) . '</strong>',
+					get_theme_support( 'custom-header', 'width' )
+				)
+			);
 		if ( current_theme_supports( 'custom-header', 'height' ) )
-			printf( __( 'Suggested height is <strong>%1$d pixels</strong>.' ) . ' ', get_theme_support( 'custom-header', 'height' ) );
+			printf(
+				/* translators: %s: size in pixels */
+				__( 'Suggested height is %s.' ) . ' ',
+				sprintf(
+					/* translators: %d: custom header height */
+					'<strong>' . __( '%d pixels' ) . '</strong>',
+					get_theme_support( 'custom-header', 'height' )
+				)
+			);
 	}
 	?></p>
 	<form enctype="multipart/form-data" id="upload-form" class="wp-upload-form" method="post" action="<?php echo esc_url( add_query_arg( 'step', 2 ) ) ?>">
@@ -536,7 +572,7 @@
 		<input type="file" id="upload" name="import" />
 		<input type="hidden" name="action" value="save" />
 		<?php wp_nonce_field( 'custom-header-upload', '_wpnonce-custom-header-upload' ); ?>
-		<?php submit_button( __( 'Upload' ), 'button', 'submit', false ); ?>
+		<?php submit_button( __( 'Upload' ), '', 'submit', false ); ?>
 	</p>
 	<?php
 		$modal_update_href = esc_url( add_query_arg( array(
@@ -594,18 +630,18 @@
 <th scope="row"><?php _e( 'Remove Image' ); ?></th>
 <td>
 	<p><?php _e( 'This will remove the header image. You will not be able to restore any customizations.' ) ?></p>
-	<?php submit_button( __( 'Remove Header Image' ), 'button', 'removeheader', false ); ?>
+	<?php submit_button( __( 'Remove Header Image' ), '', 'removeheader', false ); ?>
 </td>
 </tr>
 	<?php endif;
 
-	$default_image = get_theme_support( 'custom-header', 'default-image' );
+	$default_image = sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() );
 	if ( $default_image && get_header_image() != $default_image ) : ?>
 <tr>
 <th scope="row"><?php _e( 'Reset Image' ); ?></th>
 <td>
 	<p><?php _e( 'This will restore the original header image. You will not be able to restore any customizations.' ) ?></p>
-	<?php submit_button( __( 'Restore Original Header Image' ), 'button', 'resetheader', false ); ?>
+	<?php submit_button( __( 'Restore Original Header Image' ), '', 'resetheader', false ); ?>
 </td>
 </tr>
 	<?php endif; ?>
@@ -681,8 +717,13 @@
 	 */
 	public function step_2() {
 		check_admin_referer('custom-header-upload', '_wpnonce-custom-header-upload');
-		if ( ! current_theme_supports( 'custom-header', 'uploads' ) )
-			wp_die( __( 'Cheatin&#8217; uh?' ), 403 );
+		if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
+			wp_die(
+				'<h1>' . __( 'Something went wrong.' ) . '</h1>' .
+				'<p>' . __( 'The current theme does not support uploading a custom header image.' ) . '</p>',
+				403
+			);
+		}
 
 		if ( empty( $_POST ) && isset( $_GET['file'] ) ) {
 			$attachment_id = absint( $_GET['file'] );
@@ -753,14 +794,14 @@
 		?>
 
 <div class="wrap">
-<h2><?php _e( 'Crop Header Image' ); ?></h2>
+<h1><?php _e( 'Crop Header Image' ); ?></h1>
 
 <form method="post" action="<?php echo esc_url(add_query_arg('step', 3)); ?>">
 	<p class="hide-if-no-js"><?php _e('Choose the part of the image you want to use as your header.'); ?></p>
-	<p class="hide-if-js"><strong><?php _e( 'You need Javascript to choose a part of the image.'); ?></strong></p>
+	<p class="hide-if-js"><strong><?php _e( 'You need JavaScript to choose a part of the image.'); ?></strong></p>
 
 	<div id="crop_image" style="position: relative">
-		<img src="<?php echo esc_url( $url ); ?>" id="upload" width="<?php echo $width; ?>" height="<?php echo $height; ?>" />
+		<img src="<?php echo esc_url( $url ); ?>" id="upload" width="<?php echo $width; ?>" height="<?php echo $height; ?>" alt="" />
 	</div>
 
 	<input type="hidden" name="x1" id="x1" value="0"/>
@@ -778,7 +819,7 @@
 	<?php submit_button( __( 'Crop and Publish' ), 'primary', 'submit', false ); ?>
 	<?php
 	if ( isset( $oitar ) && 1 == $oitar && ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) )
-		submit_button( __( 'Skip Cropping, Publish Image as Is' ), 'secondary', 'skip-cropping', false );
+		submit_button( __( 'Skip Cropping, Publish Image as Is' ), '', 'skip-cropping', false );
 	?>
 	</p>
 </form>
@@ -828,15 +869,27 @@
 	 * Display third step of custom header image page.
 	 *
 	 * @since 2.1.0
+	 * @since 4.4.0 Switched to using wp_get_attachment_url() instead of the guid
+	 *              for retrieving the header image URL.
 	 */
 	public function step_3() {
 		check_admin_referer( 'custom-header-crop-image' );
 
-		if ( ! current_theme_supports( 'custom-header', 'uploads' ) )
-			wp_die( __( 'Cheatin&#8217; uh?' ), 403 );
+		if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
+			wp_die(
+				'<h1>' . __( 'Something went wrong.' ) . '</h1>' .
+				'<p>' . __( 'The current theme does not support uploading a custom header image.' ) . '</p>',
+				403
+			);
+		}
 
-		if ( ! empty( $_POST['skip-cropping'] ) && ! ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) )
-			wp_die( __( 'Cheatin&#8217; uh?' ), 403 );
+		if ( ! empty( $_POST['skip-cropping'] ) && ! ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) ) {
+			wp_die(
+				'<h1>' . __( 'Something went wrong.' ) . '</h1>' .
+				'<p>' . __( 'The current theme does not support a flexible sized header image.' ) . '</p>',
+				403
+			);
+		}
 
 		if ( $_POST['oitar'] > 1 ) {
 			$_POST['x1'] = $_POST['x1'] * $_POST['oitar'];
@@ -876,7 +929,7 @@
 		// Update the attachment
 		$attachment_id = $this->insert_attachment( $object, $cropped );
 
-		$url = $object['guid'];
+		$url = wp_get_attachment_url( $attachment_id );
 		$this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
 
 		// Cleanup.
@@ -909,7 +962,7 @@
 	 */
 	public function admin_page() {
 		if ( ! current_user_can('edit_theme_options') )
-			wp_die(__('You do not have permission to customize headers.'));
+			wp_die(__('Sorry, you are not allowed to customize headers.'));
 		$step = $this->step();
 		if ( 2 == $step )
 			$this->step_2();
@@ -923,6 +976,9 @@
 	 * Unused since 3.5.0.
 	 *
 	 * @since 3.4.0
+	 *
+	 * @param array $form_fields
+	 * @return array $form_fields
 	 */
 	public function attachment_fields_to_edit( $form_fields ) {
 		return $form_fields;
@@ -932,6 +988,9 @@
 	 * Unused since 3.5.0.
 	 *
 	 * @since 3.4.0
+	 *
+	 * @param array $tabs
+	 * @return array $tabs
 	 */
 	public function filter_upload_tabs( $tabs ) {
 		return $tabs;
@@ -941,13 +1000,13 @@
 	 * Choose a header image, selected from existing uploaded and default headers,
 	 * or provide an array of uploaded header data (either new, or from media library).
 	 *
+	 * @since 3.4.0
+	 *
 	 * @param mixed $choice Which header image to select. Allows for values of 'random-default-image',
 	 * 	for randomly cycling among the default images; 'random-uploaded-image', for randomly cycling
 	 * 	among the uploaded images; the key of a default image registered for that theme; and
-	 * 	the key of an image uploaded for that theme (the basename of the URL).
+	 * 	the key of an image uploaded for that theme (the attachment ID of the image).
 	 *  Or an array of arguments: attachment_id, url, width, height. All are required.
-	 *
-	 * @since 3.4.0
 	 */
 	final public function set_header_image( $choice ) {
 		if ( is_array( $choice ) || is_object( $choice ) ) {
@@ -999,7 +1058,7 @@
 	 * @since 3.4.0
 	 */
 	final public function remove_header_image() {
-		return $this->set_header_image( 'remove-header' );
+		$this->set_header_image( 'remove-header' );
 	}
 
 	/**
@@ -1013,9 +1072,10 @@
 		$this->process_default_headers();
 		$default = get_theme_support( 'custom-header', 'default-image' );
 
-		if ( ! $default )
-			return $this->remove_header_image();
-
+		if ( ! $default ) {
+			$this->remove_header_image();
+			return;
+		}
 		$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
 
 		$default_data = array();
@@ -1033,6 +1093,9 @@
 	/**
 	 * Calculate width and height based on what the currently selected theme supports.
 	 *
+	 * @since 3.9.0
+	 *
+	 * @param array $dimensions
 	 * @return array dst_height and dst_width of header image.
 	 */
 	final public function get_header_dimensions( $dimensions ) {
@@ -1082,14 +1145,15 @@
 	/**
 	 * Create an attachment 'object'.
 	 *
-	 * @param string $cropped Cropped image URL.
-	 * @param int $parent_attachment_id Attachment ID of parent image.
+	 * @since 3.9.0
 	 *
+	 * @param string $cropped              Cropped image URL.
+	 * @param int    $parent_attachment_id Attachment ID of parent image.
 	 * @return array Attachment object.
 	 */
 	final public function create_attachment_object( $cropped, $parent_attachment_id ) {
 		$parent = get_post( $parent_attachment_id );
-		$parent_url = $parent->guid;
+		$parent_url = wp_get_attachment_url( $parent->ID );
 		$url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
 
 		$size = @getimagesize( $cropped );
@@ -1098,10 +1162,10 @@
 		$object = array(
 			'ID' => $parent_attachment_id,
 			'post_title' => basename($cropped),
-			'post_content' => $url,
 			'post_mime_type' => $image_type,
 			'guid' => $url,
-			'context' => 'custom-header'
+			'context' => 'custom-header',
+			'post_parent' => $parent_attachment_id,
 		);
 
 		return $object;
@@ -1110,16 +1174,26 @@
 	/**
 	 * Insert an attachment and its metadata.
 	 *
-	 * @param array $object Attachment object.
+	 * @since 3.9.0
+	 *
+	 * @param array  $object  Attachment object.
 	 * @param string $cropped Cropped image URL.
-	 *
 	 * @return int Attachment ID.
 	 */
 	final public function insert_attachment( $object, $cropped ) {
+		$parent_id = isset( $object['post_parent'] ) ? $object['post_parent'] : null;
+		unset( $object['post_parent'] );
+
 		$attachment_id = wp_insert_attachment( $object, $cropped );
 		$metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
+
+		// If this is a crop, save the original attachment ID as metadata.
+		if ( $parent_id ) {
+			$metadata['attachment_parent'] = $parent_id;
+		}
+
 		/**
-		 * Filter the header image attachment metadata.
+		 * Filters the header image attachment metadata.
 		 *
 		 * @since 3.9.0
 		 *
@@ -1128,13 +1202,17 @@
 		 * @param array $metadata Attachment metadata.
 		 */
 		$metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
+
 		wp_update_attachment_metadata( $attachment_id, $metadata );
+
 		return $attachment_id;
 	}
 
 	/**
 	 * Gets attachment uploaded by Media Manager, crops it, then saves it as a
 	 * new object. Returns JSON-encoded object details.
+	 *
+	 * @since 3.9.0
 	 */
 	public function ajax_header_crop() {
 		check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
@@ -1175,11 +1253,18 @@
 
 		$object = $this->create_attachment_object( $cropped, $attachment_id );
 
-		unset( $object['ID'] );
+		$previous = $this->get_previous_crop( $object );
+
+		if ( $previous ) {
+			$object['ID'] = $previous;
+		} else {
+			unset( $object['ID'] );
+		}
 
 		$new_attachment_id = $this->insert_attachment( $object, $cropped );
 
 		$object['attachment_id'] = $new_attachment_id;
+		$object['url']           = wp_get_attachment_url( $new_attachment_id );;
 		$object['width']         = $dimensions['dst_width'];
 		$object['height']        = $dimensions['dst_height'];
 
@@ -1192,6 +1277,8 @@
 	 *
 	 * Triggered when the user tries adds a new header image from the
 	 * Media Manager, even if s/he doesn't save that change.
+	 *
+	 * @since 3.9.0
 	 */
 	public function ajax_header_add() {
 		check_ajax_referer( 'header-add', 'nonce' );
@@ -1218,6 +1305,8 @@
 	 *
 	 * Triggered when the user clicks the overlay "X" button next to each image
 	 * choice in the Customizer's Header tool.
+	 *
+	 * @since 3.9.0
 	 */
 	public function ajax_header_remove() {
 		check_ajax_referer( 'header-remove', 'nonce' );
@@ -1238,8 +1327,20 @@
 		wp_send_json_success();
 	}
 
+	/**
+	 * Updates the last-used postmeta on a header image attachment after saving a new header image via the Customizer.
+	 *
+	 * @since 3.9.0
+	 *
+	 * @param WP_Customize_Manager $wp_customize Customize manager.
+	 */
 	public function customize_set_last_used( $wp_customize ) {
-		$data = $wp_customize->get_setting( 'header_image_data' )->post_value();
+
+		$header_image_data_setting = $wp_customize->get_setting( 'header_image_data' );
+		if ( ! $header_image_data_setting ) {
+			return;
+		}
+		$data = $header_image_data_setting->post_value();
 
 		if ( ! isset( $data['attachment_id'] ) ) {
 			return;
@@ -1250,6 +1351,13 @@
 		update_post_meta( $attachment_id, $key, time() );
 	}
 
+	/**
+	 * Gets the details of default header images if defined.
+	 *
+	 * @since 3.9.0
+	 *
+	 * @return array Default header images.
+	 */
 	public function get_default_header_images() {
 		$this->process_default_headers();
 
@@ -1283,10 +1391,16 @@
 		);
 
 		// The rest of the set comes after.
-		$header_images = array_merge( $header_images, $this->default_headers );
-		return $header_images;
+		return array_merge( $header_images, $this->default_headers );
 	}
 
+	/**
+	 * Gets the previously uploaded header images.
+	 *
+	 * @since 3.9.0
+	 *
+	 * @return array Uploaded header images.
+	 */
 	public function get_uploaded_header_images() {
 		$header_images = get_uploaded_header_images();
 		$timestamp_key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
@@ -1300,4 +1414,32 @@
 
 		return $header_images;
 	}
+
+	/**
+	 * Get the ID of a previous crop from the same base image.
+	 *
+	 * @since 4.9.0
+	 *
+	 * @param  array $object A crop attachment object.
+	 * @return int|false An attachment ID if one exists. False if none.
+	 */
+	public function get_previous_crop( $object ) {
+		$header_images = $this->get_uploaded_header_images();
+
+		// Bail early if there are no header images.
+		if ( empty( $header_images ) ) {
+			return false;
+		}
+
+		$previous = false;
+
+		foreach ( $header_images as $image ) {
+			if ( $image['attachment_parent'] === $object['post_parent'] ) {
+				$previous = $image['attachment_id'];
+				break;
+			}
+		}
+
+		return $previous;
+	}
 }