Loading...
Hot as hell, this is the top-bar widget Link

Updater

A shared, self-contained update library for Bootscore products (theme and plugins). One class file, loaded once, used by any number of products – no per-product duplication of update-checking code.

Supports two update sources:

  • Custom server – a private info.json file you host yourself (e.g. for commercial plugins)
  • Public GitHub releases – reads directly from a public repo’s GitHub Releases and readme.txt, no access token needed

How it works

class-update-checker.php only defines the Bootscore_Update_Checker class. Nothing happens until something instantiates it and registers a product. This makes the file completely safe to include anywhere, at any time – if it’s never instantiated, it’s inert.

Every consumer (theme or plugin) is expected to:

  1. Make sure the class is loaded (with a fallback, in case nothing else has loaded it yet)
  2. Instantiate the shared $bootscore_updater global, if it doesn’t already exist
  3. Call register_product() with its own details

All of this happens inside an add_action('init', ...) callback – not at top-level file-include time. This avoids load-order issues between the theme and plugins (WordPress plugins load before the theme’s functions.php, so relying on top-level code being “instantiated by whoever loads first” is fragile).

Because every consumer carries its own fallback, the theme does not need to explicitly load class-update-checker.phpin functions.php. It’s safe to do so for clarity/early-loading, but not required.

Registering a product

register_product() accepts:

KeyRequiredNotes
typeyes'plugin' or 'theme'
slugyesUnique per product. Must not be reused across products – sharing a slug means one registration silently overwrites the other.
current_versionyesUsually read from the plugin/theme header
fileplugins onlyplugin_basename(...) of the main plugin file
sourceyes'custom' or 'github'
info_urlcustom source onlyFull, direct URL to info.json
github_repogithub source only'owner/repo'
namenoAuto-generated from slug if omitted
requiresno, github onlyMin WP version override. custom source always reads this from info.json.
testedno, github only“Tested up to” override. Falls back to the repo’s readme.txt, then the site’s own WP version.
requires_phpno, github onlyMin PHP version override

Important: slug must be unique across all registered products sharing the same $bootscore_updater instance (theme + every plugin on the same site). Copy-pasting a config from one product to another without updating the slug will cause one product’s registration to silently overwrite the other’s.

Case 1 – Plugin, private server (info.json)

Used for commercial plugins hosted on your own server.

inc/updater-config.php (required by the plugin’s main file):

<?php
defined('ABSPATH') || exit;

global $bootscore_updater;

// Ensure the class is loaded
if (!class_exists('Bootscore_Update_Checker')) {
  $theme_path = get_template_directory() . '/inc/updater/class-update-checker.php';
  if (file_exists($theme_path)) {
    require_once $theme_path;
  }
}

// Initialize if class exists
if (!isset($bootscore_updater) && class_exists('Bootscore_Update_Checker')) {
  $bootscore_updater = new Bootscore_Update_Checker(12 * HOUR_IN_SECONDS);
}

// Only proceed if updater is ready
if (!isset($bootscore_updater) || !class_exists('Bootscore_Update_Checker')) {
  return; // Silent fail if updater not available
}

add_action('init', function() {
  global $bootscore_updater;

  if (!class_exists('Bootscore_Update_Checker') || !$bootscore_updater) {
    return;
  }

  $plugin_file = dirname(__DIR__) . '/main.php';

  if (!file_exists($plugin_file)) {
    return;
  }

  $plugin_data = get_file_data(
    $plugin_file,
    array('Version' => 'Version')
  );

  $bootscore_updater->register_product(array(
    'type' => 'plugin',
    'slug' => 'bs-example-plugin',
    'current_version' => $plugin_data['Version'] ?? '1.0',
    'file' => plugin_basename($plugin_file),
    'source' => 'custom',
    'info_url' => 'https://files.bootscore.me/plugins/bs-example-plugin-XXXX-XXXX/info.json',
    'name' => 'bs Example Plugin',
  ));
});

info.json (hosted at the info_url above):

{
  "name" : "bs Example Plugin",
  "slug" : "bs-example-plugin",
  "author" : "<a href='https://v7-dev.bootscore.me/'>Bootscore</a>",
  "author_profile" : "https://v7-dev.bootscore.me/",
  "version" : "1.3.0",
  "download_url" : "https://files.bootscore.me/plugins/bs-example-plugin-XXXX-XXXX/bs-example-plugin.zip",
  "requires" : "5.0",
  "tested" : "7.0",
  "requires_php" : "7.4",
  "last_updated" : "2026-06-19",
  "sections" : {
    "description" : "<h4>bs Example Plugin</h4><p>Plugin description here.</p>",
    "installation" : "<h4>Installation</h4><ol><li>In your admin panel, go to Plugins &gt; and click the Add New button.</li><li>Click Upload Plugin and Choose File, then select the Plugin's .zip file. Click Install Now.</li><li>Click Activate to use your new Plugin right away.</li></ol>",
    "changelog" : "<h4>Changelog</h4><p>You can find a detailed changelog in the readme.txt file in the plugin's root directory.</p>"
  },
  "banners" : {
    "low" : "https://files.bootscore.me/plugins/banner_sm.png",
    "high" : "https://files.bootscore.me/plugins/banner_lg.png"
  }
}

Case 2 – Plugin, public GitHub release

Used for free plugins with public repos. No token required.

inc/updater-config.php:

<?php
defined('ABSPATH') || exit;

add_action('init', function() {
  global $bootscore_updater;

  // Ensure the class is loaded
  if (!class_exists('Bootscore_Update_Checker')) {
    $theme_path = get_template_directory() . '/inc/updater/class-update-checker.php';
    if (file_exists($theme_path)) {
      require_once $theme_path;
    }
  }

  // Initialize if needed
  if (!isset($bootscore_updater) && class_exists('Bootscore_Update_Checker')) {
    $bootscore_updater = new Bootscore_Update_Checker(12 * HOUR_IN_SECONDS);
  }

  if (!isset($bootscore_updater) || !class_exists('Bootscore_Update_Checker')) {
    return;
  }

  $plugin_file = dirname(__DIR__) . '/main.php';

  if (!file_exists($plugin_file)) {
    return;
  }

  $plugin_data = get_file_data(
    $plugin_file,
    array('Version' => 'Version')
  );

  $bootscore_updater->register_product(array(
    'type' => 'plugin',
    'slug' => 'bs-example-free-plugin',
    'current_version' => $plugin_data['Version'] ?? '1.0',
    'file' => plugin_basename($plugin_file),
    'source' => 'github',
    'github_repo' => 'bootscore/bs-example-free-plugin',
    'name' => 'bs Example Free Plugin',
  ));
});

No info.json needed. Instead, the plugin’s repo should contain a standard WordPress-format readme.txt at its root:

=== bs Example Free Plugin ===
Stable tag: 1.3.0
Tested up to: 7.0
Requires at least: 5.0
Requires PHP: 7.4
License: MIT License
License URI: license-url

== Description ==
This is the long description. Supports basic Markdown-lite: paragraphs and `- ` bullet lists.

== Installation ==
1. In your admin panel, go to Plugins > and click the Add New button.
2. Click Upload Plugin and Choose File, then select the Plugin's .zip file. Click Install Now.
3. Click Activate to use your new Plugin right away.

== Changelog ==
= 1.3.0 - June 19 2026 =
#### Bugfix
- Fixed a bug
#### Update
- Added icon.svg

= 1.2.0 - May 21 2026 =
#### Updates
- Some update

How it’s resolved, in order:

  1. The GitHub Release’s zip asset — an asset named exactly {slug}.zip is preferred; falls back to the first .zip asset found, then to the standard https://github.com/{repo}/releases/download/{tag}/{slug}.zip pattern.
  2. readme.txt fetched at the exact release tag (not main) provides the Description / Installation / Changelog tabs and the Tested up to / Requires at least / Requires PHP values.
  3. If no readme.txt is found, falls back to parsing the GitHub Release’s own description box, if it contains ## Description / ## Changelog headings.
  4. If neither is available: sensible defaults (5.0 / 7.4 / site’s own WP version).

Version tags: a leading v/V is stripped automatically (v1.3.0 → 1.3.0).

Icons: same as Case 1 — assets/icon.svg bundled in the plugin is used automatically.

Case 3 – Theme, private server (info.json)

Same pattern as Case 1, just 'type' => 'theme' and no file key (unused for themes).

inc/updater/updater-config.php:

<?php
defined('ABSPATH') || exit;

global $bootscore_updater;

if (!class_exists('Bootscore_Update_Checker')) {
  $class_path = get_template_directory() . '/inc/updater/class-update-checker.php';
  if (file_exists($class_path)) {
    require_once $class_path;
  }
}

if (!isset($bootscore_updater) && class_exists('Bootscore_Update_Checker')) {
  $bootscore_updater = new Bootscore_Update_Checker(12 * HOUR_IN_SECONDS);
}

if (!isset($bootscore_updater) || !class_exists('Bootscore_Update_Checker')) {
  return;
}

add_action('init', function() {
  global $bootscore_updater;

  if (!class_exists('Bootscore_Update_Checker') || !$bootscore_updater) {
    return;
  }

  $theme = wp_get_theme('bootscore');

  $bootscore_updater->register_product(array(
    'type' => 'theme',
    'slug' => 'bootscore',
    'current_version' => $theme->get('Version') ?: '1.0.0',
    'source' => 'custom',
    'info_url' => 'https://files.bootscore.me/themes/bootscore/info.json',
    'name' => 'Bootscore',
  ));
});

inc/theme-setup.php / functions.php:

// Load theme's own update configuration
require_once get_template_directory() . '/inc/updater/updater-config.php';

This one line is the only thing that needs to run unconditionally — everything else (class loading, instantiation) is self-contained inside updater-config.php.

info.json: same structure as Case 1, adjusted for the theme (theme screenshots can be used as banners if desired).

Case 4 – Theme, public GitHub release

inc/updater/updater-config.php:

<?php
defined('ABSPATH') || exit;

add_action('init', function() {
  global $bootscore_updater;

  if (!class_exists('Bootscore_Update_Checker')) {
    $class_path = get_template_directory() . '/inc/updater/class-update-checker.php';
    if (file_exists($class_path)) {
      require_once $class_path;
    }
  }

  if (!isset($bootscore_updater) && class_exists('Bootscore_Update_Checker')) {
    $bootscore_updater = new Bootscore_Update_Checker(12 * HOUR_IN_SECONDS);
  }

  if (!isset($bootscore_updater) || !class_exists('Bootscore_Update_Checker')) {
    return;
  }

  $theme = wp_get_theme('bootscore');

  $bootscore_updater->register_product(array(
    'type' => 'theme',
    'slug' => 'bootscore',
    'current_version' => $theme->get('Version') ?: '1.0.0',
    'source' => 'github',
    'github_repo' => 'bootscore/bootscore',
    'name' => 'Bootscore',
  ));
});
// functions.php
require_once get_template_directory() . '/inc/updater/updater-config.php';

No info.json needed – same readme.txt-first resolution as Case 2 applies. The theme’s own screenshot.png (standard WordPress theme convention) is used automatically for the theme details screen; no separate icon/banner config needed.

Cache & clearing

Remote data is cached in WordPress transients for the duration passed to the constructor (12 * HOUR_IN_SECONDS in all examples above):

  • Custom server: bootscore_custom_{slug}
  • GitHub: bootscore_github_{slug}

Cache is cleared automatically after any update is installed (upgrader_process_complete). To force a fresh check manually during development/testing:

wp transient delete bootscore_custom_bs-example-plugin
wp transient delete bootscore_github_bs-example-free-plugin

Note: clicking “Check Again” on the WordPress Updates screen does not clear these transients — only a genuine plugin/theme install does. Delete the transient manually when testing changes to info.json or a GitHub release/readme.

Migration note (PUC → this updater)

This library and the PluginUpdateChecker (PUC) library can coexist safely during migration, since:

  • class-update-checker.php only defines a class; it does nothing until instantiated
  • Each system only writes to the transient/plugins_api response entries for the products it has registered — there’s no shared state for products registered with only one system

Do not register the same plugin with both systems at the same time – migrate one plugin fully (add this updater’s config, remove the old PUC init) before moving to the next.