<?php
/**
 * Licensed under the GNU GPL license.
 * http://www.gnu.org/licenses/gpl.html
 */

if ( ! class_exists( 'ThemeUpdateChecker' ) ) :

    /**
     * A custom theme update checker.
     *
     * @autor Janis Elsts
     * @copyright 2012
     * @version 1.2
     * @access public
     */
    class ThemeUpdateChecker {
        public $theme                   = '';              //The theme associated with this update checker instance.
        public $metadataUrl             = '';        //The URL of the theme's metadata file.
        public $enableAutomaticChecking = true; //Enable/disable automatic update checks.

        protected $optionName          = '';      //Where to store update info.
        protected $automaticCheckDone  = false;
        protected static $filterPrefix = 'tuc_request_update_';

        /**
         * Class constructor.
         *
         * @param string $theme Theme slug, e.g. "twentyten".
         * @param string $metadataUrl The URL of the theme metadata file.
         * @param boolean $enableAutomaticChecking Enable/disable automatic update checking. If set to FALSE, you'll need to explicitly call checkForUpdates() to, err, check for updates.
         */
        public function __construct( $theme, $metadataUrl, $enableAutomaticChecking = true ) {
            $this->metadataUrl             = $metadataUrl;
            $this->enableAutomaticChecking = $enableAutomaticChecking;
            $this->theme                   = $theme;
            $this->optionName              = 'external_theme_updates-' . $this->theme;

            $this->installHooks();
        }

        /**
         * Install the hooks required to run periodic update checks and inject update info
         * into WP data structures.
         *
         * @return void
         */
        public function installHooks() {
            //Check for updates when WordPress does. We can detect when that happens by tracking
            //updates to the "update_themes" transient, which only happen in wp_update_themes().
            if ( $this->enableAutomaticChecking ) {
                add_filter( 'pre_set_site_transient_update_themes', [ $this, 'onTransientUpdate' ] );
            }

            //Insert our update info into the update list maintained by WP.
            add_filter( 'site_transient_update_themes', [ $this, 'injectUpdate' ] );

            //Delete our update info when WP deletes its own.
            //This usually happens when a theme is installed, removed or upgraded.
            add_action( 'delete_site_transient_update_themes', [ $this, 'deleteStoredData' ] );
        }

        /**
         * Retrieve update info from the configured metadata URL.
         *
         * Returns either an instance of ThemeUpdate, or NULL if there is
         * no newer version available or if there's an error.
         *
         * @uses wp_remote_get()
         *
         * @param array $queryArgs Additional query arguments to append to the request. Optional.
         * @return ThemeUpdate|null
         */
        public function requestUpdate( $queryArgs = [] ) {
            //Query args to append to the URL. Themes can add their own by using a filter callback (see addQueryArgFilter()).
            $queryArgs['installed_version'] = $this->getInstalledVersion();
            $queryArgs                      = apply_filters( self::$filterPrefix . 'query_args-' . $this->theme, $queryArgs );

            //Various options for the wp_remote_get() call. Themes can filter these, too.
            $options = [
                'timeout' => apply_filters('theme_update_checker_timeout', 10), // Default to 10 seconds, but allow filtering
            ];
            $options = apply_filters( self::$filterPrefix . 'options-' . $this->theme, $options );

            $url = $this->metadataUrl;
            if ( ! empty( $queryArgs ) ) {
                $url = add_query_arg( $queryArgs, $url );
            }

            //Send the request.
            $result = wp_remote_get( $url, $options );

            //Ensure result is not a WP_Error and is an array
            if ( is_wp_error( $result ) || ! is_array( $result ) ) {
                return null;
            }

            //Try to parse the response
            $themeUpdate = null;
            $code        = wp_remote_retrieve_response_code( $result );
            $body        = wp_remote_retrieve_body( $result );
            if ( ( $code == 200 ) && ! empty( $body ) ) {
                $themeUpdate = ThemeUpdate::fromJson( $body );
                //The update should be newer than the currently installed version.
                if ( ( $themeUpdate != null ) && version_compare( $themeUpdate->version, $this->getInstalledVersion(), '<=' ) ) {
                    $themeUpdate = null;
                }
            }

            $themeUpdate = apply_filters( self::$filterPrefix . 'result-' . $this->theme, $themeUpdate, $result );
            return $themeUpdate;
        }

        /**
         * Get the currently installed version of our theme.
         *
         * @return string Version number.
         */
        public function getInstalledVersion() {
            if ( function_exists( 'wp_get_theme' ) ) {
                $theme = wp_get_theme( $this->theme );
                return $theme->get( 'Version' );
            }

            /** @noinspection PhpDeprecationInspection wp_get_themes() used for compatibility with WP 3.3 and below. */
            foreach ( wp_get_themes() as $theme ) {
                if ( $theme['Stylesheet'] === $this->theme ) {
                    return $theme['Version'];
                }
            }
            return '';
        }

        /**
         * Check for theme updates.
         *
         * @return void
         */
        public function checkForUpdates() {
            $state = get_option( $this->optionName );
            if ( empty( $state ) ) {
                $state                 = new StdClass();
                $state->lastCheck      = 0;
                $state->checkedVersion = '';
                $state->update         = null;
            }

            $state->lastCheck      = time();
            $state->checkedVersion = $this->getInstalledVersion();
            update_option( $this->optionName, $state ); //Save before checking in case something goes wrong

            $state->update = $this->requestUpdate();
            update_option( $this->optionName, $state );
        }

        /**
         * Run the automatic update check, but no more than once per page load.
         * This is a callback for WP hooks. Do not call it directly.
         *
         * @param mixed $value
         * @return mixed
         */
        public function onTransientUpdate( $value ) {
            if ( ! $this->automaticCheckDone ) {
                $this->checkForUpdates();
                $this->automaticCheckDone = true;
            }
            return $value;
        }

        /**
         * Insert the latest update (if any) into the update list maintained by WP.
         *
         * @param StdClass $updates Update list.
         * @return array Modified update list.
         */
        public function injectUpdate( $updates ) {
            // Ensure $updates is an object and $updates->response is an array
            if ( ! is_object( $updates ) ) {
                $updates = new stdClass();
            }
            if ( ! isset( $updates->response ) || ! is_array( $updates->response ) ) {
                $updates->response = [];
            }

            $state = get_option( $this->optionName );

            // Is there an update to insert?
            if ( ! empty( $state ) && isset( $state->update ) && ! empty( $state->update ) && is_object( $state->update ) ) {
                $updates->response[ $this->theme ] = $state->update->toWpFormat();
            }

            return $updates;
        }

        /**
         * Delete any stored book-keeping data.
         *
         * @return void
         */
        public function deleteStoredData() {
            delete_option( $this->optionName );
        }

        /**
         * Register a callback for filtering query arguments.
         *
         * The callback function should take one argument - an associative array of query arguments.
         * It should return a modified array of query arguments.
         *
         * @param callable $callback
         * @return void
         */
        public function addQueryArgFilter( $callback ) {
            add_filter( self::$filterPrefix . 'query_args-' . $this->theme, $callback );
        }

        /**
         * Register a callback for filtering arguments passed to wp_remote_get().
         *
         * The callback function should take one argument - an associative array of arguments -
         * and return a modified array or arguments. See the WP documentation on wp_remote_get()
         * for details on what arguments are available and how they work.
         *
         * @param callable $callback
         * @return void
         */
        public function addHttpRequestArgFilter( $callback ) {
            add_filter( self::$filterPrefix . 'options-' . $this->theme, $callback );
        }

        /**
         * Register a callback for filtering the theme info retrieved from the external API.
         *
         * The callback function should take two arguments. If a theme update was retrieved
         * successfully, the first argument passed will be an instance of  ThemeUpdate. Otherwise,
         * it will be NULL. The second argument will be the corresponding return value of
         * wp_remote_get (see WP docs for details).
         *
         * The callback function should return a new or modified instance of ThemeUpdate or NULL.
         *
         * @param callable $callback
         * @return void
         */
        public function addResultFilter( $callback ) {
            add_filter( self::$filterPrefix . 'result-' . $this->theme, $callback, 10, 2 );
        }
    }

endif;

if ( ! class_exists( 'ThemeUpdate' ) ) :

    /**
     * A simple container class for holding information about an available update.
     *
     * @author Janis Elsts
     * @copyright 2012
     * @version 1.0
     * @access public
     */
    class ThemeUpdate {
        public $version;      //Version number.
        public $details_url;  //The URL where the user can learn more about this version.
        public $du; //The download URL for this version of the theme. Optional.

        /**
         * Create a new instance of ThemeUpdate from its JSON-encoded representation.
         *
         * @param string $json Valid JSON string representing a theme information object.
         * @return ThemeUpdate|null New instance of ThemeUpdate, or NULL on error.
         */
        public static function fromJson( $json ) {
            $apiResponse = json_decode( $json );
            if ( empty( $apiResponse ) || ! is_object( $apiResponse ) ) {
                return null;
            }

            //Very, very basic validation.
            $valid = isset( $apiResponse->version ) && ! empty( $apiResponse->version ) && isset( $apiResponse->details_url ) && ! empty( $apiResponse->details_url );
            if ( ! $valid ) {
                return null;
            }

            $update = new self();
            foreach ( get_object_vars( $apiResponse ) as $key => $value ) {
                $update->$key = $value;
            }

            return $update;
        }

        /**
         * Transform the update into the format expected by the WordPress core.
         *
         * @return array
         */
        public function toWpFormat() {
            $update = [
                'new_version' => $this->version,
                'url'         => $this->details_url,
            ];

            if ( ! empty( $this->du ) ) {
                $update['package'] = restly_decode_du( $this->du );
            }

            return $update;
        }
    }

endif;

$purchase_code_status = trim( get_option( 'restly_purchase_code_status' ) );
if ( $purchase_code_status == 'valid' ) {
    $update_checker = new ThemeUpdateChecker(
        'restly',
        'https://updatetheme.themepul.dev/restly/theme-info.json'
    );
}