🏨 Hotel Booking Enhancements: - Implemented Eagle Booking Advanced Pricing add-on - Added Booking.com-style rate management system - Created professional calendar interface for pricing - Integrated deals and discounts functionality 💰 Advanced Pricing Features: - Dynamic pricing models (per room, per person, per adult) - Base rates, adult rates, and child rates management - Length of stay discounts and early bird deals - Mobile rates and secret deals implementation - Seasonal promotions and flash sales 📅 Availability Management: - Real-time availability tracking - Stop sell and restriction controls - Closed to arrival/departure functionality - Minimum/maximum stay requirements - Automatic sold-out management 💳 Payment Integration: - Maintained Redsys payment gateway integration - Seamless integration with existing Eagle Booking - No modifications to core Eagle Booking plugin 🛠️ Technical Implementation: - Custom database tables for advanced pricing - WordPress hooks and filters integration - AJAX-powered admin interface - Data migration from existing Eagle Booking - Professional calendar view for revenue management 📊 Admin Interface: - Booking.com-style management dashboard - Visual rate and availability calendar - Bulk operations for date ranges - Statistics and analytics dashboard - Modal dialogs for quick editing 🔧 Code Quality: - WordPress coding standards compliance - Secure database operations with prepared statements - Proper input validation and sanitization - Error handling and logging - Responsive admin interface 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
459 lines
14 KiB
PHP
459 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Envato API class.
|
|
*
|
|
* @package Envato_Market
|
|
*/
|
|
|
|
if ( ! class_exists( 'Envato_Market_API' ) && class_exists( 'Envato_Market' ) ) :
|
|
|
|
/**
|
|
* Creates the Envato API connection.
|
|
*
|
|
* @class Envato_Market_API
|
|
* @version 1.0.0
|
|
* @since 1.0.0
|
|
*/
|
|
class Envato_Market_API {
|
|
|
|
/**
|
|
* The single class instance.
|
|
*
|
|
* @since 1.0.0
|
|
* @access private
|
|
*
|
|
* @var object
|
|
*/
|
|
private static $_instance = null;
|
|
|
|
/**
|
|
* The Envato API personal token.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
public $token;
|
|
|
|
/**
|
|
* Main Envato_Market_API Instance
|
|
*
|
|
* Ensures only one instance of this class exists in memory at any one time.
|
|
*
|
|
* @see Envato_Market_API()
|
|
* @uses Envato_Market_API::init_globals() Setup class globals.
|
|
* @uses Envato_Market_API::init_actions() Setup hooks and actions.
|
|
*
|
|
* @since 1.0.0
|
|
* @static
|
|
* @return object The one true Envato_Market_API.
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public static function instance() {
|
|
if ( is_null( self::$_instance ) ) {
|
|
self::$_instance = new self();
|
|
self::$_instance->init_globals();
|
|
}
|
|
return self::$_instance;
|
|
}
|
|
|
|
/**
|
|
* A dummy constructor to prevent this class from being loaded more than once.
|
|
*
|
|
* @see Envato_Market_API::instance()
|
|
*
|
|
* @since 1.0.0
|
|
* @access private
|
|
* @codeCoverageIgnore
|
|
*/
|
|
private function __construct() {
|
|
/* We do nothing here! */
|
|
}
|
|
|
|
/**
|
|
* You cannot clone this class.
|
|
*
|
|
* @since 1.0.0
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function __clone() {
|
|
_doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'envato-market' ), '1.0.0' );
|
|
}
|
|
|
|
/**
|
|
* You cannot unserialize instances of this class.
|
|
*
|
|
* @since 1.0.0
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function __wakeup() {
|
|
_doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'envato-market' ), '1.0.0' );
|
|
}
|
|
|
|
/**
|
|
* Setup the class globals.
|
|
*
|
|
* @since 1.0.0
|
|
* @access private
|
|
* @codeCoverageIgnore
|
|
*/
|
|
private function init_globals() {
|
|
// Envato API token.
|
|
$this->token = envato_market()->get_option( 'token' );
|
|
}
|
|
|
|
/**
|
|
* Query the Envato API.
|
|
*
|
|
* @uses wp_remote_get() To perform an HTTP request.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $url API request URL, including the request method, parameters, & file type.
|
|
* @param array $args The arguments passed to `wp_remote_get`.
|
|
* @return array|WP_Error The HTTP response.
|
|
*/
|
|
public function request( $url, $args = array() ) {
|
|
$defaults = array(
|
|
'sslverify' => !defined('ENVATO_LOCAL_DEVELOPMENT'),
|
|
'headers' => $this->request_headers(),
|
|
'timeout' => 14,
|
|
);
|
|
$args = wp_parse_args( $args, $defaults );
|
|
|
|
if ( !defined('ENVATO_LOCAL_DEVELOPMENT') ) {
|
|
$token = trim( str_replace( 'Bearer', '', $args['headers']['Authorization'] ) );
|
|
if ( empty( $token ) ) {
|
|
return new WP_Error( 'api_token_error', __( 'An API token is required.', 'envato-market' ) );
|
|
}
|
|
}
|
|
|
|
$debugging_information = [
|
|
'request_url' => $url,
|
|
];
|
|
|
|
// Make an API request.
|
|
$response = wp_remote_get( esc_url_raw( $url ), $args );
|
|
|
|
// Check the response code.
|
|
$response_code = wp_remote_retrieve_response_code( $response );
|
|
$response_message = wp_remote_retrieve_response_message( $response );
|
|
|
|
$debugging_information['response_code'] = $response_code;
|
|
$debugging_information['response_cf_ray'] = wp_remote_retrieve_header( $response, 'cf-ray' );
|
|
$debugging_information['response_server'] = wp_remote_retrieve_header( $response, 'server' );
|
|
|
|
if ( ! empty( $response->errors ) && isset( $response->errors['http_request_failed'] ) ) {
|
|
// API connectivity issue, inject notice into transient with more details.
|
|
$option = envato_market()->get_options();
|
|
if ( empty( $option['notices'] ) ) {
|
|
$option['notices'] = [];
|
|
}
|
|
$option['notices']['http_error'] = current( $response->errors['http_request_failed'] );
|
|
envato_market()->set_options( $option );
|
|
return new WP_Error( 'http_error', esc_html( current( $response->errors['http_request_failed'] ) ), $debugging_information );
|
|
}
|
|
|
|
if ( 200 !== $response_code && ! empty( $response_message ) ) {
|
|
return new WP_Error( $response_code, $response_message, $debugging_information );
|
|
} elseif ( 200 !== $response_code ) {
|
|
return new WP_Error( $response_code, __( 'An unknown API error occurred.', 'envato-market' ), $debugging_information );
|
|
} else {
|
|
$return = json_decode( wp_remote_retrieve_body( $response ), true );
|
|
if ( null === $return ) {
|
|
return new WP_Error( 'api_error', __( 'An unknown API error occurred.', 'envato-market' ), $debugging_information );
|
|
}
|
|
return $return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deferred item download URL.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $id The item ID.
|
|
* @return string.
|
|
*/
|
|
public function deferred_download( $id ) {
|
|
if ( empty( $id ) ) {
|
|
return '';
|
|
}
|
|
|
|
$args = array(
|
|
'deferred_download' => true,
|
|
'item_id' => $id,
|
|
);
|
|
return add_query_arg( $args, esc_url( envato_market()->get_page_url() ) );
|
|
}
|
|
|
|
/**
|
|
* Get the item download.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $id The item ID.
|
|
* @param array $args The arguments passed to `wp_remote_get`.
|
|
* @return bool|array The HTTP response.
|
|
*/
|
|
public function download( $id, $args = array() ) {
|
|
if ( empty( $id ) ) {
|
|
return false;
|
|
}
|
|
$domain = envato_market()->get_envato_api_domain();
|
|
$path = $this->api_path_for('download');
|
|
$url = $domain . $path . '?item_id=' . $id . '&shorten_url=true';
|
|
$response = $this->request( $url, $args );
|
|
|
|
// @todo Find out which errors could be returned & handle them in the UI.
|
|
if ( is_wp_error( $response ) || empty( $response ) || ! empty( $response['error'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ! empty( $response['wordpress_theme'] ) ) {
|
|
return $response['wordpress_theme'];
|
|
}
|
|
|
|
if ( ! empty( $response['wordpress_plugin'] ) ) {
|
|
return $response['wordpress_plugin'];
|
|
}
|
|
|
|
// Missing a WordPress theme and plugin, report an error.
|
|
$option = envato_market()->get_options();
|
|
if ( ! isset( $option['notices'] ) ) {
|
|
$option['notices'] = [];
|
|
}
|
|
$option['notices']['missing-package-zip'] = true;
|
|
envato_market()->set_options( $option );
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get an item by ID and type.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $id The item ID.
|
|
* @param array $args The arguments passed to `wp_remote_get`.
|
|
* @return array The HTTP response.
|
|
*/
|
|
public function item( $id, $args = array() ) {
|
|
$domain = envato_market()->get_envato_api_domain();
|
|
$path = $this->api_path_for('catalog-item');
|
|
$url = $domain . $path . '?id=' . $id;
|
|
$response = $this->request( $url, $args );
|
|
|
|
if ( is_wp_error( $response ) || empty( $response ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ! empty( $response['wordpress_theme_metadata'] ) ) {
|
|
return $this->normalize_theme( $response );
|
|
}
|
|
|
|
if ( ! empty( $response['wordpress_plugin_metadata'] ) ) {
|
|
return $this->normalize_plugin( $response );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the list of available themes.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param array $args The arguments passed to `wp_remote_get`.
|
|
* @return array The HTTP response.
|
|
*/
|
|
public function themes( $args = array() ) {
|
|
$themes = array();
|
|
|
|
$domain = envato_market()->get_envato_api_domain();
|
|
$path = $this->api_path_for('list-purchases');
|
|
$url = $domain . $path . '?filter_by=wordpress-themes';
|
|
$response = $this->request( $url, $args );
|
|
|
|
if ( is_wp_error( $response ) || empty( $response ) || empty( $response['results'] ) ) {
|
|
return $themes;
|
|
}
|
|
|
|
foreach ( $response['results'] as $theme ) {
|
|
$themes[] = $this->normalize_theme( $theme['item'] );
|
|
}
|
|
|
|
return $themes;
|
|
}
|
|
|
|
/**
|
|
* Normalize a theme.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param array $theme An array of API request values.
|
|
* @return array A normalized array of values.
|
|
*/
|
|
public function normalize_theme( $theme ) {
|
|
$normalized_theme = array(
|
|
'id' => $theme['id'],
|
|
'name' => ( ! empty( $theme['wordpress_theme_metadata']['theme_name'] ) ? $theme['wordpress_theme_metadata']['theme_name'] : '' ),
|
|
'author' => ( ! empty( $theme['wordpress_theme_metadata']['author_name'] ) ? $theme['wordpress_theme_metadata']['author_name'] : '' ),
|
|
'version' => ( ! empty( $theme['wordpress_theme_metadata']['version'] ) ? $theme['wordpress_theme_metadata']['version'] : '' ),
|
|
'description' => self::remove_non_unicode( strip_tags( $theme['wordpress_theme_metadata']['description'] ) ),
|
|
'url' => ( ! empty( $theme['url'] ) ? $theme['url'] : '' ),
|
|
'author_url' => ( ! empty( $theme['author_url'] ) ? $theme['author_url'] : '' ),
|
|
'thumbnail_url' => ( ! empty( $theme['thumbnail_url'] ) ? $theme['thumbnail_url'] : '' ),
|
|
'rating' => ( ! empty( $theme['rating'] ) ? $theme['rating'] : '' ),
|
|
'landscape_url' => '',
|
|
);
|
|
|
|
// No main thumbnail in API response, so we grab it from the preview array.
|
|
if ( empty( $normalized_theme['thumbnail_url'] ) && ! empty( $theme['previews'] ) && is_array( $theme['previews'] ) ) {
|
|
foreach ( $theme['previews'] as $possible_preview ) {
|
|
if ( ! empty( $possible_preview['landscape_url'] ) ) {
|
|
$normalized_theme['landscape_url'] = $possible_preview['landscape_url'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( empty( $normalized_theme['thumbnail_url'] ) && ! empty( $theme['previews'] ) && is_array( $theme['previews'] ) ) {
|
|
foreach ( $theme['previews'] as $possible_preview ) {
|
|
if ( ! empty( $possible_preview['icon_url'] ) ) {
|
|
$normalized_theme['thumbnail_url'] = $possible_preview['icon_url'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $normalized_theme;
|
|
}
|
|
|
|
/**
|
|
* Get the list of available plugins.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param array $args The arguments passed to `wp_remote_get`.
|
|
* @return array The HTTP response.
|
|
*/
|
|
public function plugins( $args = array() ) {
|
|
$plugins = array();
|
|
|
|
$domain = envato_market()->get_envato_api_domain();
|
|
$path = $this->api_path_for('list-purchases');
|
|
$url = $domain . $path . '?filter_by=wordpress-plugins';
|
|
$response = $this->request( $url, $args );
|
|
|
|
if ( is_wp_error( $response ) || empty( $response ) || empty( $response['results'] ) ) {
|
|
return $plugins;
|
|
}
|
|
|
|
foreach ( $response['results'] as $plugin ) {
|
|
$plugins[] = $this->normalize_plugin( $plugin['item'] );
|
|
}
|
|
|
|
return $plugins;
|
|
}
|
|
|
|
/**
|
|
* Normalize a plugin.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param array $plugin An array of API request values.
|
|
* @return array A normalized array of values.
|
|
*/
|
|
public function normalize_plugin( $plugin ) {
|
|
$requires = null;
|
|
$tested = null;
|
|
$versions = array();
|
|
|
|
// Set the required and tested WordPress version numbers.
|
|
foreach ( $plugin['attributes'] as $k => $v ) {
|
|
if ( ! empty( $v['name'] ) && 'compatible-software' === $v['name'] && ! empty( $v['value'] ) && is_array( $v['value'] ) ) {
|
|
foreach ( $v['value'] as $version ) {
|
|
$versions[] = str_replace( 'WordPress ', '', trim( $version ) );
|
|
}
|
|
if ( ! empty( $versions ) ) {
|
|
$requires = $versions[ count( $versions ) - 1 ];
|
|
$tested = $versions[0];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
$plugin_normalized = array(
|
|
'id' => $plugin['id'],
|
|
'name' => ( ! empty( $plugin['wordpress_plugin_metadata']['plugin_name'] ) ? $plugin['wordpress_plugin_metadata']['plugin_name'] : '' ),
|
|
'author' => ( ! empty( $plugin['wordpress_plugin_metadata']['author'] ) ? $plugin['wordpress_plugin_metadata']['author'] : '' ),
|
|
'version' => ( ! empty( $plugin['wordpress_plugin_metadata']['version'] ) ? $plugin['wordpress_plugin_metadata']['version'] : '' ),
|
|
'description' => self::remove_non_unicode( strip_tags( $plugin['wordpress_plugin_metadata']['description'] ) ),
|
|
'url' => ( ! empty( $plugin['url'] ) ? $plugin['url'] : '' ),
|
|
'author_url' => ( ! empty( $plugin['author_url'] ) ? $plugin['author_url'] : '' ),
|
|
'thumbnail_url' => ( ! empty( $plugin['thumbnail_url'] ) ? $plugin['thumbnail_url'] : '' ),
|
|
'landscape_url' => ( ! empty( $plugin['previews']['landscape_preview']['landscape_url'] ) ? $plugin['previews']['landscape_preview']['landscape_url'] : '' ),
|
|
'requires' => $requires,
|
|
'tested' => $tested,
|
|
'number_of_sales' => ( ! empty( $plugin['number_of_sales'] ) ? $plugin['number_of_sales'] : '' ),
|
|
'updated_at' => ( ! empty( $plugin['updated_at'] ) ? $plugin['updated_at'] : '' ),
|
|
'rating' => ( ! empty( $plugin['rating'] ) ? $plugin['rating'] : '' ),
|
|
);
|
|
|
|
// No main thumbnail in API response, so we grab it from the preview array.
|
|
if ( empty( $plugin_normalized['landscape_url'] ) && ! empty( $plugin['previews'] ) && is_array( $plugin['previews'] ) ) {
|
|
foreach ( $plugin['previews'] as $possible_preview ) {
|
|
if ( ! empty( $possible_preview['landscape_url'] ) ) {
|
|
$plugin_normalized['landscape_url'] = $possible_preview['landscape_url'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( empty( $plugin_normalized['thumbnail_url'] ) && ! empty( $plugin['previews'] ) && is_array( $plugin['previews'] ) ) {
|
|
foreach ( $plugin['previews'] as $possible_preview ) {
|
|
if ( ! empty( $possible_preview['icon_url'] ) ) {
|
|
$plugin_normalized['thumbnail_url'] = $possible_preview['icon_url'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $plugin_normalized;
|
|
}
|
|
|
|
public function api_path_for( $path ) {
|
|
if ( defined('ENVATO_LOCAL_DEVELOPMENT') ) {
|
|
$paths = MONOLITH_API_PATHS;
|
|
} else {
|
|
$paths = array(
|
|
'download' => '/v2/market/buyer/download',
|
|
'catalog-item' => '/v2/market/catalog/item',
|
|
'list-purchases' => '/v2/market/buyer/list-purchases',
|
|
'total-items' => '/v1/market/total-items.json'
|
|
);
|
|
}
|
|
|
|
return $paths[$path];
|
|
}
|
|
|
|
/**
|
|
* Remove all non unicode characters in a string
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $retval The string to fix.
|
|
* @return string
|
|
*/
|
|
static private function remove_non_unicode( $retval ) {
|
|
return preg_replace( '/[\x00-\x1F\x80-\xFF]/', '', $retval );
|
|
}
|
|
|
|
private function request_headers() {
|
|
$user_agent = array('User-Agent' => 'WordPress - Envato Market ' . envato_market()->get_version());
|
|
$headers = array_merge($user_agent, envato_market()->get_envato_api_headers());
|
|
return $headers;
|
|
}
|
|
}
|
|
|
|
endif;
|