9

I'm developing a wordpress plugin at the moment which I do not want in the Wordpress plugin repository. However I still want to be able to push updates to my costumers from my own API repository.

I've been reading quite a bit about this, and one thing that seems to be something about is the pre_set_site_transient_update_plugins filter, however I can't find much info about this. I've tried this tutorial (http://konstruktors.com/blog/wordpress/2538-automatic-updates-for-plugins-and-themes-hosted-outside-wordpress-extend/) which I couldn't get working. I can tell from the comments that others can actually get this working with what must be almost the current version of WP (latest response Apr 22).

I tried installing the plugin from the site and putting the API folder on a second domain, but the update notification I usually get when an update is available, didn't show anywhere at all.

I'm not sure if it is actually possible to have custom plugins run the auto-update from other repositories so I would like to hear if anyone in here has any experience at all with this stuff? The solution in the tutorial seemed to be an easy solution - I wonder if it's somehow possible to do it in a more advanced way?

Any help getting this auto-update from my own repository working would be much appreciated!

(PS: I'm running WP version 3.1.3)

1

5 Answers 5

7

For the benefit of others who find this page, I suggest those wishing to provide their own updates outside the official WP repository check out this project on GitHub, that demonstrates the functionality:

https://github.com/jeremyclark13/automatic-theme-plugin-update

2

For a single-site install (I haven't tested it on a multi-site), there are only two hooks you need to update from an external service such as github or gitlab. In the code below, I use gitlab since that's what I use to host my code right now. I probably should abstract the gitlab parts out...

The first hook you'll need to use is pre_set_site_transient_update_themes. This is the filter that WordPress uses to set the site_transient to show if there are updates available. Use this hook to connect to your remote version and see if there are updates available. If there are, then modify the transient so that WordPress knows there are updates and can show the notice to the user.

The other hook you'll need to use is upgrader_source_selection. This filter is needed, for gitlab anyway, because the name of the downloaded folder is not the same as the theme, so we use this hook to rename it to the correct name. If you're remote repository provides a zip with the correct name, then you don't even need this hook.

The third, optional, hook you can use is auto_update_theme if you want to auto update your theme. In the example below, I use this hook to auto update only this specific theme.

This code has only been tested with WordPress 4.9.x. It requires PHP > 7.0.

functions.php

//* Load the updater.
require PATH_TO . 'updater.php';
$updater = new updater();
\add_action( 'init', [ $updater, 'init' ] );

updater.php

/**
 * @package StackExchange\WordPress
 */
declare( strict_types = 1 );
namespace StackExchange\WordPress;

/**
 * Class for updating the theme.
 */
class updater {

  /**
   * @var Theme slug.
   */
  protected $theme = 'theme';

  /**
   * @var Theme repository name.
   */
  protected $repository = 'project/theme';

  /**
   * @var Repository domain.
   */
  protected $domain = 'https://gitlab.com/';

  /**
   * @var CSS endpoint for repository.
   */
  protected $css_endpoint = '/raw/master/style.css';

  /**
   * @var ZIP endpoint for repository.
   */
  protected $zip_endpoint = '/repository/archive.zip';

  /**
   * @var Remote CSS URI.
   */
  protected $remote_css_uri;

  /**
   * @var Remote ZIP URI.
   */
  protected $remote_zip_uri;

  /**
   * @var Remote version.
   */
  protected $remote_version;

  /**
   * @var Local version.
   */
  protected $local_version;

  /**
   * Method called from the init hook to initiate the updater
   */
  public function init() {
    \add_filter( 'auto_update_theme', [ $this, 'auto_update_theme' ], 20, 2 );
    \add_filter( 'upgrader_source_selection', [ $this, 'upgrader_source_selection' ], 10, 4 );
    \add_filter( 'pre_set_site_transient_update_themes', [ $this, 'pre_set_site_transient_update_themes' ] );
  }

  /**
   * Method called from the auto_update_theme hook.
   * Only auto update this theme.
   * This hook and method are only needed if you want to auto update the theme.
   *
   * @return bool Whether to update the theme.
   */
  public function auto_update_theme( bool $update, \stdClass $item ) : bool {
    return $this->theme === $item->theme;
  }

  /**
   * Rename the unzipped folder to be the same as the existing folder
   *
   * @param string       $source        File source location
   * @param string       $remote_source Remote file source location
   * @param \WP_Upgrader $upgrader      \WP_Upgrader instance
   * @param array        $hook_extra    Extra arguments passed to hooked filters
   *
   * @return string | \WP_Error The updated source location or a \WP_Error object on failure
   */
  public function upgrader_source_selection( string $source, string $remote_source, \WP_Upgrader $upgrader, array $hook_extra ) {
    global $wp_filesystem;

    $update = [ 'update-selected', 'update-selected-themes', 'upgrade-theme' ];

    if( ! isset( $_GET[ 'action' ] ) || ! in_array( $_GET[ 'action' ], $update, true ) ) {
      return $source;
    }

    if( ! isset( $source, $remote_source ) ) {
      return $source;
    }

    if( false === stristr( basename( $source ), $this->theme ) ) {
      return $source;
    }

    $basename = basename( $source );
    $upgrader->skin->feedback( esc_html_e( 'Renaming theme directory.', 'bootstrap' ) );
    $corrected_source = str_replace( $basename, $this->theme, $source );

    if( $wp_filesystem->move( $source, $corrected_source, true ) ) {
      $upgrader->skin->feedback( esc_html_e( 'Rename successful.', 'bootstrap' ) );
      return $corrected_source;
    }

    return new \WP_Error();
  }

  /**
   * Add respoinse to update transient if theme has an update.
   *
   * @param $transient
   *
   * @return
   */
  public function pre_set_site_transient_update_themes( $transient ) {
    require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
    $this->local_version = ( \wp_get_theme( $this->theme ) )->get( 'Version' );

    if( $this->hasUpdate() ) {
      $response = [
        'theme'       => $this->theme,
        'new_version' => $this->remote_version,
        'url'         => $this->construct_repository_uri(),
        'package'     => $this->construct_remote_zip_uri(),
        'branch'      => 'master',
      ];
      $transient->response[ $this->theme ] = $response;
    }

    return $transient;
  }

  /**
   * Construct and return the URI to the remote stylesheet
   *
   * @return string The remote stylesheet URI
   */
  protected function construct_remote_stylesheet_uri() : string {
    return $this->remote_css_uri = $this->domain . $this->repository . $this->css_endpoint;
  }

  /**
   * Construct and return the URI to the remote ZIP file
   *
   * @return string The remote ZIP URI
   */
  protected function construct_remote_zip_uri() : string {
    return $this->remote_zip_uri = $this->domain . $this->repository . $this->zip_endpoint;
  }

  /**
   * Construct and return the URI to remote repository
   *
   * @access protected
   * @since  1.0
   *
   * @return string The remote repository URI
   */
  protected function construct_repository_uri() : string {
    return $this->repository_uri = $this->domain . \trailingslashit( $this->repository );
  }

  /**
   * Get and return the remote version
   *
   * @return string The remote version
   */
  protected function get_remote_version() : string {
    $this->remote_stylesheet_uri = $this->construct_remote_stylesheet_uri();
    $response = $this->remote_get( $this->remote_stylesheet_uri );
    $response = str_replace( "\r", "\n", \wp_remote_retrieve_body( $response ) );
    $headers = [ 'Version' => 'Version' ];

    foreach( $headers as $field => $regex ) {
      if( preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $response, $match ) && $match[1] ) {
        $headers[ $field ] = _cleanup_header_comment( $match[1] );
      }
      else {
        $headers[ $field ] = '';
      }
    }

    return $this->remote_version = ( '' === $headers[ 'Version' ] ) ? '' : $headers[ 'Version' ];
  }

  /**
   * Return whether the theme has an update
   *
   * @return bool Whether the theme has an update
   */
  protected function hasUpdate() : bool {
    if( ! $this->remote_version ) $this->remote_version = $this->get_remote_version();
    return version_compare( $this->remote_version, $this->local_version, '>' );
  }

  /**
   * Wrapper for \wp_remote_get()
   *
   * @param string $url  The URL to get
   * @param array  $args Array or arguments to pass through to \wp_remote_get()
   *
   * @return array|WP_Error Return the request or an error object
   */
  protected function remote_get( string $url, array $args = [] ) {
    return \wp_remote_get( $url, $args );
  }
}
1

Yes, this is possible. There's an entire chapter in Professional WordPress Plugin Development dedicated to this. If you haven't already, pick up a copy. It will definitely help.

4
  • I actually found a PDF version of this online, but that didn't seem to work for me either.
    – Simon
    May 29, 2011 at 21:08
  • It does work if you do it right, I have done it, look at the HTTP API, codex.wordpress.org/HTTP_API
    – Wyck
    May 30, 2011 at 3:58
  • I just started over again. What I've got so far is hooking into the plugin update check using add_filter("pre_set_site_transient_update_plugins","dne_altapi_check"); After that I have the function dne_altapi_check which contains print_r("hi"); - however when I press the "Check again" button under updates, it doesn't print anything at all.. Am I doing something wrong when hooking into the update checker?
    – Simon
    May 30, 2011 at 8:06
  • I remember someone wrote class for plugin update staff, but can find link for that post :/
    – Mamaduka
    Oct 3, 2011 at 21:48
1

There is this commercial Plugin and Theme Update API manager for WooCommerce that specifically works if the plugin or theme is not hosted on wordpress.org. It is designed to provide updates for self hosted plugins and themes. The plugin is for those who don’t want to write it yourself, and need a lot of features, plus working examples for plugins and themes that are being sold.

http://www.toddlahman.com/shop/wordpress-automatic-update-api-manager/

1

There is also a neat service at http://wp-updates.com/ - you get one theme or plugin for free. FYI - this is not my site but I've tried it a while ago and it seemed pretty good.

1
  • Seems a nice service, but I didn't notice (almost on free plan) HTTPS in web control panel nor in communication: furthermore I didn't found any kind of ownership check when making the update check (it seems to me a very plain POST request), I feel that stuff could be stealed, knowing the plugin name and making some guesses. I would have loved to use it, if it seemed a bit more professional on security side.
    – reallynice
    Apr 8, 2015 at 15:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.