YahnisElsts/plugin-update-checker

API for self update function

Masegi opened this issue · 11 comments

Masegi commented

I'm trien to make my plugin update itself, when I call an api. Its an internal plugin and a server to server connection, so it should be secure enough.

I tried different things, but Plugin_Upgrader always returns "The plugin is at the latest version".

I tried this for example:

`global $tdhPluginUpdateChecker;
$plugin_slug = 'justawesome-asd';

$plugin = plugin_basename( sanitize_text_field( wp_unslash( $plugin_slug ) ) );

$status = array(
	'success'     => false,
	'update'     => 'plugin',
	'slug'       => sanitize_key( wp_unslash( $plugin_slug ) ),
	'oldVersion' => '',
	'newVersion' => '',
	'errorMessage' => '',
);

$plugin_data = get_plugin_data( __FILE__ );
print_r($plugin_data);
$status['plugin']     = $plugin;
$status['pluginName'] = $plugin_data['Name'];

if ( $plugin_data['Version'] ) {
	$status['oldVersion'] = $plugin_data['Version'];
}

require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';

$tmpUpdate = $tdhPluginUpdateChecker->checkForUpdates();
wp_update_plugins();

$skin     = new WP_Ajax_Upgrader_Skin();
$upgrader = new Plugin_Upgrader( $skin );
// $result   = $upgrader->bulk_upgrade( array( $plugin ) );
$result   = $upgrader->upgrade( $plugin );

var_dump($result);
// print_r($skin->result);

if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
	$status['debug'] = $skin->get_upgrade_messages();
}

if ( is_wp_error( $skin->result ) ) {
	$status['errorCode']    = $skin->result->get_error_code();
	$status['errorMessage'] = $skin->result->get_error_message();
	return $status;
} elseif ( $skin->get_errors()->has_errors() ) {
	$status['errorMessage'] = $skin->get_error_messages();
	return $status;
} elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
	if ( true === $result[ $plugin ] ) {
		$status['errorMessage'] = $upgrader->strings['up_to_date'];
		return $status;
	}

	$plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
	$plugin_data = reset( $plugin_data );

	if ( $plugin_data['Version'] ) {
		$status['newVersion'] = $plugin_data['Version'];
		$status['success'] = true;
	}

	return $status;
} elseif ( false === $result ) {
	global $wp_filesystem;

	$status['errorCode']    = 'unable_to_connect_to_filesystem';
	$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );

	// Pass through the error from WP_Filesystem if one was raised.
	if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
		$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
	}

	return $status;
}

// An unhandled error occurred.
$status['errorMessage'] = __( 'Plugin update failed.' );
return $status;	`

By using code from ajax updater and trien to make plugin-update-checker inject the external plugin.

So basicly my target is an function call to check and update plugin by rest api.

Does the update show up in "Dashboard -> Updates"? If it doesn't, there might be a problem with PUC initialization or configuration. If it does, the problem might be something more complicated.

Masegi commented

Yes, when I do update by dashboard its already working fine

Hmm, I don't see an obvious reason why it wouldn't work then. Since you're already doing some output/logging in that code, could you also dump the site_transient_update_plugins site transient just after the checkForUpdates call to see if your plugin is getting added to the update list or not?

You could also try calling the getUpdate method on PUC and dump the result. This is to see if PUC itself knows that there is an update. It should return either update details or NULL.

Masegi commented

trien, shouldnt this give an object? it return false

	$transient = get_transient('site_transient_update_plugins');
	var_dump($transient);

I already debugged $result of upgrader and result of PUC also also old plugin data. All seems to be correct, but actually cant look into what-to-update-object yet

Sorry, I gave you the wrong transient name by mistake. It should probably be this:
$transient = get_site_transient('update_plugins');

Masegi commented

ok the plugin is shown in the object but im note sure if version is correc,t in the checked propery is the current version shown not the outstanding, but when I look at return of PUC checkForUpdates it shows next version as expected. But in the object[pluginname] property is the next / coming version, so hat seem to be correct.

Masegi commented

just to get sure, it looks like this:


Array
(
    [WC requires at least] => 
    [WC tested up to] => 
    [Woo] => 
    [Name] => TDH CRM Frontend
    [PluginURI] => https://www.justaswesome.de
    [Version] => 1.0.508
    [Description] => asd.</cite>
    [Author] => <a href="https://www.justawesome.de/ueber-uns/">asd</a>
    [AuthorURI] => https://www.justawesome.de/ueber-uns/
    [TextDomain] => asd
    [DomainPath] => 
    [Network] => 
    [RequiresWP] => 
    [RequiresPHP] => 
    [UpdateURI] => 
    [Title] => <a href="https://www.justaswesome.de">asd</a>
    [AuthorName] => asd
)

upgrader->upgrade( $plugin ):

YahnisElsts\PluginUpdateChecker\v5p2\Plugin\Update Object
(
    [id] => 0
    [homepage] => https://www.justawesome.de/
    [upgrade_notice] => 
    [tested] => 6.2.999
    [requires_php] => 
    [icons] => Array
        (
        )

    [filename] =>asd/asd.php
    [slug] => justawesome-tdh
    [version] => 1.0.509
    [download_url] => asd/asd.zip
    [translations] => Array
        (
        )

    [extraProperties:protected] => Array
        (
        )

)

get_site_transient('update_plugins'):
object(stdClass)#36101 (5) {
  ["last_checked"]=>
  int(1697810681)
  ["response"]=>
  array(3) {
    ....
    ["asd/asd.php"]=>
    object(stdClass)#36102 (8) {
      ["slug"]=>
      string(15) "asd"
      ["new_version"]=>
      string(7) "1.0.509"
      ["package"]=>
      string(79) "asd/asd.zip"
      ["id"]=>
      int(0)
      ["url"]=>
      string(27) "https://www.justawesome.de/"
      ["tested"]=>
      string(7) "6.2.999"
      ["requires_php"]=>
      NULL
      ["plugin"]=>
      string(35) "asd/asd.php"
    }
  }....
  }
  ["checked"]=>
  array(39) {
    ...
    ["asd/asd.php"]=>
    string(7) "1.0.508"
	...
  }
}```

changed plugin name to asd

That looks fine-ish to me.

Are you sure you're passing the correct value to $upgrader->upgrade()? The PHPDoc for that method says the argument should be:

Path to the plugin file relative to the plugins directory.

Usually, this will look something like foo/bar.php. Does the $plugin value look like that at that point, or is it something else?

Masegi commented

great, thx, that gives me some progress. Now I get "Plugin update failed." as errorMessage (so last line of function), so result of upgrader->upgrade is true, I will check it again, maybe I can strip out the core stuff and go back to wp functions, Il give feedback.

Oh but debug log gives this: "debug":["Downloading update from asd/asd.zip…","Unpacking the update…","Installing the latest version…","Removing the old version of the plugin…","Plugin updated successfully."]

so il dig deeper.

Masegi commented

Thanks for your fast response again btw.

its working fine now, did it like this:


function pluginname_asd_upgrade_plugin( ) {
	global $tdhPluginUpdateChecker;	
	
	$plugin_slug = 'pluginname-asd';
	$plugin_base_name = $plugin_slug.'/'.$plugin_slug.'.php';
	
	$plugin = plugin_basename( sanitize_text_field( wp_unslash( $plugin_base_name ) ) );

	$status = array(
		'success'     => false,
		'update'     => 'plugin',
		'slug'       => sanitize_key( wp_unslash( $plugin_slug ) ),
		'oldVersion' => '',
		'newVersion' => '',
		'errorMessage' => '',
	);

	// if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
		// $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
		// return $status;
	// }
	
	$plugin_data = get_plugin_data( __FILE__ );
	$status['plugin']     = $plugin;
	$status['pluginName'] = $plugin_data['Name'];

	if ( $plugin_data['Version'] ) {
		$status['oldVersion'] = $plugin_data['Version'];
	}

	require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';

	$tdhPluginUpdateChecker->checkForUpdates();
	wp_update_plugins();

	$skin     = new WP_Ajax_Upgrader_Skin();
	$upgrader = new Plugin_Upgrader( $skin );
	
	if($upgrader->upgrade( $plugin ))
	{	 
		$plugin_data = get_plugin_data( __FILE__ );

		if ( $plugin_data['Version'] ) {
			$status['newVersion'] = $plugin_data['Version'];
			$status['success'] = true;
		}
		activate_plugin( $plugin_base_name );
		return $status;
	} 
	else
	{
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			$status['debug'] = $skin->get_upgrade_messages();
		}

		if ( is_wp_error( $skin->result ) ) {
			$status['errorCode']    = $skin->result->get_error_code();
			$status['errorMessage'] = $skin->result->get_error_message();
			return $status;
		} elseif ( $skin->get_errors()->has_errors() ) {
			$status['errorMessage'] = $skin->get_error_messages();
			return $status;
		} elseif ( false === $result ) {
			global $wp_filesystem;

			$status['errorCode']    = 'unable_to_connect_to_filesystem';
			$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );

			// Pass through the error from WP_Filesystem if one was raised.
			if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
				$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
			}

			return $status;
		}
	}
	// An unhandled error occurred.
	$status['errorMessage'] = __( 'Plugin update failed.' );
	return $status;	
}
 
function pluginname_asd_api_update( WP_REST_Request $request ) {			
	$returnObj = array();	
	$returnObj['success'] = false;
	
	$data = $request->get_params();
	if($data['key'] == 'rndstr')
	{				
		$returnObj = pluginname_asd_upgrade_plugin();
	}	
	return $returnObj;
}

add_action( 'rest_api_init', function () {
  register_rest_route( 'pluginname-asd/v1', '/update', array(
    'methods' => 'POST',
    'callback' => 'pluginname_asd_api_update',
    'permission_callback' => '__return_true',
  ) );
  register_rest_route( 'pluginname-asd/v1', '/update', array(
    'methods' => 'GET',
    'callback' => 'pluginname_asd_api_update',
    'permission_callback' => '__return_true',
  ) );
} );```

All right. Since the problem appears solved, I will close this issue now.