: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$attachment_id = wp_insert_attachment( $attachment, $file );
return compact( 'attachment_id', 'file', 'filename', 'url', 'type' );
* Displays third step of custom header image page.
* @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' ) ) {
'<h1>' . __( 'Something went wrong.' ) . '</h1>' .
'<p>' . __( 'The active theme does not support uploading a custom header image.' ) . '</p>',
if ( ! empty( $_POST['skip-cropping'] )
&& ! current_theme_supports( 'custom-header', 'flex-height' )
&& ! current_theme_supports( 'custom-header', 'flex-width' )
'<h1>' . __( 'Something went wrong.' ) . '</h1>' .
'<p>' . __( 'The active theme does not support a flexible sized header image.' ) . '</p>',
if ( $_POST['oitar'] > 1 ) {
$_POST['x1'] = $_POST['x1'] * $_POST['oitar'];
$_POST['y1'] = $_POST['y1'] * $_POST['oitar'];
$_POST['width'] = $_POST['width'] * $_POST['oitar'];
$_POST['height'] = $_POST['height'] * $_POST['oitar'];
$attachment_id = absint( $_POST['attachment_id'] );
$original = get_attached_file( $attachment_id );
$dimensions = $this->get_header_dimensions(
'height' => $_POST['height'],
'width' => $_POST['width'],
$height = $dimensions['dst_height'];
$width = $dimensions['dst_width'];
if ( empty( $_POST['skip-cropping'] ) ) {
$cropped = wp_crop_image(
} elseif ( ! empty( $_POST['create-new-attachment'] ) ) {
$cropped = _copy_image_file( $attachment_id );
$cropped = get_attached_file( $attachment_id );
if ( ! $cropped || is_wp_error( $cropped ) ) {
wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) );
/** This filter is documented in wp-admin/includes/class-custom-image-header.php */
$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
$attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, 'custom-header' );
if ( ! empty( $_POST['create-new-attachment'] ) ) {
unset( $attachment['ID'] );
// Update the attachment.
$attachment_id = $this->insert_attachment( $attachment, $cropped );
$url = wp_get_attachment_url( $attachment_id );
$this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
$medium = str_replace( wp_basename( $original ), 'midsize-' . wp_basename( $original ), $original );
if ( file_exists( $medium ) ) {
wp_delete_file( $medium );
if ( empty( $_POST['create-new-attachment'] ) && empty( $_POST['skip-cropping'] ) ) {
wp_delete_file( $original );
return $this->finished();
* Displays last step of custom header image page.
public function finished() {
* Displays the page based on the current step.
public function admin_page() {
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_die( __( 'Sorry, you are not allowed to customize headers.' ) );
} elseif ( 3 === $step ) {
* @param array $form_fields
* @return array $form_fields
public function attachment_fields_to_edit( $form_fields ) {
public function filter_upload_tabs( $tabs ) {
* Chooses a header image, selected from existing uploaded and default headers,
* or provides an array of uploaded header data (either new, or from media library).
* @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 attachment ID of the image). Or an array of arguments: attachment_id,
* url, width, height. All are required.
final public function set_header_image( $choice ) {
if ( is_array( $choice ) || is_object( $choice ) ) {
$choice = (array) $choice;
if ( ! isset( $choice['attachment_id'] ) || ! isset( $choice['url'] ) ) {
$choice['url'] = sanitize_url( $choice['url'] );
$header_image_data = (object) array(
'attachment_id' => $choice['attachment_id'],
'thumbnail_url' => $choice['url'],
'height' => $choice['height'],
'width' => $choice['width'],
update_post_meta( $choice['attachment_id'], '_wp_attachment_is_custom_header', get_stylesheet() );
set_theme_mod( 'header_image', $choice['url'] );
set_theme_mod( 'header_image_data', $header_image_data );
if ( in_array( $choice, array( 'remove-header', 'random-default-image', 'random-uploaded-image' ), true ) ) {
set_theme_mod( 'header_image', $choice );
remove_theme_mod( 'header_image_data' );
$uploaded = get_uploaded_header_images();
if ( $uploaded && isset( $uploaded[ $choice ] ) ) {
$header_image_data = $uploaded[ $choice ];
$this->process_default_headers();
if ( isset( $this->default_headers[ $choice ] ) ) {
$header_image_data = $this->default_headers[ $choice ];
set_theme_mod( 'header_image', sanitize_url( $header_image_data['url'] ) );
set_theme_mod( 'header_image_data', $header_image_data );
* Removes a header image.
final public function remove_header_image() {
$this->set_header_image( 'remove-header' );
* Resets a header image to the default image for the theme.
* This method does not do anything if the theme does not have a default header image.
final public function reset_header_image() {
$this->process_default_headers();
$default = get_theme_support( 'custom-header', 'default-image' );
$this->remove_header_image();
$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
foreach ( $this->default_headers as $header => $details ) {
if ( $details['url'] === $default ) {
$default_data = $details;
set_theme_mod( 'header_image', $default );
set_theme_mod( 'header_image_data', (object) $default_data );
* Calculates width and height based on what the currently selected theme supports.
* @param array $dimensions
* @return array dst_height and dst_width of header image.
final public function get_header_dimensions( $dimensions ) {
$width = absint( $dimensions['width'] );
$height = absint( $dimensions['height'] );
$theme_height = get_theme_support( 'custom-header', 'height' );
$theme_width = get_theme_support( 'custom-header', 'width' );
$has_flex_width = current_theme_supports( 'custom-header', 'flex-width' );
$has_flex_height = current_theme_supports( 'custom-header', 'flex-height' );
$has_max_width = current_theme_supports( 'custom-header', 'max-width' );
// For flex, limit size of image displayed to 1500px unless theme says otherwise.
$max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
$max_width = max( $max_width, $theme_width );
if ( $has_flex_height && ( ! $has_flex_width || $width > $max_width ) ) {
$dst['dst_height'] = absint( $height * ( $max_width / $width ) );
} elseif ( $has_flex_height && $has_flex_width ) {
$dst['dst_height'] = $height;
$dst['dst_height'] = $theme_height;
if ( $has_flex_width && ( ! $has_flex_height || $width > $max_width ) ) {
$dst['dst_width'] = absint( $width * ( $max_width / $width ) );
} elseif ( $has_flex_width && $has_flex_height ) {
$dst['dst_width'] = $width;
$dst['dst_width'] = $theme_width;
* Creates an attachment 'object'.
* @param string $cropped Cropped image URL.
* @param int $parent_attachment_id Attachment ID of parent image.
* @return array An array with attachment object data.
final public function create_attachment_object( $cropped, $parent_attachment_id ) {
_deprecated_function( __METHOD__, '6.5.0', 'wp_copy_parent_attachment_properties()' );
$parent = get_post( $parent_attachment_id );
$parent_url = wp_get_attachment_url( $parent->ID );
$url = str_replace( wp_basename( $parent_url ), wp_basename( $cropped ), $parent_url );
$size = wp_getimagesize( $cropped );
$image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
'ID' => $parent_attachment_id,
'post_title' => wp_basename( $cropped ),
'post_mime_type' => $image_type,
'context' => 'custom-header',
'post_parent' => $parent_attachment_id,
* Inserts an attachment and its metadata.
* @param array $attachment An array with attachment object data.
* @param string $cropped File path to cropped image.
* @return int Attachment ID.
final public function insert_attachment( $attachment, $cropped ) {
$parent_id = isset( $attachment['post_parent'] ) ? $attachment['post_parent'] : null;
unset( $attachment['post_parent'] );
$attachment_id = wp_insert_attachment( $attachment, $cropped );
$metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
// If this is a crop, save the original attachment ID as metadata.
$metadata['attachment_parent'] = $parent_id;
* Filters the header image attachment metadata.
* @see wp_generate_attachment_metadata()
* @param array $metadata Attachment metadata.
$metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
wp_update_attachment_metadata( $attachment_id, $metadata );
* Gets attachment uploaded by Media Manager, crops it, then saves it as a
* new object. Returns JSON-encoded object details.
public function ajax_header_crop() {
check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
$crop_details = $_POST['cropDetails'];
$dimensions = $this->get_header_dimensions(
'height' => $crop_details['height'],
'width' => $crop_details['width'],
$attachment_id = absint( $_POST['id'] );
$cropped = wp_crop_image(
(int) $crop_details['x1'],
(int) $crop_details['y1'],
(int) $crop_details['width'],
(int) $crop_details['height'],
(int) $dimensions['dst_width'],
(int) $dimensions['dst_height']
if ( ! $cropped || is_wp_error( $cropped ) ) {
wp_send_json_error( array( 'message' => __( 'Image could not be processed. Please go back and try again.' ) ) );
/** This filter is documented in wp-admin/includes/class-custom-image-header.php */
$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
$attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, 'custom-header' );
$previous = $this->get_previous_crop( $attachment );
$attachment['ID'] = $previous;
unset( $attachment['ID'] );
$new_attachment_id = $this->insert_attachment( $attachment, $cropped );
$attachment['attachment_id'] = $new_attachment_id;
$attachment['url'] = wp_get_attachment_url( $new_attachment_id );
$attachment['width'] = $dimensions['dst_width'];
$attachment['height'] = $dimensions['dst_height'];
wp_send_json_success( $attachment );
* Given an attachment ID for a header image, updates its "last used"
* Triggered when the user tries adds a new header image from the
* Media Manager, even if s/he doesn't save that change.
public function ajax_header_add() {
check_ajax_referer( 'header-add', 'nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
$attachment_id = absint( $_POST['attachment_id'] );
if ( $attachment_id < 1 ) {
$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
update_post_meta( $attachment_id, $key, time() );
update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
* Given an attachment ID for a header image, unsets it as a user-uploaded
* header image for the active theme.
* Triggered when the user clicks the overlay "X" button next to each image
* choice in the Customizer's Header tool.
public function ajax_header_remove() {
check_ajax_referer( 'header-remove', 'nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
$attachment_id = absint( $_POST['attachment_id'] );
if ( $attachment_id < 1 ) {
$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
delete_post_meta( $attachment_id, $key );
delete_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );