I have written my own anti-spam plugin. It has been live-tested 3 months. Since then the code has proven to be very effective in combat against post-spam.
I decided to create a full-featured plugin and distribute it over 20 WordPress sites. And here comes the problem which is a combination of 2 axioms:
When I improve the code, I cannot update plug-in manually over 20 websites. This can be solved by uploading plugin code to the official WordPress repository what is not a big deal since I already maintain one such plug-in.
This procedure has one disadvantage, especially when it comes to anti-spam plugins. Open-sourcing code makes such plug-in vulnerable because a potential spammer can learn how to by-pass protection algorithm.
Solution to the need of standard WordPress updates without source-code disclosure over official plug-in repository is to create a private WordPress repo.
How hard it is? Let’s ask Google. I was surprised when I found several pre-made libraries while most robust is wp-update-server created by Yahnis Elsts.
It’s actively maintained and.. real overkill for my need of “1-plugin-repository“. What else is out there? Misha Rudrastyh wrote a beautiful post on the topic Self-Hosted Plugin Updates.
All you need it to add 3 additional “add_action” to your plugin + PHP controller (your very simple private plugin repository server) responding with jSon on is-there-a-new-release question.
Code for a plugin
Place following snippets to your plug-in or theme.
Pop-up with details about plugin update visible when there is a new release
define( 'PREFIX_PLUGIN_VERSION', '1.7' );
###################
# Automatic updates
###################
/**
* Pop-up with details about plugin update visible when there is a new release
*/
function prefix_plugin_info( $res, $action, $args ) {
// do nothing if this is not about getting plugin information
if ($action !== 'plugin_information') {
return false;
}
// do nothing if it is not our plugin
if ('pluginslug' !== $args->slug) {
return $res;
}
// trying to get from cache first, to disable cache comment 23,33,34,35,36
if (false == $remote = get_transient( 'prefix_upgrade_pluginslug' )) {
// info.json is the file with the actual information about plug-in on your server
$remote = wp_remote_get( 'https://www.example.com/foldername/get-info.php?slug=pluginslug&action=info', array(
'timeout' => 10,
'headers' => array(
'Accept' => 'application/json'
))
);
if (!is_wp_error( $remote ) && isset( $remote[ 'response' ][ 'code' ] ) && $remote[ 'response' ][ 'code' ] == 200 && !empty( $remote[ 'body' ] )) {
set_transient( 'prefix_upgrade_pluginslug', $remote, 21600 ); // 6 hours cache
}
}
if (!is_wp_error( $remote )) {
$remote = json_decode( $remote[ 'body' ] );
$res = new stdClass();
$res->name = $remote->name;
$res->slug = $remote->slug;
$res->version = $remote->version;
$res->tested = $remote->tested;
$res->requires = $remote->requires;
$res->author = $remote->author;
$res->author_profile = $remote->author_homepage;
$res->download_link = $remote->download_link;
$res->trunk = $remote->download_link;
$res->last_updated = $remote->last_updated;
$res->sections = array(
'description' => $remote->sections->description, // description tab
'installation' => $remote->sections->installation, // installation tab
// you can add your custom sections (tabs) here like 'changelog'
);
$res->banners = array(
'low' => $remote->banners->low,
'high' => $remote->banners->high,
);
return $res;
}
return false;
}
add_filter( 'plugins_api', 'prefix_plugin_info', 20, 3 );
Push update itself for plugin or theme
/**
* Push update itself for plugin or theme
*/
function prefix_push_update( $transient ) {
if (empty( $transient->checked )) {
return $transient;
}
// trying to get from cache first, to disable cache comment 11,20,21,22,23
if (false == $remote = get_transient( 'prefix_upgrade_pluginslug' )) {
// info.json is the file with the actual plugin information on your server
$remote = wp_remote_get( 'https://www.jasom.net/repo/get-info.php?slug=pluginslug&action=update', array(
'timeout' => 10,
'headers' => array(
'Accept' => 'application/json'
))
);
if (!is_wp_error( $remote ) && isset( $remote[ 'response' ][ 'code' ] ) && $remote[ 'response' ][ 'code' ] == 200 && !empty( $remote[ 'body' ] )) {
set_transient( 'prefix_upgrade_pluginslug', $remote, 21600 ); // 6 hours cache
}
}
if ($remote) {
$remote = json_decode( $remote[ 'body' ] );
// your installed plugin version should be on the line below! You can obtain it dynamically of course
if ($remote && version_compare( prefix_PLUGIN_VERSION, $remote->version, '<' ) && version_compare( $remote->requires, get_bloginfo( 'version' ), '<' )) {
$res = new stdClass();
$res->slug = 'pluginslug';
$res->plugin = 'pluginslug/pluginslug.php'; // it could be just pluginslug.php if your plugin doesn't have its own directory
$res->new_version = $remote->version;
$res->tested = $remote->tested;
$res->package = $remote->download_link;
$transient->response[ $res->plugin ] = $res;
//$transient->checked[$res->plugin] = $remote->version;
}
}
return $transient;
}
add_filter( 'site_transient_update_plugins', 'prefix_push_update' );
Cache the results to make update process fast
/**
* Cache the results to make update process fast
*/
function prefix_after_update( $upgrader_object, $options ) {
if ($options[ 'action' ] == 'update' && $options[ 'type' ] === 'plugin') {
// just clean the cache when new plugin version is installed
delete_transient( 'prefix_upgrade_pluginslug' );
}
}
add_action( 'upgrader_process_complete', 'prefix_after_update', 10, 2 );
How my “custom-wordpress-plugin-repository” works
Controller for a simple repository server is uploaded to Github. I will summarize how to install and configure it + how the script works.
Download file and configure it with your domain name and folder where it runs. It requires you to run cron job for the url:
https://www.example.com/repofolder/get-info.php?action=cron
Upload your zipped plugin to repo folder. Controller on cron run will automatically recognize all .zip
files, creates a new jSon
file for each plugin with basic information extracted from plugin.php
file and logs cron run to the file cron.log
.
It checks if uploaded .zip
files have up-to-date jSon which is used to store information when WordPress installation with the plugin enabled requests the info under URL as follows:
https://www.example.com/repofolder/get-info.php?slug=pluginslug
Every WordPress request is logged to the file called requests.log
where you can see requested plugin, the domain request came from and the time of the request.
Ok, that’s all. Fork, build upon, enjoy.
Tobias Berke said on
I’ve written a little plugin for WP in the last few weeks. And have been hanging on the problem of the update function for a long time.
I read your blog post with great interest. Thanks a lot for this!
I then installed your demo plugin in my test environment. When I click the button “Check again” in the dashboard, I don’t get an updated display.
Get-info.php can be reached via Postman. I am using WP 5.3.2
When I run the get-info.php directly, I get this error:
Fatal error: Uncaught Error: Class’ ZipArchive ‘not found in /otl/get-info.php:233 Stack trace: # 0 /otl/get-info.php(159): RepoServer-> unzipPlugin (‘ mypluginslug1.z. .. ‘) # 1 /otl/get-info.php(130): RepoServer-> createJsonInfoFile (‘ mypluginslug1.z … ‘) # 2 /otl/get-info.php(64): RepoServer-> cron () # 3 /otl/get-info.php(275): RepoServer -> __ construct () # 4 {main} thrown in /otl/get-info.php on line 233
Do you have any idea how I can fix the errors? I’ve been working on it all week now and I’m not really getting anywhere.
Thank you for your time.
jasom said on
Hi Tobias. It is super easy. Using my easy German: Deine php-server kein zip, ja? 🙂 https://stackoverflow.com/q/38104348