Files
WPS3Media/classes/as3cf-utils.php

1065 lines
25 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* Plugin Utilities
*
* @package amazon-s3-and-cloudfront
* @subpackage Classes/Utils
* @copyright Copyright (c) 2015, Delicious Brains
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
use DeliciousBrains\WP_Offload_Media\Items\Item;
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'AS3CF_Utils' ) ) {
/**
* AS3CF_Utils Class
*
* This class contains utility functions that need to be available
* across the Pro plugin codebase
*/
class AS3CF_Utils {
/**
* Get post ID.
*
* @param null|int|WP_Post $post Optional. Post ID or post object. Defaults to current post.
*
* @return int
*/
public static function get_post_id( $post = null ) {
return (int) get_post_field( 'ID', $post );
}
/**
* Trailing slash prefix string ensuring no leading slashes.
*
* @param string $string
*
* @return string
*/
public static function trailingslash_prefix( $string ) {
return static::unleadingslashit( trailingslashit( trim( $string ) ) );
}
/**
* Ensure string has a leading slash, like in absolute paths.
*
* @param string $string
*
* @return string
*/
public static function leadingslashit( $string ) {
return '/' . static::unleadingslashit( $string );
}
/**
* Ensure string has no leading slash, like in relative paths.
*
* @param string $string
*
* @return string
*/
public static function unleadingslashit( $string ) {
return ltrim( trim( $string ), '/\\' );
}
/**
* Ensure string has a leading and trailing slash, like in absolute directory paths.
*
* @param string $string
*
* @return string
*/
public static function leadingtrailingslashit( $string ) {
return static::leadingslashit( trailingslashit( trim( $string ) ) );
}
/**
* Remove scheme from URL.
*
* @param string $url
*
* @return string
*/
public static function remove_scheme( $url ) {
return preg_replace( '/^(?:http|https):/', '', $url );
}
/**
* Remove size from filename (image[-100x100].jpeg).
*
* @param string $url
* @param bool $remove_extension
*
* @return string
*/
public static function remove_size_from_filename( $url, $remove_extension = false ) {
$url = preg_replace( '/^(\S+)-[0-9]{1,4}x[0-9]{1,4}(\.[a-zA-Z0-9\.]{2,})?/', '$1$2', $url );
$url = apply_filters( 'as3cf_remove_size_from_filename', $url );
if ( $remove_extension ) {
$ext = pathinfo( $url, PATHINFO_EXTENSION );
$url = str_replace( ".$ext", '', $url );
}
return $url;
}
/**
* Is the given size recognized as the full sized image?
*
* @param string|null $size
*
* @return bool
*/
public static function is_full_size( $size ) {
if ( empty( $size ) || in_array( $size, array( 'full', Item::primary_object_key() ) ) ) {
return true;
}
return false;
}
/**
* Reduce the given URL down to the simplest version of itself.
*
* Useful for matching against the full version of the URL in a full-text search
* or saving as a key for dictionary type lookup.
*
* @param string $url
*
* @return string
*/
public static function reduce_url( $url ) {
$parts = static::parse_url( $url );
$host = isset( $parts['host'] ) ? $parts['host'] : '';
$port = isset( $parts['port'] ) ? ":{$parts['port']}" : '';
$path = isset( $parts['path'] ) ? $parts['path'] : '';
return '//' . $host . $port . $path;
}
/**
* Parses a URL into its components. Compatible with PHP < 5.4.7.
*
* @param string $url The URL to parse.
*
* @param int $component PHP_URL_ constant for URL component to return.
*
* @return mixed An array of the parsed components, mixed for a requested component, or false on error.
*/
public static function parse_url( $url, $component = -1 ) {
$url = trim( $url );
$no_scheme = 0 === strpos( $url, '//' );
if ( $no_scheme ) {
$url = 'http:' . $url;
}
$parts = parse_url( $url, $component );
if ( 0 < $component ) {
return $parts;
}
if ( $no_scheme && is_array( $parts ) ) {
unset( $parts['scheme'] );
}
return $parts;
}
/**
* Is the string a URL?
*
* @param mixed $string
*
* @return bool
*/
public static function is_url( $string ): bool {
if ( empty( $string ) || ! is_string( $string ) ) {
return false;
}
if ( preg_match( '@^(?:https?:)?//[a-zA-Z0-9\-]+@', $string ) ) {
return true;
}
return false;
}
/**
* Is the string a relative URL?
*
* @param string $string
*
* @return bool
*/
public static function is_relative_url( $string ) {
if ( empty( $string ) || ! is_string( $string ) ) {
return false;
}
$url = static::parse_url( $string );
return ( empty( $url['scheme'] ) && empty( $url['host'] ) );
}
/**
* Get file paths for all attachment versions.
*
* @param int $attachment_id
* @param bool $exists_locally
* @param array|bool $meta
* @param bool $include_backups
*
* @return array
*/
public static function get_attachment_file_paths( $attachment_id, $exists_locally = true, $meta = false, $include_backups = true ) {
$file_path = get_attached_file( $attachment_id, true );
$paths = array(
Item::primary_object_key() => $file_path,
);
if ( ! $meta ) {
$meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
}
if ( is_wp_error( $meta ) ) {
return $paths;
}
$file_name = wp_basename( $file_path );
// If file edited, current file name might be different.
if ( isset( $meta['file'] ) ) {
$paths['file'] = str_replace( $file_name, wp_basename( $meta['file'] ), $file_path );
// However, if this file path turns out to be exactly the same as the primary objet key, we don't need it.
if ( $paths[ Item::primary_object_key() ] === $paths['file'] ) {
unset( $paths['file'] );
}
}
// Thumb
if ( isset( $meta['thumb'] ) ) {
$paths['thumb'] = str_replace( $file_name, $meta['thumb'], $file_path );
}
// Original Image (when large image scaled down to threshold size and used as "full").
if ( isset( $meta['original_image'] ) ) {
$paths['original_image'] = str_replace( $file_name, $meta['original_image'], $file_path );
}
// Sizes
if ( isset( $meta['sizes'] ) ) {
foreach ( $meta['sizes'] as $size => $file ) {
if ( isset( $file['file'] ) ) {
$paths[ $size ] = str_replace( $file_name, $file['file'], $file_path );
}
}
}
$backups = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
// Backups
if ( $include_backups && is_array( $backups ) ) {
foreach ( $backups as $size => $file ) {
if ( isset( $file['file'] ) ) {
$paths[ $size ] = str_replace( $file_name, $file['file'], $file_path );
}
}
}
// Allow other processes to add files to be uploaded
$paths = apply_filters( 'as3cf_attachment_file_paths', $paths, $attachment_id, $meta );
// Remove paths that don't exist
if ( $exists_locally ) {
foreach ( $paths as $key => $path ) {
if ( ! file_exists( $path ) ) {
unset( $paths[ $key ] );
}
}
}
return $paths;
}
/**
* Get intermediate size from attachment filename.
* If multiple sizes exist with same filename, only the first size found will be returned.
*
* @param int $attachment_id
* @param string $filename
*
* @return string
*/
public static function get_intermediate_size_from_filename( $attachment_id, $filename ) {
$sizes = self::get_attachment_file_paths( $attachment_id, false );
foreach ( $sizes as $size => $file ) {
if ( wp_basename( $file ) === $filename ) {
return $size;
}
}
return '';
}
/**
* Strip edited image suffix and extension from path.
*
* @param string $path
* @param string $source_type
*
* @return string
*/
public static function strip_image_edit_suffix_and_extension( $path, $source_type = 'media-library' ) {
$parts = pathinfo( $path );
$filename = preg_replace( '/-e[0-9]{13}/', '', $parts['filename'] );
$result = str_replace( $parts['basename'], $filename, $path );
/**
* Allow source type specific cleanup
*
* @param string $path
* @param string $source_type
*
* @return string
*/
return apply_filters( 'as3cf_strip_image_edit_suffix_and_extension', $result, $source_type );
}
/**
* Create a site link for given URL.
*
* @param string $url URL for the link
* @param string $text Text for the link
* @param string $class Optional class to add to link
*
* @return string
*/
public static function dbrains_link( $url, $text, $class = '' ) {
$class = empty( $class ) ? '' : ' class="' . trim( $class ) . '"';
return sprintf( '<a href="%s"%s target="_blank">%s</a>', esc_url( $url ), $class, esc_html( $text ) );
}
/**
* Check whether two URLs share the same domain.
*
* @param string $url_a
* @param string $url_b
*
* @return bool
*/
public static function url_domains_match( $url_a, $url_b ) {
if ( ! static::is_url( $url_a ) || ! static::is_url( $url_b ) ) {
return false;
}
return static::parse_url( $url_a, PHP_URL_HOST ) === static::parse_url( $url_b, PHP_URL_HOST );
}
/**
* Get the current domain.
*
* @return string|false
*/
public static function current_domain() {
return parse_url( home_url(), PHP_URL_HOST );
}
/**
* Get the base domain of the current domain.
*
* @return string
*/
public static function current_base_domain() {
return static::base_domain( static::current_domain() );
}
/**
* Get the base domain of the supplied domain.
*
* @param string $domain
*
* @return string
*/
public static function base_domain( $domain ) {
if ( WP_Http::is_ip_address( $domain ) ) {
return $domain;
}
$parts = explode( '.', $domain );
// localhost etc.
if ( is_string( $parts ) ) {
return $domain;
}
if ( count( $parts ) < 3 ) {
return $domain;
}
// Just knock off the first segment.
unset( $parts[0] );
return implode( '.', $parts );
}
/**
* Very basic check of whether domain is real.
*
* @param string $domain
*
* @return bool
*
* Note: Very early version, may extend with further "local" domain checks if relevant.
*/
public static function is_public_domain( $domain ) {
// We're not going to test SEO etc. for ip addresses.
if ( WP_Http::is_ip_address( $domain ) ) {
return false;
}
$parts = explode( '.', $domain );
// localhost etc.
if ( is_string( $parts ) ) {
return false;
}
// TODO: Maybe check domain TLD.
return true;
}
/**
* Is given URL considered SEO friendly?
*
* @param string $url
*
* @return bool
*/
public static function seo_friendly_url( $url ) {
$domain = static::base_domain( parse_url( $url, PHP_URL_HOST ) );
$base_domain = static::current_base_domain();
// If either domain is not a public domain then skip checks.
if ( ! static::is_public_domain( $domain ) || ! static::is_public_domain( $base_domain ) ) {
return true;
}
if ( substr( $domain, -strlen( $base_domain ) ) === $base_domain ) {
return true;
}
return false;
}
/**
* A safe wrapper for deactivate_plugins()
*/
public static function deactivate_plugins() {
if ( ! function_exists( 'deactivate_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
call_user_func_array( 'deactivate_plugins', func_get_args() );
}
/**
* Get the first defined constant from the given list of constant names.
*
* @param array $constants
*
* @return string|false string constant name if defined, otherwise false if none are defined
*/
public static function get_first_defined_constant( $constants ) {
if ( ! empty( $constants ) ) {
foreach ( (array) $constants as $constant ) {
if ( defined( $constant ) ) {
return $constant;
}
}
}
return false;
}
/**
* Ensure returned keys are for correct attachment.
*
* @param int $source_id
* @param array $keys
* @param string $source_type
*
* @return array
*/
public static function validate_attachment_keys( $source_id, $keys, $source_type ) {
if ( Media_Library_Item::source_type() === $source_type ) {
$paths = self::get_attachment_file_paths( $source_id, false );
$filenames = array_map( 'wp_basename', $paths );
foreach ( $keys as $key => $value ) {
$filename = wp_basename( $value );
if ( ! in_array( $filename, $filenames ) ) {
unset( $keys[ $key ] );
}
}
}
return $keys;
}
/**
* Sanitize custom domain
*
* @param string $domain
*
* @return string
*/
public static function sanitize_custom_domain( $domain ) {
$domain = preg_replace( '@^[a-zA-Z]*:\/\/@', '', $domain );
$domain = preg_replace( '@[^a-zA-Z0-9\.\-]@', '', $domain );
return $domain;
}
/**
* Decode file name in potentially URL encoded URL path.
*
* @param string $file
*
* @return string
*/
public static function decode_filename_in_path( $file ) {
$url = parse_url( $file );
if ( ! isset( $url['path'] ) ) {
// Can't determine path, return original
return $file;
}
$file_name = wp_basename( $url['path'] );
if ( false === strpos( $file_name, '%' ) ) {
// File name not encoded, return original
return $file;
}
$decoded_file_name = rawurldecode( $file_name );
return str_replace( $file_name, $decoded_file_name, $file );
}
/**
* Returns indexed array of full size paths, e.g. orig and edited.
*
* @param array $paths Associative array of sizes and relative paths
*
* @return array
*
* @see get_attachment_file_paths
*/
public static function fullsize_paths( $paths ) {
if ( is_array( $paths ) && ! empty( $paths ) ) {
return array_values( array_unique( array_intersect_key( $paths, array_flip( array( Item::primary_object_key(), 'file', 'full-orig', 'original_image' ) ) ) ) );
} else {
return array();
}
}
/**
* Converts an array of upload file paths to all be relative paths.
* If any path is not absolute or does begin with current uploads base dir it will not be altered.
*
* @param array $paths Array of upload file paths, absolute or relative.
*
* @return array Input array with values switched to relative upload file paths.
*/
public static function make_upload_file_paths_relative( $paths ) {
if ( empty( $paths ) ) {
return array();
}
if ( ! is_array( $paths ) ) {
$paths = array( $paths );
}
$uploads = wp_upload_dir();
$basedir = trailingslashit( $uploads['basedir'] );
$offset = strlen( $basedir );
foreach ( $paths as $key => $path ) {
if ( 0 === strpos( $path, $basedir ) ) {
$paths[ $key ] = substr( $path, $offset );
}
}
return $paths;
}
/**
* Encode file names according to RFC 3986 when generating urls
* As per Amazon https://forums.aws.amazon.com/thread.jspa?threadID=55746#jive-message-244233
*
* @param string $file
*
* @return string Encoded filename
*/
public static function encode_filename_in_path( $file ) {
$url = parse_url( $file );
if ( ! isset( $url['path'] ) ) {
// Can't determine path, return original
return $file;
}
if ( isset( $url['query'] ) ) {
// Manually strip query string, as passing $url['path'] to basename results in corrupt <20> characters
$file_name = wp_basename( str_replace( '?' . $url['query'], '', $file ) );
} else {
$file_name = wp_basename( $file );
}
if ( false !== strpos( $file_name, '%' ) ) {
// File name already encoded, return original
return $file;
}
$encoded_file_name = rawurlencode( $file_name );
if ( $file_name === $encoded_file_name ) {
// File name doesn't need encoding, return original
return $file;
}
return str_replace( $file_name, $encoded_file_name, $file );
}
/**
* Get a file's real mime type
*
* @param string $file_path
*
* @return string
*/
public static function get_mime_type( $file_path ) {
$file_type = wp_check_filetype_and_ext( $file_path, wp_basename( $file_path ) );
return $file_type['type'];
}
/**
* Is this an AJAX process?
*
* @return bool
*/
public static function is_ajax() {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return true;
}
return false;
}
/**
* Is this a REST-API process?
*
* @return bool
*/
public static function is_rest_api() {
if (
( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
( function_exists( 'wp_is_json_request' ) && wp_is_json_request() )
) {
return true;
}
return false;
}
/**
* Recursive version of the standard array_diff_assoc function.
*
* @see https://www.php.net/manual/en/function.array-diff-assoc.php#111675
*
* @param array $array1
* @param array $array2
*
* @return array
*/
public static function array_diff_assoc_recursive( $array1, $array2 ) {
$difference = array();
foreach ( $array1 as $key => $value ) {
if ( is_array( $value ) ) {
if ( ! isset( $array2[ $key ] ) || ! is_array( $array2[ $key ] ) ) {
$difference[ $key ] = $value;
} else {
$new_diff = static::array_diff_assoc_recursive( $value, $array2[ $key ] );
if ( ! empty( $new_diff ) ) {
$difference[ $key ] = $new_diff;
}
}
} elseif ( ! array_key_exists( $key, $array2 ) || $array2[ $key ] !== $value ) {
$difference[ $key ] = $value;
}
}
return $difference;
}
/**
* Renders an HTML select element.
*
* @param array $args
*
* @return string
*/
public static function make_dropdown( array $args ): string {
if (
empty( $args['id'] ) ||
empty( $args['name'] ) ||
empty( $args['label'] ) ||
empty( $args['options'] ) ||
! is_array( $args['options'] )
) {
return '';
}
$selected = isset( $args['selected'] ) && ! empty( $args['selected'] ) ? $args['selected'] : false;
$html = sprintf(
'<label for="%1$s" class="screen-reader-text">%2$s</label><select name="%3$s" id="%1$s">',
$args['id'],
$args['label'],
$args['name']
);
foreach ( $args['options'] as $key => $option_value ) {
if ( is_string( $option_value ) ) {
$html .= sprintf(
'<option value="%s" %s>%s</option>',
$key,
$selected !== false && $key === $selected ? 'selected' : '',
$option_value
);
}
if ( is_array( $option_value ) ) {
$html .= sprintf(
'<optgroup label="%s">',
$key
);
foreach ( $option_value as $sub_key => $sub_option_value ) {
$html .= sprintf(
'<option value="%s" %s>%s</option>',
$sub_key,
$selected !== false && $sub_key === $selected ? 'selected' : '',
$sub_option_value
);
}
$html .= '</optgroup>';
}
}
$html .= '</select> ';
return $html;
}
/**
* If the given string is broken (optionally base64 encoded) serialized data,
* fix it,otherwise return untouched.
*
* phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
* phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
*
* @param mixed $input
*
* @return mixed
*/
public static function maybe_fix_serialized_string( $input ) {
if ( ! is_string( $input ) ) {
return $input;
}
$output = $input;
$base64 = false;
if ( self::is_base64( $output ) ) {
$base64 = true;
$output = base64_decode( $output, true );
}
$is_slashed = false;
if ( self::is_slashed_serialized( $output ) ) {
$is_slashed = true;
$output = wp_unslash( $output );
}
if ( ! self::is_broken_serialized( $output ) ) {
return $input;
}
$output = preg_replace_callback( '/s:(\d+):"(.*?)"\s?;/',
array( __CLASS__, 'fix_serialized_matches' ),
$output
);
if ( ! is_serialized( $output ) || self::is_broken_serialized( $output ) ) {
return $input;
}
// If the data was slashed before then re-slash it.
if ( $is_slashed ) {
$output = wp_slash( $output );
}
// If it was base64 encoded then re-encode.
if ( $base64 ) {
$output = base64_encode( $output );
}
return $output;
}
/**
* Is the given string a usable URL?
*
* We need URLs that include at least a domain and filename with extension
* for URL rewriting in either direction.
*
* @param mixed $url
*
* @return bool
*/
public static function usable_url( $url ): bool {
if ( ! static::is_url( $url ) ) {
return false;
}
$parts = static::parse_url( $url );
if (
empty( $parts['host'] ) ||
empty( $parts['path'] ) ||
! pathinfo( $parts['path'], PATHINFO_EXTENSION )
) {
return false;
}
return true;
}
/**
* Join array elements with a string separator, using a separate separator
* for the last element
*
* @param string $separator
* @param string $last_separator
* @param array $arr
*
* @return string
*/
public static function human_readable_join( string $separator, string $last_separator, array $arr ): string {
if ( count( $arr ) < 2 ) {
return join( $separator, $arr );
}
$last = array_pop( $arr );
return join( $separator, $arr ) . $last_separator . $last;
}
/**
* Get all the blog IDs for the multisite network used for table prefixes.
*
* @return false|array
*/
public static function get_blog_ids() {
if ( ! is_multisite() ) {
return false;
}
$args = array(
'limit' => false, // Deprecated
'number' => false, // WordPress 4.6+
'spam' => 0,
'deleted' => 0,
'archived' => 0,
);
$blogs = get_sites( $args );
$blog_ids = array();
foreach ( $blogs as $blog ) {
$blog = (array) $blog;
$blog_ids[] = (int) $blog['blog_id'];
}
return $blog_ids;
}
/**
* Get all the table prefixes for the blogs in the site. MS compatible.
*
* @param array $exclude_blog_ids blog ids to exclude
*
* @return array associative array with blog ID as key, prefix as value
*/
public static function get_all_blog_table_prefixes( array $exclude_blog_ids = array() ): array {
global $wpdb;
$prefix = $wpdb->prefix;
$table_prefixes = array();
if ( ! in_array( 1, $exclude_blog_ids ) ) {
$table_prefixes[1] = $prefix;
}
if ( is_multisite() ) {
$blog_ids = static::get_blog_ids();
foreach ( $blog_ids as $blog_id ) {
if ( in_array( $blog_id, $exclude_blog_ids ) ) {
continue;
}
$table_prefixes[ $blog_id ] = $wpdb->get_blog_prefix( $blog_id );
}
}
return $table_prefixes;
}
/**
* Fixes the broken string segments of a serialized data string.
*
* @param array $matches array of matched parts.
*
* @return string
*/
private static function fix_serialized_matches( array $matches ): string {
return 's:' . strlen( $matches[2] ) . ':"' . $matches[2] . '";';
}
/**
* Tests whether given serialized data is broken or not.
*
* @param string $data Serialized data string.
*
* @return bool
*/
private static function is_broken_serialized( string $data ): bool {
$broken = false;
if ( is_serialized( $data ) ) {
$value = self::maybe_unserialize( $data );
if ( false === $value && serialize( false ) !== $data ) {
$broken = true;
}
}
return $broken;
}
/**
* Determine if a string is a slashed serialized string.
*
* @param string $data
*
* @return bool
*/
private static function is_slashed_serialized( string $data ): bool {
if ( ! is_serialized( $data ) ) {
return false;
}
$pattern = '/s:\d+:\\\"/';
$start_quotes = preg_match_all( $pattern, $data, $matches );
if ( $start_quotes == 0 ) {
return false;
}
$pattern = '/\\\";(s|a|o|i|b|d|N|})./';
$end_quotes = preg_match_all( $pattern, $data, $matches );
if ( $end_quotes == 0 ) {
return false;
}
$pattern = '/\\\";}$/';
$end_quotes += preg_match_all( $pattern, $data, $matches );
if ( $start_quotes !== $end_quotes ) {
return false;
}
return true;
}
/**
* Is the data a base64 encoded serialized string, object or JSON string?
*
* @param string $data
*
* @return bool
*/
private static function is_base64( string $data ): bool {
if ( ! empty( $data ) && base64_encode( base64_decode( $data, true ) ) === $data ) {
$data = base64_decode( $data, true );
if ( is_serialized( $data ) || is_object( $data ) || self::is_json( $data ) ) {
return true;
}
}
return false;
}
/**
* Can the supplied string be treated as JSON?
*
* @param string $value
*
* @return bool
*/
public static function is_json( string $value ): bool {
$is_json = false;
if ( 0 < strlen( trim( $value ) ) && ! is_numeric( $value ) && null !== json_decode( $value ) ) {
$is_json = true;
}
return $is_json;
}
/**
* Maybe unserialize data, but not if an object.
*
* @param mixed $data
*
* @return mixed
*/
public static function maybe_unserialize( $data ) {
if ( is_serialized( $data ) ) {
return @unserialize( $data, array( 'allowed_classes' => false ) ); // @phpcs:ignore
}
return $data;
}
}
}