path( 'lib/classes/compatibility/js/shortpixel.js', 'url' ), array( 'shortpixel' ), '', true ); $image_host = ud_get_stateless_media()->get_gs_host(); $bucketLink = apply_filters( 'wp_stateless_bucket_link', $image_host ); wp_localize_script( 'stateless-short-pixel', '_stateless_short_pixel', array( 'baseurl' => $upload_dir[ 'baseurl' ], 'bucketLink' => $bucketLink, ) ); } /** * Return Path of ShortPixel backup path. * Bypass the checking whether a backup file exist when returning backup path. * @todo check on GCS if backup really exist. Maybe we can implement transient caching for performance. * * @param $ret * @param $file * @param $thumbs * @return string */ public function getBackupFolderAny( $ret, $file, $thumbs ) { if( $ret == false ) { $fullSubDir = $this->returnSubDir( $file ); $ret = SHORTPIXEL_BACKUP_FOLDER . '/' . $fullSubDir; } return $ret; } /** * Check whether image exist on GCS. * Only when image is not available on server. * * @param $return * @param $path * @param null $id * @return bool */ public function shortpixel_image_exists( $return, $path, $id = null ) { if( $return ) return $return; $key = "stateless_url_to_postid_" . md5( $path ); $return = get_transient( $key ); // echo "\npath: $path \nKey: $key\nReturn: $return\nID: $id\n "; if( !$return ) { // Checking by matching file name in gs_name and $path. if( !empty( $id ) ) { $metadata = wp_get_attachment_metadata( $id ); $basename = basename( $path ); if( !empty( $metadata[ 'gs_name' ] ) ) { $gs_basename = basename( $metadata[ 'gs_name' ] ); if( $gs_basename == $basename ) { $return = true; } if( is_array( $metadata[ 'sizes' ] ) ) { foreach( $metadata[ 'sizes' ] as $key => &$data ) { if( empty( $data[ 'gs_name' ] ) ) continue; $gs_basename = basename( $data[ 'gs_name' ] ); if( $gs_basename == $basename ) { $return = true; } } } } } // Directly check on GCS if image exist. else if( empty( $id ) ) { $wp_uploads_dir = wp_get_upload_dir(); $gs_name = str_replace( trailingslashit( $wp_uploads_dir[ 'basedir' ] ), '', $path ); $gs_name = str_replace( trailingslashit( $wp_uploads_dir[ 'baseurl' ] ), '', $gs_name ); $gs_name = str_replace( trailingslashit( ud_get_stateless_media()->get_gs_host() ), '', $gs_name ); $gs_name = apply_filters( 'wp_stateless_file_name', $gs_name ); if( $media = ud_get_stateless_media()->get_client()->media_exists( $gs_name ) ) { $return = true; } } set_transient( $key, $return, 10 * MINUTE_IN_SECONDS ); } return $return; } /** * Short-circuit filter. * We need to remove backup image from GCS when shortpixel tries to delete from server. * We are returning true to short-circuit in ephemeral mode, because file not exist in server. * * @param $return * @param $paths * @return bool */ public function shortpixel_skip_delete_backups_and_webps( $return, $paths ) { if( empty( $paths ) || !is_array( $paths ) ) return $return; $sp__uploads = wp_upload_dir(); $fullSubDir = $this->returnSubDir( $paths[ 0 ] ); $backup_path = SHORTPIXEL_BACKUP_FOLDER . '/' . $fullSubDir; foreach( $paths as $key => $path ) { // Removing backup $name = apply_filters( 'wp_stateless_file_name', SHORTPIXEL_BACKUP . '/' . $fullSubDir . basename( $path ) ); do_action( 'sm:sync::deleteFile', $name ); // Removing WebP $backup_images = \WPShortPixelSettings::getOpt( 'wp-short-create-webp' ); if( $backup_images ) { $name = str_replace( $sp__uploads[ 'basedir' ], '', $path ); $name = apply_filters( 'wp_stateless_file_name', $name . '.webp' ); do_action( 'sm:sync::deleteFile', $name ); } } if( ud_get_stateless_media()->get( 'sm.mode' ) == 'ephemeral' ) { return true; } // When ephemeral mode isn't set backup files will be available in server. So no short-circuit. return $return; } /** * Skip the original backup process in ephemeral mode. * * @param $return * @param $mainPath * @param $PATHs * @return bool */ public function shortpixel_skip_backup( $return, $mainPath, $PATHs ) { if( ud_get_stateless_media()->get( 'sm.mode' ) == 'ephemeral' ) { return true; } return $return; } /** * In ephemeral mode we need to take backup directly from original location. * Because we will skip the backup process of shortpixel. * * @param $metadata * @param $attachment_id * @return mixed */ public function wp_update_attachment_metadata( $metadata, $attachment_id ) { if( ud_get_stateless_media()->get( 'sm.mode' ) == 'ephemeral' ) { $backup_images = \WPShortPixelSettings::getOpt( 'wp-short-backup_images' ); if( $backup_images ) { $this->sync_backup_file( $attachment_id, $metadata, false, array( 'before_optimization' => true ) ); } } return $metadata; } /** * Sync image after optimization. * * @param $id */ public function shortpixel_image_optimised( $id ) { $metadata = wp_get_attachment_metadata( $id ); ud_get_stateless_media()->add_media( $metadata, $id, true ); // Sync the webp to GCS $create_webp = \WPShortPixelSettings::getOpt( 'wp-short-create-webp' ); if( $create_webp ) { $this->sync_webp_file($id, $metadata); } // Don't needed in ephemeral mode. In ephemeral mode the back will be sync once on wp_update_attachment_metadata filter. if( ud_get_stateless_media()->get( 'sm.mode' ) !== 'ephemeral' ) { $this->sync_backup_file( $id, $metadata, true ); } } /** * Before shortpixel tries to restore from backup we need to make files available on server. * * @param $id * @param null $metadata */ public function shortpixel_before_restore_image( $id, $metadata = null ) { $this->sync_backup_file( $id, $metadata, true, array( 'download' => true ) ); } /** * Disable default shortpixel restore and directly update image on GCS from backup copy in GCS. * * @param $return * @param null $id * @return bool */ public function shortpixel_skip_restore_image( $return, $id = null ) { if( ud_get_stateless_media()->get( 'sm.mode' ) === 'ephemeral' ) { $this->client = ud_get_stateless_media()->get_client(); $this->client->copy_media( 'localhost/ShortpixelBackups/wp-content/uploads/2019/04/htpps.png', 'localhost/2019/04/htpps.png' ); return true; } return $return; } /** * Sync backup image * * @param $id * @param null $metadata * @param bool $force * @param array $args * before_optimization : pass true if you want to sync directly from original path instead of backup path. */ public function sync_backup_file( $id, $metadata = null, $force = false, $args = array() ) { $args = wp_parse_args( $args, array( 'download' => false, // whether to only download. 'before_optimization' => false, // whether to delete local file in ephemeral mode. ) ); /* Get metadata in case if method is called directly. */ if( empty( $metadata ) ) { $metadata = wp_get_attachment_metadata( $id ); } /* Now we go through all available image sizes and upload them to Google Storage */ if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) { // Sync backup file with GCS $file_path = get_attached_file( $id ); $fullSubDir = $this->returnSubDir( $file_path ); $backup_path = SHORTPIXEL_BACKUP_FOLDER . '/' . $fullSubDir; if( $args[ 'before_optimization' ] ) { $upload_dir = wp_upload_dir(); $backup_path = $upload_dir[ 'basedir' ] . '/' . dirname( $metadata[ 'file' ] ); $args = array( 'ephemeral' => false ); } $absolutePath = trailingslashit( $backup_path ) . basename( $metadata[ 'file' ] ); $name = apply_filters( 'wp_stateless_file_name', SHORTPIXEL_BACKUP . '/' . $fullSubDir . basename( $metadata[ 'file' ] ) ); do_action( 'sm:sync::syncFile', $name, $absolutePath, $force, $args ); foreach( (array) $metadata[ 'sizes' ] as $image_size => $data ) { $absolutePath = trailingslashit( $backup_path ) . $data[ 'file' ]; $name = apply_filters( 'wp_stateless_file_name', SHORTPIXEL_BACKUP . '/' . $fullSubDir . $data[ 'file' ] ); do_action( 'sm:sync::syncFile', $name, $absolutePath, $force, $args ); } } } /** * Sync from sync tab * * @param $id * @param null $metadata */ public function sync_webp_file( $id, $metadata = null ) { /* Get metadata in case if method is called directly. */ if( empty( $metadata ) ) { $metadata = wp_get_attachment_metadata( $id ); } add_filter( 'upload_mimes', array( $this, 'add_webp_mime' ), 10, 2 ); // Sync the webp to GCS ud_get_stateless_media()->add_media( $metadata, $id, true, array( 'is_webp' => '.webp' ) ); remove_filter( 'upload_mimes', array( $this, 'add_webp_mime' ), 10 ); } /** * return subdir for that particular attached file - if it's media library then last 3 path items, otherwise substract the uploads path * Has trailing directory separator (/) * * @copied from shortpixel-image-optimiser\class\db\shortpixel-meta-facade.php * @param type $file * @return string */ public function returnSubDir( $file ) { $hp = wp_normalize_path( get_home_path() ); $file = wp_normalize_path( $file ); $sp__uploads = wp_upload_dir(); if( strstr( $file, $hp ) ) { $path = str_replace( $hp, "", $file ); } elseif( strstr( $file, dirname( WP_CONTENT_DIR ) ) ) { //in some situations the content dir is not inside the root, check this also (ex. single.shortpixel.com) $path = str_replace( trailingslashit( dirname( WP_CONTENT_DIR ) ), "", $file ); } elseif( ( strstr( realpath( $file ), realpath( $hp ) ) ) ) { $path = str_replace( realpath( $hp ), "", realpath( $file ) ); } elseif( strstr( $file, trailingslashit( dirname( dirname( $sp__uploads[ 'basedir' ] ) ) ) ) ) { $path = str_replace( trailingslashit( dirname( dirname( $sp__uploads[ 'basedir' ] ) ) ), "", $file ); } else { $path = ( substr( $file, 1 ) ); } $pathArr = explode( '/', $path ); unset( $pathArr[ count( $pathArr ) - 1 ] ); return implode( '/', $pathArr ) . '/'; } /** * Sync images after shortpixel restore them from backup. * * @param $attachmentID */ public function handleRestoreBackup( $attachmentID ) { $metadata = wp_get_attachment_metadata( $attachmentID ); $this->add_media( $metadata, $attachmentID ); } /** * Customized version of wpCloud\StatelessMedia\Utility::add_media() * to satisfied our need in restore backup * If a image isn't restored from backup then ignore it. * * @param $metadata * @param $attachment_id */ public static function add_media( $metadata, $attachment_id ) { $upload_dir = wp_upload_dir(); $client = ud_get_stateless_media()->get_client(); if( !is_wp_error( $client ) ) { $fullsizepath = wp_normalize_path( get_attached_file( $attachment_id ) ); // Make non-images uploadable. if( empty( $metadata[ 'file' ] ) && $attachment_id ) { $metadata = array( "file" => str_replace( trailingslashit( $upload_dir[ 'basedir' ] ), '', get_attached_file( $attachment_id ) ) ); } $file = wp_normalize_path( $metadata[ 'file' ] ); $image_host = ud_get_stateless_media()->get_gs_host(); $bucketLink = apply_filters( 'wp_stateless_bucket_link', $image_host ); $_cacheControl = \wpCloud\StatelessMedia\Utility::getCacheControl( $attachment_id, $metadata, null ); $_contentDisposition = \wpCloud\StatelessMedia\Utility::getContentDisposition( $attachment_id, $metadata, null ); $_metadata = array( "width" => isset( $metadata[ 'width' ] ) ? $metadata[ 'width' ] : null, "height" => isset( $metadata[ 'height' ] ) ? $metadata[ 'height' ] : null, 'object-id' => $attachment_id, 'source-id' => md5( $attachment_id . ud_get_stateless_media()->get( 'sm.bucket' ) ), 'file-hash' => md5( $metadata[ 'file' ] ) ); if( file_exists( $fullsizepath ) ) { $file = apply_filters( 'wp_stateless_file_name', $file ); /* Add default image */ $media = $client->add_media( $_mediaOptions = array_filter( array( 'force' => true, 'name' => $file, 'absolutePath' => wp_normalize_path( get_attached_file( $attachment_id ) ), 'cacheControl' => $_cacheControl, 'contentDisposition' => $_contentDisposition, 'mimeType' => get_post_mime_type( $attachment_id ), 'metadata' => $_metadata ) ) ); // ephemeral mode: we don't need the local version. if( ud_get_stateless_media()->get( 'sm.mode' ) === 'ephemeral' ) { unlink( $fullsizepath ); } } /* Now we go through all available image sizes and upload them to Google Storage */ if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) { $path = wp_normalize_path( dirname( get_attached_file( $attachment_id ) ) ); $mediaPath = apply_filters( 'wp_stateless_file_name', trim( dirname( $metadata[ 'file' ] ), '\/\\' ) ); foreach( (array) $metadata[ 'sizes' ] as $image_size => $data ) { $absolutePath = wp_normalize_path( $path . '/' . $data[ 'file' ] ); if( !file_exists( $absolutePath ) ) { continue; } /* Add 'image size' image */ $media = $client->add_media( array( 'force' => true, 'name' => $file_path = trim( $mediaPath . '/' . $data[ 'file' ], '/' ), 'absolutePath' => $absolutePath, 'cacheControl' => $_cacheControl, 'contentDisposition' => $_contentDisposition, 'mimeType' => $data[ 'mime-type' ], 'metadata' => array_merge( $_metadata, array( 'width' => $data[ 'width' ], 'height' => $data[ 'height' ], 'child-of' => $attachment_id, 'file-hash' => md5( $data[ 'file' ] ) ) ) ) ); /* Break if we have errors. */ if( !is_wp_error( $media ) ) { // ephemeral mode: we don't need the local version. if( ud_get_stateless_media()->get( 'sm.mode' ) === 'ephemeral' ) { unlink( $absolutePath ); } } } } } } // End add_media /** * modifying gs_name and absolutePath so that we can upload webp image using the same Utility::add_media function. * * @param $args * @return mixed */ public function wp_stateless_add_media_args( $args ) { if( !empty( $args[ 'is_webp' ] ) && $args[ 'is_webp' ] ) { if( \file_exists( $args[ 'absolutePath' ] . '.webp' ) ) { $args[ 'name' ] = $args[ 'name' ] . '.webp'; $args[ 'absolutePath' ] = $args[ 'absolutePath' ] . '.webp'; } else { $pathinfo = pathinfo( $args[ 'absolutePath' ] ); $absolutePath = trailingslashit( $pathinfo[ 'dirname' ] ) . $pathinfo[ 'filename' ] . '.webp'; if( file_exists( $absolutePath ) ) { $args[ 'name' ] = $args[ 'name' ] . '.webp'; $args[ 'absolutePath' ] = $absolutePath; } } $args[ 'mimeType' ] = 'image/webp'; } return $args; } /** * Bypass server url check and return base url for GCS image. * * @param $imageBase * @param $src * @return mixed */ public function shortpixel_webp_image_base( $imageBase, $src ) { $gs_link = \ud_get_stateless_media()->convert_to_gs_link( $src, true ); if( $gs_link ) { $imageBase = trailingslashit( dirname( $gs_link ) ); } return $imageBase; } /** * @param $URLs * @param $id * @return mixed */ public function shortpixel_image_urls( $URLs, $id ) { foreach( $URLs as $key => $url ) { $url_parts = wp_parse_url( $url ); if( $url_parts[ 'host' ] == 'storage.googleapis.com' ) { if( preg_match( "@(^/?.*?/)(.*)@", $url_parts[ 'path' ], $matches ) ) { $bucket = trim( $matches[ 1 ], '/' ); $url_parts[ 'path' ] = $matches[ 2 ]; $url_parts[ 'host' ] = $bucket . '.' . $url_parts[ 'host' ]; $URLs[ $key ] = Utility::join_url( $url_parts ); } } } return $URLs; } } } }