laipower/wp-content/plugins/menu-icons/vendor/codeinwp/themeisle-sdk/src/Modules/Endpoint.php

359 lines
8.4 KiB
PHP

<?php
/**
* The class that exposes endpoints.
*
* @package ThemeIsleSDK
* @subpackage Rollback
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
// Exit if accessed directly.
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Loader;
use ThemeisleSDK\Product;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Expose endpoints for ThemeIsle SDK.
*/
class Endpoint extends Abstract_Module {
/**
* Endpoint slug.
*/
const SDK_ENDPOINT = 'themeisle-sdk';
/**
* Endpoint version.
*/
const SDK_ENDPOINT_VERSION = 1;
/**
* Hash file which contains the checksum.
*/
const HASH_FILE = 'themeisle-hash.json';
/*
* If true, the endpoint will expect a product slug and will return the value only for that.
*/
const PRODUCT_SPECIFIC = false;
/**
* Registers the endpoints.
*/
function rest_register() {
register_rest_route(
self::SDK_ENDPOINT . '/v' . self::SDK_ENDPOINT_VERSION,
'/checksum/' . ( self::PRODUCT_SPECIFIC ? '(?P<slug>.*)/' : '' ),
array(
'methods' => 'GET',
'callback' => array( $this, 'checksum' ),
)
);
}
/**
* The checksum endpoint.
*
* @param \WP_REST_Request $data the request.
*
* @return \WP_REST_Response Response or the error
*/
function checksum( \WP_REST_Request $data ) {
$products = Loader::get_products();
if ( self::PRODUCT_SPECIFIC ) {
$params = $this->validate_params( $data, array( 'slug' ) );
foreach ( $products as $product ) {
if ( $params['slug'] === $product->get_slug() ) {
$products = array( $product );
break;
}
}
}
$response = array();
$custom_css = $this->has_custom_css();
if ( is_bool( $custom_css ) ) {
$response['custom_css'] = $custom_css;
}
$response['child_theme'] = $this->get_theme_properties();
foreach ( $products as $product ) {
$files = array();
switch ( $product->get_type() ) {
case 'plugin':
$files = array();
break;
case 'theme':
$files = array( 'style.css', 'functions.php' );
break;
}
$error = '';
// if any element in the $files array contains a '/', this would imply recursion is required.
$diff = $this->generate_diff(
$product,
$files,
array_reduce(
$files,
array(
$this,
'is_recursion_required',
),
false
)
);
if ( is_wp_error( $diff ) ) {
/**
* Error returner by the diff checker method.
*
* @var \WP_Error $diff Error returned.
*/
$error = $diff->get_error_message();
$diff = array();
}
$response['products'][] = array(
'slug' => $product->get_slug(),
'version' => $product->get_version(),
'diffs' => $diff,
'error' => $error,
);
}
return new \WP_REST_Response( array( 'checksum' => $response ) );
}
/**
* Validates the parameters to the API
*
* @param \WP_REST_Request $data the request.
* @param array $params the parameters to validate.
*
* @return array of parameter name=>value
*/
private function validate_params( \WP_REST_Request $data, $params ) {
$collect = array();
foreach ( $params as $param ) {
$value = sanitize_text_field( $data[ $param ] );
if ( empty( $value ) ) {
return rest_ensure_response(
new \WP_Error(
'themeisle_' . $param . '_invalid',
sprintf( 'Invalid %', $param ),
array(
'status' => 403,
)
)
);
} else {
$collect[ $param ] = $value;
}
}
return $collect;
}
/**
* Check if custom css has been added to the theme.
*
* @return bool Whether custom css has been added to the theme.
*/
private function has_custom_css() {
$query = new \WP_Query(
array(
'post_type' => 'custom_css',
'post_status' => 'publish',
'numberposts' => 1,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
)
);
if ( $query->have_posts() ) {
$query->the_post();
$content = get_the_content();
// if the content contains a colon, a CSS rule has been added.
return strpos( $content, ':' ) === false ? false : true;
}
return false;
}
/**
* Get the current theme properties.
*
* @return mixed Properties of the current theme.
*/
function get_theme_properties() {
if ( ! is_child_theme() ) {
return false;
}
$properties = array();
$theme = wp_get_theme();
// @codingStandardsIgnoreStart
$properties['name'] = $theme->Name;
// @codingStandardsIgnoreEnd
// get the files in the child theme.
require_once( ABSPATH . 'wp-admin/includes/file.php' );
WP_Filesystem();
global $wp_filesystem;
$path = str_replace( ABSPATH, $wp_filesystem->abspath(), get_stylesheet_directory() );
$list = $wp_filesystem->dirlist( $path, false, false );
if ( $list ) {
$list = array_keys( self::flatten_dirlist( $list ) );
$properties['files'] = $list;
}
return $properties;
}
/**
* Flatten the results of WP_Filesystem::dirlist() for iterating over.
*
* @access private
*
* @param array $nested_files Array of files as returned by WP_Filesystem::dirlist().
* @param string $path Relative path to prepend to child nodes. Optional.
*
* @return array $files A flattened array of the $nested_files specified.
*/
private static function flatten_dirlist( $nested_files, $path = '' ) {
$files = array();
foreach ( $nested_files as $name => $details ) {
$files[ $path . $name ] = $details;
// Append children recursively.
if ( ! empty( $details['files'] ) ) {
$children = self::flatten_dirlist( $details['files'], $path . $name . '/' );
// Merge keeping possible numeric keys, which array_merge() will reindex from 0..n.
$files = $files + $children;
}
}
return $files;
}
/**
* Generate the diff of the files.
*
* @param Product $product Themeisle Product.
* @param array $files Array of files.
* @param bool $recurse Whether to recurse or not.
*
* @return mixed Diff data.
*/
private function generate_diff( $product, $files, $recurse = false ) {
require_once( ABSPATH . 'wp-admin/includes/file.php' );
WP_Filesystem();
global $wp_filesystem;
$diff = array();
$path = str_replace( ABSPATH, $wp_filesystem->abspath(), plugin_dir_path( $product->get_basefile() ) );
$list = $wp_filesystem->dirlist( $path, false, $recurse );
// nothing found.
if ( ! $list ) {
return $diff;
}
$list = array_keys( self::flatten_dirlist( $list ) );
// now let's get the valid files that actually exist.
if ( empty( $files ) ) {
$files = $list;
} else {
$files = array_intersect( $files, $list );
}
// fetch the calculated hashes.
if ( ! $wp_filesystem->is_readable( $path . '/' . self::HASH_FILE ) ) {
return new \WP_Error( 'themeisle_sdk_hash_not_found', sprintf( '%s not found', self::HASH_FILE ) );
}
$hashes = json_decode( $wp_filesystem->get_contents( $path . '/' . self::HASH_FILE ), true );
ksort( $hashes );
$diff = array();
foreach ( $files as $file ) {
// file does not exist in the hashes.
if ( ! array_key_exists( $file, $hashes ) ) {
continue;
}
$new = md5( $wp_filesystem->get_contents( $path . $file ) );
$old = $hashes[ $file ];
// same hash, bail.
if ( $new === $old ) {
continue;
}
$diff[] = $file;
}
return $diff;
}
/**
* Check if recursion needs to be enabled on the WP_Filesystem by reducing the array of files to a boolean.
*
* @param string $carry Value of the previous iteration.
* @param string $item Value of the current iteration.
*
* @return bool Whether to recurse or not.
*/
function is_recursion_required( $carry, $item ) {
if ( ! $carry ) {
return ( strpos( $item, '/' ) !== false );
}
return $carry;
}
/**
* Load module for this product.
*
* @param Product $product Product to check.
*
* @return bool Should we load this?
*/
public function can_load( $product ) {
return true;
}
/**
* Load module logic.
*
* @param Product $product Product to load.
*/
public function load( $product ) {
$this->setup_endpoints();
return $this;
}
/**
* Setup endpoints.
*/
private function setup_endpoints() {
global $wp_version;
if ( version_compare( $wp_version, '4.4', '<' ) ) {
// no REST support.
return;
}
$this->setup_rest();
}
/**
* Setup REST endpoints.
*/
private function setup_rest() {
add_action( 'rest_api_init', array( $this, 'rest_register' ) );
}
}