<?php
/**
 * Media class.
 *
 * @since 1.0.0
 *
 * @package Envira_Gallery
 * @author  Thomas Griffin
 */
class Envira_Gallery_Media {

    /**
     * Holds the class object.
     *
     * @since 1.0.0
     *
     * @var object
     */
    public static $instance;

    /**
     * Path to the file.
     *
     * @since 1.0.0
     *
     * @var string
     */
    public $file = __FILE__;

    /**
     * Holds the base class object.
     *
     * @since 1.0.0
     *
     * @var object
     */
    public $base;

    /**
     * Primary class constructor.
     *
     * @since 1.0.0
     */
    public function __construct() {

        // Load the base class object.
        $this->base = ( class_exists( 'Envira_Gallery' ) ? Envira_Gallery::get_instance() : Envira_Gallery_Lite::get_instance() );

        add_filter( 'wp_handle_upload', array( $this, 'fix_image_orientation' ) );

    }

    /**
    * Check if the EXIF orientation flag matches one of the values we're looking for
    * http://www.impulseadventure.com/photo/exif-orientation.html
    *
    * If it does, this means we need to rotate the image based on the orientation flag and then remove the flag.
    * This will ensure the image has the correct orientation, regardless of where it's displayed.
    *
    * Whilst most browsers and applications will read this flag to perform the rotation on displaying just the image, it's
    * not possible to do this in some situations e.g. displaying an image within a lightbox, or when the image is
    * within HTML markup.
    *
    * Orientation flags we're looking for:
    * 8: We need to rotate the image 90 degrees counter-clockwise
    * 3: We need to rotate the image 180 degrees
    * 6: We need to rotate the image 90 degrees clockwise (270 degrees counter-clockwise)
    *
    * @since 1.3.8.2
    *
    * @param array $file    Uploaded File
    * @return array         Uploaded File
    */
    public function fix_image_orientation( $file ) {

        // Check we have a file
        if ( ! file_exists( $file['file'] ) ) {
            return $file;
        }

        // Check we have a JPEG
        if ( $file['type'] !== 'image/jpg' && $file['type'] !== 'image/jpeg' ) {
            return $file;
        }

        // Attempt to read EXIF data from the image
        $exif_data = wp_read_image_metadata( $file['file'] );
        if ( ! $exif_data ) {
            return $file;
        }

        // Check if an orientation flag exists
        if ( ! isset( $exif_data['orientation'] ) ) {
            return $file;
        }

        // Check if the orientation flag matches one we're looking for
        $required_orientations = array( 8, 3, 6 );
        if ( ! in_array( $exif_data['orientation'], $required_orientations ) ) {
            return $file;
        }

        // If here, the orientation flag matches one we're looking for
        // Load the WordPress Image Editor class
        $image = wp_get_image_editor( $file['file'] );
        if ( is_wp_error( $image ) ) {
            // Something went wrong - abort
            return $file;
        } 

        // Store the source image EXIF and IPTC data in a variable, which we'll write 
        // back to the image once its orientation has changed
        // This is required because when we save an image, it'll lose its metadata.
        $source_size = getimagesize( $file['file'], $image_info );

        // Depending on the orientation flag, rotate the image
        switch ( $exif_data['orientation'] ) {

            /**
            * Rotate 90 degrees counter-clockwise
            */
            case 8:
                $image->rotate( 90 );
                break;

            /**
            * Rotate 180 degrees
            */
            case 3:
                $image->rotate( 180 );
                break;

            /**
            * Rotate 270 degrees counter-clockwise ($image->rotate always works counter-clockwise)
            */
            case 6:
                $image->rotate( 270 );
                break;

        }

        // Save the image, overwriting the existing image
        // This will discard the EXIF and IPTC data
        $image->save( $file['file'] );

        // Drop the EXIF orientation flag, otherwise applications will try to rotate the image
        // before display it, and we don't need that to happen as we've corrected the orientation

        // Write the EXIF and IPTC metadata to the revised image
        $result = $this->transfer_iptc_exif_to_image( $image_info, $file['file'], $exif_data['orientation'] );
        if ( ! $result ) {
            return $file;
        }
        
        // Read the image again to see if the EXIF data was preserved
        $exif_data = wp_read_image_metadata( $file['file'] );
        
        // Finally, return the data that's expected
        return $file;

    }

    /**
    * Transfers IPTC and EXIF data from a source image which contains either/both,
    * and saves it into a destination image's headers that might not have this IPTC
    * or EXIF data
    *
    * Useful for when you edit an image through PHP and need to preserve IPTC and EXIF
    * data
    *
    * @since 1.3.8.2
    *
    * @source http://php.net/iptcembed - ebashkoff at gmail dot com
    *
    * @param string $image_info             EXIF and IPTC image information from the source image, using getimagesize()
    * @param string $destination_image      Path and File of Destination Image, which needs IPTC and EXIF data
    * @param int    $original_orientation   The image's original orientation, before we changed it. 
    *                                       Used when we replace this orientation in the EXIF data
    * @return bool                          Success
    */
    private function transfer_iptc_exif_to_image( $image_info, $destination_image, $original_orientation ) {

        // Check destination exists
        if ( ! file_exists( $destination_image ) ) {
            return false;
        }

        // Get EXIF data from the image info, and create the IPTC segment
        $exif_data = ( ( is_array( $image_info ) && key_exists( 'APP1', $image_info ) ) ? $image_info['APP1'] : null );
        if ( $exif_data ) {
            // Find the image's original orientation flag, and change it to 1
            // This prevents applications and browsers re-rotating the image, when we've already performed that function
            $exif_data = str_replace( chr( dechex( $original_orientation ) ) , chr( 0x1 ), $exif_data );

            $exif_length = strlen( $exif_data ) + 2;
            if ( $exif_length > 0xFFFF ) {
                return false;
            }

            // Construct EXIF segment
            $exif_data = chr(0xFF) . chr(0xE1) . chr( ( $exif_length >> 8 ) & 0xFF) . chr( $exif_length & 0xFF ) . $exif_data;
        }

        // Get IPTC data from the source image, and create the IPTC segment
        $iptc_data = ( ( is_array( $image_info ) && key_exists( 'APP13', $image_info ) ) ? $image_info['APP13'] : null );
        if ( $iptc_data ) {
            $iptc_length = strlen( $iptc_data ) + 2;
            if ( $iptc_length > 0xFFFF ) {
                return false;
            }

            // Construct IPTC segment
            $iptc_data = chr(0xFF) . chr(0xED) . chr( ( $iptc_length >> 8) & 0xFF) . chr( $iptc_length & 0xFF ) . $iptc_data;
        }    

        // Get the contents of the destination image
        $destination_image_contents = file_get_contents( $destination_image );
        if ( ! $destination_image_contents ) {
            return false;
        }
        if ( strlen( $destination_image_contents ) == 0 ) {
            return false;
        }

        // Build the EXIF and IPTC data headers
        $destination_image_contents = substr( $destination_image_contents, 2 );
        $portion_to_add = chr(0xFF) . chr(0xD8); // Variable accumulates new & original IPTC application segments
        $exif_added = ! $exif_data;
        $iptc_added = ! $iptc_data;

        while ( ( substr( $destination_image_contents, 0, 2 ) & 0xFFF0 ) === 0xFFE0 ) {
            $segment_length = ( substr( $destination_image_contents, 2, 2 ) & 0xFFFF );
            $iptc_segment_number = ( substr( $destination_image_contents, 1, 1 ) & 0x0F );   // Last 4 bits of second byte is IPTC segment #
            if ( $segment_length <= 2 ) {
                return false;
            }
            
            $thisexistingsegment = substr( $destination_image_contents, 0, $segment_length + 2 );
            if ( ( 1 <= $iptc_segment_number) && ( ! $exif_added ) ) {
                $portion_to_add .= $exif_data;
                $exif_added = true;
                if ( 1 === $iptc_segment_number ) {
                    $thisexistingsegment = '';
                }
            }

            if ( ( 13 <= $iptc_segment_number ) && ( ! $iptc_added ) ) {
                $portion_to_add .= $iptc_data;
                $iptc_added = true;
                if ( 13 === $iptc_segment_number ) {
                    $thisexistingsegment = '';
                }
            }

            $portion_to_add .= $thisexistingsegment;
            $destination_image_contents = substr( $destination_image_contents, $segment_length + 2 );
        }

        // Write the EXIF and IPTC data to the new file
        if ( ! $exif_added ) {
            $portion_to_add .= $exif_data;
        }
        if ( ! $iptc_added ) {
            $portion_to_add .= $iptc_data;
        }

        $output_file = fopen( $destination_image, 'w' );
        if ( $output_file ) {
            return fwrite( $output_file, $portion_to_add . $destination_image_contents ); 
        }

        return false;
        
    }

    /**
     * Returns the singleton instance of the class.
     *
     * @since 1.0.0
     *
     * @return object The Envira_Gallery_Media object.
     */
    public static function get_instance() {

        if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Envira_Gallery_Media ) ) {
            self::$instance = new Envira_Gallery_Media();
        }

        return self::$instance;

    }

}

// Load the media class.
$envira_gallery_media = Envira_Gallery_Media::get_instance();