Commit 22b329c4 authored by Reinhold Kainhofer's avatar Reinhold Kainhofer

V1.2.1: Implement automatic upgrades for the advanced (paid) plugin

parent 9ed05d2a
BASE=ordernumbers
PLATTFORM=woocommerce
VENDOR=opentools
VERSION=1.2
VERSION=1.2.1
DIR = $(shell pwd)
SVNDIR=wordpress-plugin-svn
......@@ -11,7 +11,7 @@ ADVBUILDDIR=$(PLATTFORM)-advanced-$(BASE)
PLUGINFILES=library assets ordernumber_helper_woocommerce.php ordernumbers_woocommerce_basic.php readme.txt LICENSE.txt
BASICPLUGINFILES=woocommerce-basic-ordernumbers.php
ADVPLUGINFILES=woocommerce-advanced-ordernumbers.php ordernumbers_woocommerce.php
ADVPLUGINFILES=woocommerce-advanced-ordernumbers.php ordernumbers_woocommerce.php opentools-update-checker.php plugin-update-checker/
TRANSLATIONS=
......
tr.otup_update_credentials .update-credentials {
/* font-size: 13px; */
/* font-weight: 400; */
margin: 0 10px 8px 31px;
padding: 6px 12px 8px 40px;
background-color: #f7f7f7;
background-color: rgba(0,0,0,.03);
}
.widefat tr.otup_update_credentials th input, .widefat tr.otup_update_credentials thead td input {
/* margin: 0 0 0 8px; */
padding: inherit;
vertical-align: inherit;
}
tr.otup_update_credentials div.message-fail {
background-color: #FFBFBF;
padding: 3px;
}
tr.otup_update_credentials div.message-success {
background-color: #BFFFBF;
padding: 3px;
}
/**
* Ordernumber Admin JS
*/
var showUpdateCredentialsRow = function (btn, slug, nonce) {
var ajaxargs = {
type: "POST",
url: ajax_updatecheck.ajax_url,
data: {
action: 'getUpdateCredentialsRow',
slug: slug,
_ajax_nonce: nonce
},
success: function ( json ) {
jQuery(btn).closest('tr').after(json['row']);
},
error: function() { },
complete: function() { },
};
jQuery.ajax(ajaxargs);
return false;
};
var submitUpdateCredentials = function(btn) {
var tr = jQuery(btn).closest('tr');
var slug = jQuery(tr).data("slug");
var nonce = jQuery(tr).data("nonce");
var order_number = jQuery(tr).find("input[name='otup_update_credentials["+slug+"][order_number]']").val();
var order_pass = jQuery(tr).find("input[name='otup_update_credentials["+slug+"][order_pass]']").val();
var ajaxargs = {
type: "POST",
url: ajax_updatecheck.ajax_url,
data: {
action: 'submitUpdateCredentials',
slug: slug,
_ajax_nonce: nonce,
order_number: order_number,
order_pass: order_pass
},
success: function ( json ) {
if (json['success']) {
jQuery(tr).find('div.update-credentials-message').html(json['message']);
jQuery(tr).find('div.update-credentials').removeClass('message-fail').addClass('message-success')
jQuery(tr).find('div.update-credentials-form').fadeOut( 500, function() { jQuery(this).remove(); });
jQuery(tr).delay(5000).fadeOut(1000, function() { jQuery(this).remove(); });
} else {
jQuery(tr).find('div.update-credentials-message').html(json['message']);
jQuery(tr).find('div.update-credentials').addClass('message-fail').removeClass('message-success');
}
},
error: function() {
jQuery(tr).find('div.update-credentials-message').html("Unable to validate the update credentials. Please make sure the server is available.");
jQuery(tr).find('div.update-credentials').addClass('message-fail').removeClass('message-success');
},
complete: function() { },
};
jQuery.ajax(ajaxargs);
return false;
}
jQuery( function ( $ ) {
$('input.otup_update_credentials_submit').click(submitUpdateCredentials);
});
<?php
// *****************************************************************
// * PLUGIN UPDATES (using plugin-update-checker and a self-written update server script
// * http://w-shadow.com/blog/2010/09/02/automatic-updates-for-any-plugin/
// *****************************************************************
if (!class_exists('OpenToolsPluginUpdateChecker')):
require 'plugin-update-checker/plugin-update-checker.php';
class OpenToolsPluginUpdateChecker extends PluginUpdateChecker_2_1 {
public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '')
{
parent::__construct($metadataUrl, $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);
$this->installOTHooks();
}
protected function installOTHooks()
{
// Append the update credentials to the update server link
$this->addQueryArgFilter(array($this, 'appendQueryArgsCredentials'));
add_action('admin_print_scripts-plugins.php', array($this, 'addCredentialCheckScripts'));
add_action('admin_print_styles-plugins.php', array($this, 'addCredentialCheckStyles'));
add_filter('plugin_row_meta', array($this, 'displayUpdateCredentialsLink'), 10, 2);
add_action( 'wp_ajax_getUpdateCredentialsRow', array( &$this, 'getUpdateCredentialsRow') );
add_action( 'wp_ajax_submitUpdateCredentials', array( &$this, 'submitUpdateCredentials') );
// add_action('after_plugin_row_'.$this->pluginFile, array($this, 'displayUpdateCredentialsRow'), 10, 2);
// add_action('admin_init', array($this, 'checkSubmittedUpdateCredentials'));
// add_action('all_admin_notices', array($this, 'displayCredentialsCheckResult'));
}
protected function getCredentials($slug)
{
$credentials = array('validated' => FALSE);
$credentials['order_number'] = get_option('otup_credentials_order_number_'.$slug);
$credentials['order_pass'] = get_option('otup_credentials_order_pass_'.$slug);
$credentials['validated'] = get_option('otup_credentials_validated_'.$slug);
return $credentials;
}
protected function setCredentials($slug, $order_number, $order_pass, $validated = false)
{
update_option('otup_credentials_order_number_'.$slug, $order_number,false);
update_option('otup_credentials_order_pass_'.$slug, $order_pass, false);
update_option('otup_credentials_validated_'.$slug, $validated, false);
}
public function addCredentialCheckScripts() {
wp_register_script( 'opentools-updatecheck', plugins_url('assets/js/opentools-updatecheck.js', __FILE__), array('jquery'));
wp_enqueue_script( 'opentools-updatecheck');
// Handle the translations:
// Check for MS dashboard
if( is_network_admin() )
$url = network_admin_url( 'admin-ajax.php' );
else
$url = admin_url( 'admin-ajax.php' );
$localizations = array( 'ajax_url' => $url );
// in JavaScript, object properties are accessed as ajax_object.ajax_url, ajax_object.we_value
wp_localize_script( 'opentools-updatecheck', 'ajax_updatecheck', $localizations );
}
public function addCredentialCheckStyles() {
wp_register_style( 'opentools-updatecheck', plugins_url('assets/css/opentools-updatecheck.css', __FILE__));
wp_enqueue_style( 'opentools-updatecheck');
}
/** Append the ordernumber and order password to the update server URL
*/
public function appendQueryArgsCredentials($queryArgs) {
$credentials = $this->getCredentials($this->slug);
if (isset($credentials['order_number'])) {
$queryArgs['order_number'] = $credentials['order_number'];
}
if (isset($credentials['order_pass'])) {
$queryArgs['order_pass'] = $credentials['order_pass'];
}
return $queryArgs;
}
/**
* Add a "Update Credentials" link to the plugin row in the "Plugins" page. By default,
* the new link will appear after the "Visit plugin site" link.
*
* You can change the link text by using the "otup_enter_update_credentials-$slug" filter.
* Returning an empty string from the filter will disable the link.
*
* @param array $pluginMeta Array of meta links.
* @param string $pluginFile
* @return array
*/
public function displayUpdateCredentialsLink($pluginMeta, $pluginFile) {
$isRelevant = ($pluginFile == $this->pluginFile)
|| (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
if ( $isRelevant && current_user_can('update_plugins') ) {
$linkText = apply_filters('otup_enter_update_credentials-' . $this->slug, __('Update Credentials', 'oton-updates'));
if ( !empty($linkText) ) {
$pluginMeta[] = sprintf('<a href="#" onClick=\'return showUpdateCredentialsRow(this, "%s", "%s");\' >%s</a>', esc_attr($this->slug), esc_attr(wp_create_nonce( 'otup_enter_update_credentials' )), $linkText);
}
}
return $pluginMeta;
}
/**
* If the user has clicked on the "Update Credentials" link, display the input boxes after the plugin row.
*
* @param string $file
* @param array $plugin_data
* @return false|void
*/
public function getUpdateCredentialsRow() {
$json = array('row' => '', 'message'=>'Unsuccessful');
$showCredentials = isset($_REQUEST['slug'])
&& $_REQUEST['slug'] == $this->slug
&& current_user_can('update_plugins')
&& check_ajax_referer('otup_enter_update_credentials');
if ( $showCredentials && (is_network_admin() || !is_multisite() )) {
$slug = $this->slug;
if ( is_network_admin() ) {
$active_class = is_plugin_active_for_network( $this->pluginFile ) ? 'active': '';
} else {
$active_class = is_plugin_active( $this->pluginFile ) ? 'active' : '';
}
$current_credentials = $this->getCredentials($slug);
// TODO: Remove the arrows icon from the field:
$tr = '<tr class="' . $active_class . ' otup_update_credentials" id="' . esc_attr( $slug . '-credentials' ) . '" data-slug="' . esc_attr( $slug ) . '" data-nonce="' . esc_attr( wp_create_nonce( 'otup_enter_update_credentials_'.$slug ) ) . '">';
$tr .= '<th colspan="3" class="check-column colspanchange">';
$tr .= '<div class="update-credentials">';
$tr .= '<div class="update-credentials-message">';
$tr .= '</div>';
$tr .= '<div class="update-credentials-form">';
$tr .= __('Order Number:') . " <input type=\"text\" name=\"otup_update_credentials[$slug][order_number]\" value=\"" . esc_attr($current_credentials['order_number']) . "\">&nbsp;&nbsp;&nbsp;&nbsp;";
$tr .= __('Order Password:') . " <input type=\"text\" name=\"otup_update_credentials[$slug][order_pass]\" value=\"" . esc_attr($current_credentials['order_pass']) . "\">&nbsp;&nbsp;&nbsp;&nbsp;";
$tr .= '<input type="submit" class="button otup_update_credentials_submit" onclick="return submitUpdateCredentials(this);" >';
$tr .= '</div>';
$tr .= '</div></th></tr>';
$json['row'] = $tr;
$json['message'] = '';
} else {
$json['message'] = __("No permissions to modify update credentials", "opentools-updatecheck");
}
wp_send_json($json);
}
/**
* Check the submitted update credentials for correctness and save them
*
* @return void
*/
public function submitUpdateCredentials() {
$json = array('message' => '', 'success' => FALSE);
$slug = isset($_REQUEST['slug'])?($_REQUEST['slug']):"INVALIDSLUG";
$submitCredentials = $slug == $this->slug
&& current_user_can('update_plugins')
&& check_ajax_referer('otup_enter_update_credentials_'.$slug);
if ( $submitCredentials ) {
$ordernumber = $_REQUEST['order_number'];
$orderpass= $_REQUEST['order_pass'];
$message = "";
$validated = $this->checkUpdateCredentials($ordernumber, $orderpass, $message);
$this->setCredentials($this->slug, $ordernumber, $orderpass, $validated);
$json['success'] = $validated;
if ($validated) {
if ( is_network_admin() ) {
$active_class = is_plugin_active_for_network( $this->pluginFile ) ? 'active': '';
} else {
$active_class = is_plugin_active( $this->pluginFile ) ? 'active' : '';
}
$json['message'] .= __("Update credentials successfully validated and saved. Automatic updates will be provided.", "opentools-updatecheck");
} else {
$json['message'] = $message;
}
} else {
$json['message'] = __("No permissions to modify update credentials", "opentools-updatecheck");
}
wp_send_json($json);
}
public function checkUpdateCredentials($ordernumber, $orderpass, &$message)
{
$this->setCredentials($this->slug, $ordernumber, $orderpass);
$success = FALSE;
$updateinfo = $this->requestInfo(array());
if ($updateinfo && isset($updateinfo->download_url)) {
$downloadurl = $updateinfo->download_url;
$downloadurl .= (parse_url($downloadurl, PHP_URL_QUERY) ? '&' : '?') . 'check_access=1';
$headers = get_headers($downloadurl);
list($version, $status_code, $msg) = explode(' ',$headers[0], 3);
// Check the HTTP Status code
$message = $msg;
$success = ($status_code==200);
} else {
$message = __('Unable to access plugin download URL. Please check your credentials.');
}
return $success;
}
};
endif;
// *****************************************************************
......@@ -268,6 +268,84 @@ class OpenToolsOrdernumbers extends OpenToolsOrdernumbersBasic {
$reps = apply_filters( 'opentools_ordernumber_replacements', $reps, $details, $nrtype);
}
/** ************************************************************
* Support for automatic extension updates
** ************************************************************/
public function update_access_check() {
$ordernumber = $_POST['order_number'];
$orderpass = $_POST['order_pass'];
$json = $this->helper->ajax_counter_delete($_POST['nrtype'], $_POST['counter']);
wp_send_json($json);
}
public function checkUpdateAccess($order_number, $order_pass, $json = array()) {
// First, extract the update server URL from the manifest, then load
// the update XML from the update server, extract the download URL,
// append the order number and password and check whether access is
// possible.
$json['success'] = FALSE;
if (isset($this->_xmlFile)) {
$xmlfile = $this->_xmlFile;
} else {
// VM 2 does not set the _xmlFile property, so construct it manually
$xmlfile = JPATH_SITE . '/plugins/' . $this->_type . '/' . $this->_name . '/' . $this->_name . '.xml';
}
$xml = simplexml_load_file($xmlfile);
if (!$xml || !isset($xml->updateservers)) {
JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $this->_xmlFile), 'error');
return $json;
}
$updateservers = $xml->updateservers;
foreach ($updateservers->children() as $server) {
if ($server->getName()!='server') {
JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $this->_xmlFile), 'error');
continue;
}
$updateurl = html_entity_decode((string)$server);
$updatescript = simplexml_load_file($updateurl);
if (!$updatescript) {
JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_UPDATESCRIPT_ERROR', $updateurl), 'error');
continue;
}
$urls = $updatescript->xpath('/updates/update/downloads/downloadurl');
while (list( , $node) = each($urls)) {
$downloadurl = (string)($node);
if ($order_number) {
$downloadurl .= (parse_url($downloadurl, PHP_URL_QUERY) ? '&' : '?') . 'order_number=' . urlencode($order_number);
}
if ($order_pass) {
$downloadurl .= (parse_url($downloadurl, PHP_URL_QUERY) ? '&' : '?') . 'order_pass=' . urlencode($order_pass);
}
$downloadurl .= (parse_url($downloadurl, PHP_URL_QUERY) ? '&' : '?') . 'check_access=1';
$headers = get_headers($downloadurl);
list($version, $status_code, $msg) = explode(' ',$headers[0], 3);
// Check the HTTP Status code
switch($status_code) {
case 200:
$json['success'] = TRUE;
JFactory::getApplication()->enqueueMessage($msg, 'message');
$this->setupUpdateCredentials($order_number, $order_pass);
break;
default:
JFactory::getApplication()->enqueueMessage($msg, 'error');
// Clear the credentials...
$this->setupUpdateCredentials("", "");
break;
}
$this->setAndSaveParams(array(
'update_credentials_checked'=>$json['success'],
'order_number' => $order_number,
'order_pass' => $order_pass,
));
}
}
return $json;
}
/** ************************************************************
* Support for WPO WooCommerce PDF Invoices and Packaging Slips
** ************************************************************
......
Plugin Update Checker
=====================
This is a custom update checker library for WordPress plugins. It lets you add automatic update notifications and one-click upgrades to your commercial and private plugins. All you need to do is put your plugin details in a JSON file, place the file on your server, and pass the URL to the library. The library periodically checks the URL to see if there's a new version available and displays an update notification to the user if necessary.
From the users' perspective, it works just like with plugins hosted on WordPress.org. The update checker uses the default plugin upgrade UI that will already be familiar to most WordPress users.
[See this blog post](http://w-shadow.com/blog/2010/09/02/automatic-updates-for-any-plugin/) for more information and usage instructions.
Getting Started
---------------
### Self-hosted Plugins
1. Make a JSON file that describes your plugin. Here's a minimal example:
```json
{
"name" : "My Cool Plugin",
"version" : "2.0",
"author" : "John Smith",
"download_url" : "http://example.com/plugins/my-cool-plugin.zip",
"sections" : {
"description" : "Plugin description here. You can use HTML."
}
}
```
See [this table](https://spreadsheets.google.com/pub?key=0AqP80E74YcUWdEdETXZLcXhjd2w0cHMwX2U1eDlWTHc&authkey=CK7h9toK&hl=en&single=true&gid=0&output=html) for a full list of supported fields.
2. Upload this file to a publicly accessible location.
3. Download [the update checker](https://github.com/YahnisElsts/plugin-update-checker/releases/latest), unzip the archive and copy the `plugin-update-checker` directory to your plugin.
4. Add the following code to the main plugin file:
```php
require 'plugin-update-checker/plugin-update-checker.php';
$myUpdateChecker = PucFactory::buildUpdateChecker(
'http://example.com/path/to/metadata.json',
__FILE__
);
```
#### Notes
- You could use [wp-update-server](https://github.com/YahnisElsts/wp-update-server) to automatically generate JSON metadata from ZIP packages.
- The second argument passed to `buildUpdateChecker` should be the full path to the main plugin file.
- There are more options available - see the [blog](http://w-shadow.com/blog/2010/09/02/automatic-updates-for-any-plugin/) for details.
### Plugins Hosted on GitHub
*(GitHub support is experimental.)*
1. Download [the latest release](https://github.com/YahnisElsts/plugin-update-checker/releases/latest), unzip it and copy the `plugin-update-checker` directory to your plugin.
2. Add the following code to the main file of your plugin:
```php
require 'plugin-update-checker/plugin-update-checker.php';
$className = PucFactory::getLatestClassVersion('PucGitHubChecker');
$myUpdateChecker = new $className(
'https://github.com/user-name/plugin-repo-name/',
__FILE__,
'master'
);
```
The third argument specifies the branch to use for updating your plugin. The default is `master`. If the branch name is omitted or set to `master`, the update checker will use the latest release or tag (if available). Otherwise it will use the specified branch.
3. Optional: Add a `readme.txt` file formatted according to the [WordPress.org plugin readme standard](https://wordpress.org/plugins/about/readme.txt). The contents of this file will be shown when the user clicks the "View version 1.2.3 details" link.
#### Notes
If your GitHub repository requires an access token, you can specify it like this:
```php
$myUpdateChecker->setAccessToken('your-token-here');
```
The GitHub version of the library will pull update details from the following parts of a release/tag/branch:
- Changelog
- The "Changelog" section of `readme.txt`.
- One of the following files:
CHANGES.md, CHANGELOG.md, changes.md, changelog.md
- Release notes.
- Version number
- The "Version" plugin header.
- The latest release or tag name.
- Required and tested WordPress versions
- The "Requires at least" and "Tested up to" fields in `readme.txt`.
- The following plugin headers:
`Required WP`, `Tested WP`, `Requires at least`, `Tested up to`
- "Last updated" timestamp
- The creation timestamp of the latest release.
- The latest commit of the selected tag or branch that changed the main plugin file.
- Number of downloads
- The `download_count` statistic of the latest release.
- If you're not using GitHub releases, there will be no download stats.
- Other plugin details - author, homepage URL, description
- The "Description" section of `readme.txt`.
- Remote plugin headers (i.e. the latest version on GitHub).
- Local plugin headers (i.e. the currently installed version).
- Ratings, banners, screenshots
- Not supported.
{
"name": "yahnis-elsts/plugin-update-checker",
"type": "library",
"description": "A custom update checker for WordPress plugins. Useful if you can't host your plugin in the official WP plugin repository but still want it to support automatic plugin updates.",
"keywords": ["wordpress", "plugin updates", "automatic updates"],
"homepage": "https://github.com/YahnisElsts/plugin-update-checker/",
"license": "MIT",
"authors": [
{
"name": "Yahnis Elsts",
"email": "whiteshadow@w-shadow.com",
"homepage": "http://w-shadow.com/",
"role": "Developer"
}
],
"require": {
"php": ">=5.2.0"
}
}
\ No newline at end of file
.puc-debug-bar-panel pre {
margin-top: 0;
}
/* Style the debug data table to match "widefat" table style used by WordPress. */
table.puc-debug-data {
width: 100%;
clear: both;
margin: 0;
border-spacing: 0;
background-color: #f9f9f9;
border-radius: 3px;
border: 1px solid #dfdfdf;
border-collapse: separate;
}
table.puc-debug-data * {
word-wrap: break-word;
}
table.puc-debug-data th {
width: 11em;
padding: 7px 7px 8px;
text-align: left;
font-family: "Georgia", "Times New Roman", "Bitstream Charter", "Times", serif;
font-weight: 400;
font-size: 14px;
line-height: 1.3em;
text-shadow: rgba(255, 255, 255, 0.804) 0 1px 0;
}
table.puc-debug-data td, table.puc-debug-data th {
border-width: 1px 0;
border-style: solid;
border-top-color: #fff;
border-bottom-color: #dfdfdf;
text-transform: none;
}
table.puc-debug-data td {
color: #555;
font-size: 12px;
padding: 4px 7px 2px;
vertical-align: top;
}
.puc-ajax-response {
border: 1px solid #dfdfdf;
border-radius: 3px;
padding: 0.5em;
margin: 5px 0;
background-color: white;
}
.puc-ajax-nonce {
display: none;
}
\ No newline at end of file
<?php
if ( !class_exists('PluginUpdateCheckerPanel', false) && class_exists('Debug_Bar_Panel', false) ) {
/**
* A Debug Bar panel for the plugin update checker.
*/
class PluginUpdateCheckerPanel extends Debug_Bar_Panel {
/** @var PluginUpdateChecker */
private $updateChecker;
public function __construct($updateChecker) {
$this->updateChecker = $updateChecker;
$title = sprintf(
'<span id="puc-debug-menu-link-%s">PUC (%s)</span>',
esc_attr($this->updateChecker->slug),
$this->updateChecker->slug
);
parent::Debug_Bar_Panel($title);
}