jeremyclark13 / automatic-theme-plugin-update

Self hosted plugin and theme update scripts
http://clark-technet.com/2010/12/wordpress-self-hosted-plugin-update-api
548 stars 147 forks source link

plugin update failed #46

Closed ve3 closed 7 years ago

ve3 commented 7 years ago

I tried to install 2 plugins (self hosted) and have available for update of those 2 plugins.

On installed plugins page. It show up there are update for 2 plugins, I clicked on update link and ajax update is working for first plugin and update completed. Now i try to click on update link on second plugin and it said plugin update failed.

From my investigation it was called Plugin_Upgrader->bulk_upgrade() (wp-admin/includes/class-plugin-upgrader.php) from wp_ajax_update_plugin (wp-admin/includes/ajax-actions.php) function and stuck at if ( !isset( $current->response[ $plugin ] ) ) for second update.

Here is my plugin code:

<?php
/**
 * Plugin Name: V Test plugin 1
 * Plugin URI: http://rundiz.com
 * Description: A very simple 1 file plugin for testing.
 * Version: 0.0.7
 * Author: Vee Winch
 * Author URI: http://rundiz.com
 * License: MIT
 * License URI: https://opensource.org/licenses/MIT
 * Text Domain: testplug1
 * Domain Path: 
 */

namespace TestPlug1;

if (!defined('RDWPPS_URL')) {
    define('RDWPPS_URL', 'http://localhost/wordpress/single-site/rdwpps');
}

class TestPlugin
{

    public $check_self_hosted_log = __DIR__.'/debug/check-self-hosted.txt';
    public $http_request_args_log = __DIR__.'/debug/http-request-args.txt';
    public $upgrade_complete_log = __DIR__.'/debug/upgrade-process-complete.txt';

    public function __construct()
    {
        if (!is_dir(__DIR__.'/debug')) {
            mkdir(__DIR__.'/debug');
        }
    }// __construct

    /**
     * Check for self hosted plugin/theme server.
     * 
     * @param object $checked_data Checked data object.
     * @return object Return checked data object.
     */
    public function checkSelfHostedRepository($checked_data)
    {
        //$this->getOptions();
        //global $plugin_template_optname;

        //if (is_array($plugin_template_optname) && array_key_exists('activation_check_update', $plugin_template_optname) && $plugin_template_optname['activation_check_update'] != '1') {
        //    return $checked_data;
        //}

        global $wp_version;
        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        $plugin_sysname = plugin_basename(__FILE__);// pluginname/pluginname.php
        $this->debugLog($this->check_self_hosted_log, 'plugin_slug:plugin_sysname'."\n".$plugin_slug . ' : ' . $plugin_sysname."\n\n");

        if (empty($checked_data->checked)) {
            $this->debugLog($this->check_self_hosted_log, 'checked data is empty'."\n".print_r($checked_data, true)."\n\n");
            return $checked_data;
        } else {
            $this->debugLog($this->check_self_hosted_log, 'checked data is not empty'."\n".print_r($checked_data, true)."\n\n");
        }

        $args = array(
            'slug' => $plugin_slug,
            'version' => $checked_data->checked[$plugin_sysname],
        );
        $request_string = array(
            'body' => array(
                'action' => 'basic_check',
                'request' => serialize($args),
                'buyer_id' => 'test_buyer_id',
                'serial_number' => 'test_serial_number',
                'website' => get_bloginfo('url'),
            ),
            'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo('url')
        );

        // Start checking for an update
        $raw_response = wp_remote_post(RDWPPS_URL, $request_string);
        $this->debugLog($this->check_self_hosted_log, 'raw response after remote post to ' . RDWPPS_URL."\n".print_r($raw_response, true)."\n\n");

        if (!is_wp_error($raw_response) && isset($raw_response['response']['code']) && isset($raw_response['body']) && ($raw_response['response']['code'] == 200)) {
            $response = @unserialize($raw_response['body']);
            $this->debugLog($this->check_self_hosted_log, 'response body: '."\n".print_r($response, true)."\n\n");
        }

        if (isset($response) && is_object($response) && !empty($response)) {
            // Feed the update data into WP updater
            $checked_data->response[$plugin_sysname] = $response;
        }

        $this->debugLog($this->check_self_hosted_log, 'final checked data: '."\n".print_r($checked_data, true)."\n\n");
        return $checked_data;
    }// checkSelfHostedRepository

    /**
     * Simple debug log.
     * 
     * @param string $file
     * @param string $message
     */
    public function debugLog($file, $message)
    {
        if (!is_dir(__DIR__.'/debug')) {
            mkdir(__DIR__.'/debug');
        }

        file_put_contents($file, $message, FILE_APPEND);
    }// debugLog

    /**
     * Remove this plugin slug from request to WordPress.org repository.<br>
     * This method will not work if plugin is not activated.
     *
     * @param array  $request An array of HTTP request arguments.
     * @param string $url The request URL.
     * @return array Return filtered data.
     */
    public function httpRequestArgs($request, $url)
    {
        if (!isset($request['body']['plugins'])) {
            $this->debugLog($this->http_request_args_log, '$request[\'body\'][\'plugins\'] is not set'."\n".print_r($request, true)."\n\n");
            return $request;
        } else {
            $this->debugLog($this->http_request_args_log, '$request[\'body\'][\'plugins\'] is set'."\n".print_r($request, true)."\n\n");
        }

        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        $plugin_sysname = plugin_basename(__FILE__);// pluginname/pluginname.php
        $this->debugLog($this->http_request_args_log, 'plugin_slug:plugin_sysname'."\n".$plugin_slug . ' : ' . $plugin_sysname."\n\n");

        // Plugin update request.
        $this->debugLog($this->http_request_args_log, '$url'."\n".$url."\n\n");
        if (false !== strpos($url, '//api.wordpress.org/plugins/update-check')) {
            //Excluded plugin slugs that should never ping the WordPress API.
            $data = json_decode($request['body']['plugins']);
            $this->debugLog($this->http_request_args_log, '$data value:'."\n".print_r($data, true)."\n\n");
            // Remove the excluded plugins.
            unset($data->plugins->$plugin_sysname);
            if (isset($data->active) && is_array($data->active)) {
                unset($data->active[array_search($plugin_sysname, $data->active)]);
                $data->active = array_values($data->active);
            }
            $this->debugLog($this->http_request_args_log, '$data value after modified:'."\n".print_r($data, true)."\n\n");
            // Encode back into JSON and update the response.
            $request['body']['plugins'] = wp_json_encode($data);
        }
        return $request;
    }// httpRequestArgs

    /**
     * Send plugin information to display in to modal dialog of client website when there is an update for this plugin.
     * 
     * @param mixed $def
     * @param string $action
     * @param object $args
     * @return mixed
     */
    public function pluginInformation($def, $action, $args)
    {
        //$this->getOptions();
        //global $plugin_template_optname;

        global $wp_version;
        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        $plugin_sysname = plugin_basename(__FILE__);// pluginname/pluginname.php

        if (isset($args->slug) && ($args->slug != $plugin_slug)) {
            return $def;
        }

        // Get the current version
        $plugin_info = get_site_transient('update_plugins');
        if (!isset($plugin_info->checked) || !is_array($plugin_info->checked)) {
            return $def;
        }
        $current_version = $plugin_info->checked[$plugin_sysname];
        $args->version = $current_version;

        $request_string = array(
            'body' => array(
                'action' => $action,
                'request' => serialize($args),
                'buyer_id' => 'test_buyer_id',
                'serial_number' => 'test_serial_number',
                'website' => get_bloginfo('url'),
            ),
            'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo('url')
        );

        unset($current_version, $plugin_info, $plugin_slug, $plugin_sysname);
        $request = wp_remote_post(RDWPPS_URL, $request_string);

        if (is_wp_error($request)) {
            $res = new \WP_Error('plugins_api_failed', __('An Unexpected HTTP Error occurred during the API request.</p> <p><a href="?" onclick="document.location.reload(); return false;">Try again</a>'), $request->get_error_message());
        } elseif (isset($action) && $action == 'plugin_information' && isset($request['body'])) {
            $res = @unserialize($request['body']);

            if ($res === false) {
                $res = new \WP_Error('plugins_api_failed', __('An unknown error occurred'), $request['body']);
            }
        } else {
            return $def;
        }
        return $res;
    }// pluginInformation

    /**
     * register all plugin actions hooks and add filters or actions.
     */
    public function registerHooks()
    {
        // add filter to row meta. (in plugin page below description)
        add_filter('plugin_row_meta', [&$this, 'rowMeta'], 10, 2);
        // add filter to remove this plugin slug from check update with WordPress repository server. remove this filter if this plugin is on WordPress.
        add_filter('http_request_args', [&$this, 'httpRequestArgs'], 5, 2);
        // add filter to check update from self hosted server.
        add_filter('pre_set_site_transient_update_plugins', [&$this, 'checkSelfHostedRepository'], 10, 1);
        // add filter to display plugin information when there is an update for this plugin. this will be display as modal dialog just like other WordPress plugins.
        add_filter('plugins_api', [&$this, 'pluginInformation'], 10, 3);
        // add filter to update/upgrade plugin.
        //add_filter('upgrader_package_options', [&$this, 'upgraderOptions'], 10, 1);
        // add action after plugin upgrade complete.
        add_action('upgrader_process_complete', [&$this, 'upgradeProcessComplete'], 10, 2);
    }// registerHooks

    /**
     * add links to row meta that is in plugin page under plugin description.
     * 
     * @staticvar string $plugin the plugin file name.
     * @param array $links current meta links
     * @param string $file the plugin file name for checking.
     * @return array return modified links.
     */
    public function rowMeta($links, $file)
    {
        static $plugin;// pluginname/pluginname.php

        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        if (!isset($plugin) || $plugin == null) {
            $plugin = plugin_basename(__FILE__);// pluginname/pluginname.php
        }

        if ($plugin === $file) {
            $new_link[] = '<a class="thickbox open-plugin-details-modal" data-title="one file plugin" aria-label="More information about one file plugin" href="' . admin_url('plugin-install.php?tab=plugin-information&plugin=' . $plugin_slug . '&TB_iframe=true') . '">' . __('View details') . '</a>';
            $links = array_merge($links, $new_link);
            unset($new_link);
        }

        return $links;
    }// rowMeta

    /**
     * Hook after upgrade complete.
     * 
     * @param object $UpgraderObject
     * @param array $options
     */
    public function upgradeProcessComplete($UpgraderObject, $options = [])
    {
        $this->debugLog($this->upgrade_complete_log, 'upgrader object: '.print_r($UpgraderObject, true)."\n\n");
        $this->debugLog($this->upgrade_complete_log, 'upgrade complete options:'."\n".print_r($options, true)."\n\n");
    }// upgradeProcessComplete

    /**
     * Upgrader options
     * 
     * @param array $options
     */
    public function upgraderOptions($options = [])
    {
        if (is_array($options)) {
            if (!isset($options['is_multi'])) {
                $options['is_multi'] = false;
            } else {
                $options['is_multi'] = false;
            }
        }

        return $options;
    }// upgraderOptions

}

// start running the plugin. -----------------------------------------------------------------
$TestPlugin = new \TestPlug1\TestPlugin();
$TestPlugin->registerHooks();
unset($TestPlugin);

The another plugin is the same, just change the number from plug1 to plug2.

How to make ajax update plugin works for 2 or more self hosted plugins?

ve3 commented 7 years ago

Fixed. Changed some thing in checkSelfHostedRepository() method.

<?php
/**
 * Plugin Name: V Test plugin 1
 * Plugin URI: http://rundiz.com
 * Description: A very simple 1 file plugin for testing.
 * Version: 0.0.11
 * Author: Vee Winch
 * Author URI: http://rundiz.com
 * License: MIT
 * License URI: https://opensource.org/licenses/MIT
 * Text Domain: testplug1
 * Domain Path: 
 */

namespace TestPlug1;

if (!defined('RDWPPS_URL')) {
    define('RDWPPS_URL', 'http://localhost/wordpress/single-site/rdwpps');
}

class TestPlugin
{

    public $check_self_hosted_log = __DIR__.'/debug/check-self-hosted.txt';
    public $http_request_args_log = __DIR__.'/debug/http-request-args.txt';
    public $upgrade_complete_log = __DIR__.'/debug/upgrade-process-complete.txt';

    public function __construct()
    {
        if (!is_dir(__DIR__.'/debug')) {
            mkdir(__DIR__.'/debug');
        }
    }// __construct

    /**
     * Check for self hosted plugin/theme server.
     * 
     * @param object $checked_data Checked data object.
     * @return object Return checked data object.
     */
    public function checkSelfHostedRepository($checked_data)
    {
        //$this->getOptions();
        //global $plugin_template_optname;

        //if (is_array($plugin_template_optname) && array_key_exists('activation_check_update', $plugin_template_optname) && $plugin_template_optname['activation_check_update'] != '1') {
        //    return $checked_data;
        //}

        global $wp_version;
        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        $plugin_sysname = plugin_basename(__FILE__);// pluginname/pluginname.php
        $this->debugLog($this->check_self_hosted_log, 'plugin_slug:plugin_sysname'."\n".$plugin_slug . ' : ' . $plugin_sysname);

        if (empty($checked_data->response)) {
            $this->debugLog($this->check_self_hosted_log, 'response data is empty'."\n".print_r($checked_data, true));
        } else {
            $this->debugLog($this->check_self_hosted_log, 'response data is not empty'."\n".print_r($checked_data, true));
            if (isset($checked_data->response[$plugin_sysname])) {
                $this->debugLog($this->check_self_hosted_log, 'response data of this plugin is already exists');
                return $checked_data;
            }
        }

        $plugin_version = '0';
        if (defined('WPMU_PLUGIN_DIR') && is_dir(WPMU_PLUGIN_DIR)) {
            $plugin_data = get_plugin_data(WPMU_PLUGIN_DIR.'/'.$plugin_sysname);
        } elseif (defined('WP_PLUGIN_DIR') && is_dir(WP_PLUGIN_DIR)) {
            $plugin_data = get_plugin_data(WP_PLUGIN_DIR.'/'.$plugin_sysname);
        }
        if (isset($plugin_data) && is_array($plugin_data) && array_key_exists('Version', $plugin_data)) {
            $plugin_version = $plugin_data['Version'];
            unset($plugin_data);
        }

        $args = array(
            'slug' => $plugin_slug,
            'version' => $plugin_version,
            'plugin_sysname' => $plugin_sysname,
        );
        $request_string = array(
            'body' => array(
                'action' => 'basic_check',
                'request' => serialize($args),
                'buyer_id' => 'test_buyer_id',
                'serial_number' => 'test_serial_number',
                'website' => get_bloginfo('url'),
            ),
            'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo('url')
        );

        // Start checking for an update
        $raw_response = wp_remote_post(RDWPPS_URL, $request_string);
        $this->debugLog($this->check_self_hosted_log, 'raw response after remote post to ' . RDWPPS_URL."\n".print_r($raw_response, true));

        if (!is_wp_error($raw_response) && isset($raw_response['response']['code']) && isset($raw_response['body']) && ($raw_response['response']['code'] == 200)) {
            $response = @unserialize($raw_response['body']);
            $this->debugLog($this->check_self_hosted_log, 'response body: '."\n".print_r($response, true));
        }

        if (isset($response) && is_object($response) && !empty($response)) {
            // Feed the update data into WP updater
            $checked_data->response[$plugin_sysname] = $response;
        }

        $this->debugLog($this->check_self_hosted_log, 'final checked data: '."\n".print_r($checked_data, true));
        return $checked_data;
    }// checkSelfHostedRepository

    /**
     * Simple debug log.
     * 
     * @param string $file
     * @param string $message
     */
    public function debugLog($file, $message)
    {
        if (!is_dir(__DIR__.'/debug')) {
            mkdir(__DIR__.'/debug');
        }

        file_put_contents($file, $message . "\r\n\r\n\r\n", FILE_APPEND);
    }// debugLog

    /**
     * Remove this plugin slug from request to WordPress.org repository.<br>
     * This method will not work if plugin is not activated.
     *
     * @param array  $request An array of HTTP request arguments.
     * @param string $url The request URL.
     * @return array Return filtered data.
     */
    public function httpRequestArgs($request, $url)
    {
        if (!isset($request['body']['plugins'])) {
            $this->debugLog($this->http_request_args_log, '$request[\'body\'][\'plugins\'] is not set'."\n".print_r($request, true));
            return $request;
        } else {
            $this->debugLog($this->http_request_args_log, '$request[\'body\'][\'plugins\'] is set'."\n".print_r($request, true));
        }

        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        $plugin_sysname = plugin_basename(__FILE__);// pluginname/pluginname.php
        $this->debugLog($this->http_request_args_log, 'plugin_slug:plugin_sysname'."\n".$plugin_slug . ' : ' . $plugin_sysname);

        // Plugin update request.
        $this->debugLog($this->http_request_args_log, '$url'."\n".$url);
        if (false !== strpos($url, '//api.wordpress.org/plugins/update-check')) {
            //Excluded plugin slugs that should never ping the WordPress API.
            $data = json_decode($request['body']['plugins']);
            $this->debugLog($this->http_request_args_log, '$data value:'."\n".print_r($data, true));
            // Remove the excluded plugins.
            unset($data->plugins->$plugin_sysname);
            if (isset($data->active) && is_array($data->active)) {
                unset($data->active[array_search($plugin_sysname, $data->active)]);
                $data->active = array_values($data->active);
            }
            $this->debugLog($this->http_request_args_log, '$data value after modified:'."\n".print_r($data, true));
            // Encode back into JSON and update the response.
            $request['body']['plugins'] = wp_json_encode($data);
        }
        return $request;
    }// httpRequestArgs

    /**
     * Send plugin information to display in to modal dialog of client website when there is an update for this plugin.
     * 
     * @param mixed $def
     * @param string $action
     * @param object $args
     * @return mixed
     */
    public function pluginInformation($def, $action, $args)
    {
        //$this->getOptions();
        //global $plugin_template_optname;

        global $wp_version;
        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        $plugin_sysname = plugin_basename(__FILE__);// pluginname/pluginname.php

        if (isset($args->slug) && ($args->slug != $plugin_slug)) {
            return $def;
        }

        // Get the current version
        $plugin_info = get_site_transient('update_plugins');
        if (!isset($plugin_info->checked) || !is_array($plugin_info->checked)) {
            return $def;
        }
        $current_version = $plugin_info->checked[$plugin_sysname];
        $args->version = $current_version;

        $request_string = array(
            'body' => array(
                'action' => $action,
                'request' => serialize($args),
                'buyer_id' => 'test_buyer_id',
                'serial_number' => 'test_serial_number',
                'website' => get_bloginfo('url'),
            ),
            'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo('url')
        );

        unset($current_version, $plugin_info, $plugin_slug, $plugin_sysname);
        $request = wp_remote_post(RDWPPS_URL, $request_string);

        if (is_wp_error($request)) {
            $res = new \WP_Error('plugins_api_failed', __('An Unexpected HTTP Error occurred during the API request.</p> <p><a href="?" onclick="document.location.reload(); return false;">Try again</a>'), $request->get_error_message());
        } elseif (isset($action) && $action == 'plugin_information' && isset($request['body'])) {
            $res = @unserialize($request['body']);

            if ($res === false) {
                $res = new \WP_Error('plugins_api_failed', __('An unknown error occurred'), $request['body']);
            }
        } else {
            return $def;
        }
        return $res;
    }// pluginInformation

    /**
     * register all plugin actions hooks and add filters or actions.
     */
    public function registerHooks()
    {
        // add filter to row meta. (in plugin page below description)
        add_filter('plugin_row_meta', [&$this, 'rowMeta'], 10, 2);
        // add filter to remove this plugin slug from check update with WordPress repository server. remove this filter if this plugin is on WordPress.
        add_filter('http_request_args', [&$this, 'httpRequestArgs'], 5, 2);
        // add filter to check update from self hosted server.
        add_filter('pre_set_site_transient_update_plugins', [&$this, 'checkSelfHostedRepository'], 10, 1);
        // add filter to display plugin information when there is an update for this plugin. this will be display as modal dialog just like other WordPress plugins.
        add_filter('plugins_api', [&$this, 'pluginInformation'], 10, 3);
    }// registerHooks

    /**
     * add links to row meta that is in plugin page under plugin description.
     * 
     * @staticvar string $plugin the plugin file name.
     * @param array $links current meta links
     * @param string $file the plugin file name for checking.
     * @return array return modified links.
     */
    public function rowMeta($links, $file)
    {
        static $plugin;// pluginname/pluginname.php

        $plugin_slug = basename(dirname(__FILE__)); // pluginname
        if (!isset($plugin) || $plugin == null) {
            $plugin = plugin_basename(__FILE__);// pluginname/pluginname.php
        }

        if ($plugin === $file) {
            $new_link[] = '<a class="thickbox open-plugin-details-modal" data-title="one file plugin" aria-label="More information about one file plugin" href="' . admin_url('plugin-install.php?tab=plugin-information&plugin=' . $plugin_slug . '&TB_iframe=true') . '">' . __('View details') . '</a>';
            $links = array_merge($links, $new_link);
            unset($new_link);
        }

        return $links;
    }// rowMeta

}

// start running the plugin. -----------------------------------------------------------------
$TestPlugin = new \TestPlug1\TestPlugin();
$TestPlugin->registerHooks();
unset($TestPlugin);