1065 lines
25 KiB
PHP
1065 lines
25 KiB
PHP
|
|
<?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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|