';
@@ -429,6 +745,13 @@ class UPServ {
$this->display_tabs();
}
+ /**
+ * Render help page
+ *
+ * Displays the plugin's help documentation.
+ *
+ * @since 1.0.0
+ */
public function help_page() {
if ( ! current_user_can( 'manage_options' ) ) {
@@ -453,6 +776,13 @@ class UPServ {
* Protected methods
*******************************************************************/
+ /**
+ * Display navigation tabs
+ *
+ * Renders the tab navigation for admin pages.
+ *
+ * @since 1.0.0
+ */
protected function display_tabs() {
$states = $this->get_tab_states();
$state = array_filter( $states );
@@ -463,6 +793,13 @@ class UPServ {
$state = array_keys( $state );
$state = reset( $state );
+ /**
+ * Filter the admin tab links.
+ *
+ * @param array $links The existing tab links
+ * @return array The modified tab links
+ * @since 1.0.0
+ */
$links = apply_filters( 'upserv_admin_tab_links', array() );
upserv_get_admin_template(
@@ -475,20 +812,51 @@ class UPServ {
);
}
+ /**
+ * Get tab states
+ *
+ * Determines which tab is currently active.
+ *
+ * @return array Tab states
+ * @since 1.0.0
+ */
protected function get_tab_states() {
$page = sanitize_text_field( wp_unslash( filter_input( INPUT_GET, 'page' ) ) );
$states = array();
if ( 0 === strpos( $page, 'upserv-page' ) ) {
+ /**
+ * Filter the admin tab states.
+ *
+ * @param array $states The existing tab states
+ * @param string $page The current page
+ * @return array The modified tab states
+ * @since 1.0.0
+ */
$states = apply_filters( 'upserv_admin_tab_states', $states, $page );
}
return $states;
}
+ /**
+ * Enqueue styles
+ *
+ * Loads stylesheets for the admin interface.
+ *
+ * @param array $styles Styles to enqueue
+ * @return array Enqueued styles
+ * @since 1.0.0
+ */
protected function enqueue_styles( $styles ) {
- $filter = 'upserv_admin_styles';
- $styles = apply_filters( $filter, $styles );
+ /**
+ * Filter the admin styles to be enqueued.
+ *
+ * @param array $styles Array of styles to be enqueued
+ * @return array Modified array of styles
+ * @since 1.0.0
+ */
+ $styles = apply_filters( 'upserv_admin_styles', $styles );
if ( ! empty( $styles ) ) {
@@ -516,9 +884,24 @@ class UPServ {
return $styles;
}
+ /**
+ * Enqueue scripts
+ *
+ * Loads JavaScript files for the admin interface.
+ *
+ * @param array $scripts Scripts to enqueue
+ * @return array Enqueued scripts
+ * @since 1.0.0
+ */
protected function enqueue_scripts( $scripts ) {
- $filter = 'upserv_admin_scripts';
- $scripts = apply_filters( $filter, $scripts );
+ /**
+ * Filter the admin scripts to be enqueued.
+ *
+ * @param array $scripts Array of scripts to be enqueued
+ * @return array Modified array of scripts
+ * @since 1.0.0
+ */
+ $scripts = apply_filters( 'upserv_admin_scripts', $scripts );
if ( ! empty( $scripts ) ) {
diff --git a/inc/class-utils.php b/inc/class-utils.php
index 67f3b73..c84f04d 100644
--- a/inc/class-utils.php
+++ b/inc/class-utils.php
@@ -10,15 +10,26 @@ if ( ! defined( 'ABSPATH' ) ) {
* Class Utils
*
* @package Anyape\Utils
+ * @since 1.0.0
*/
class Utils {
- // JSON options
+ /**
+ * JSON encoding options
+ *
+ * @var int
+ * @since 1.0.0
+ */
const JSON_OPTIONS = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE;
/**
- * @param string $message
- * @param string $prefix
+ * Log a message to PHP error log
+ *
+ * Adds class/method context information to the log message.
+ *
+ * @param string $message Message to log
+ * @param string $prefix Optional prefix for the log message
+ * @since 1.0.0
*/
public static function php_log( $message = '', $prefix = '' ) {
$prefix = $prefix ? ' ' . $prefix . ' => ' : ' => ';
@@ -33,10 +44,14 @@ class Utils {
}
/**
- * @param string $ip
- * @param string $range
+ * Check if IP address is within CIDR range
*
- * @return bool
+ * Validates whether a given IP address falls within the specified CIDR range.
+ *
+ * @param string $ip IP address to check
+ * @param string $range CIDR range notation (e.g., 192.168.1.0/24)
+ * @return bool True if IP is in range, false otherwise
+ * @since 1.0.0
*/
public static function cidr_match( $ip, $range ) {
list ( $subnet, $bits ) = explode( '/', $range );
@@ -54,12 +69,16 @@ class Utils {
}
/**
- * @param array $_array
- * @param string $path
- * @param null $value
- * @param bool $update
+ * Access or update nested array using path notation
*
- * @return mixed|null
+ * Gets or sets a value in a nested array using a path string with / as separator.
+ *
+ * @param array $_array Reference to the array to access
+ * @param string $path Path notation to the nested element (e.g., 'parent/child/item')
+ * @param mixed $value Optional value to set if updating
+ * @param bool $update Whether to update the array (true) or just read (false)
+ * @return mixed|null Retrieved value or null if path doesn't exist
+ * @since 1.0.0
*/
public static function access_nested_array( &$_array, $path, $value = null, $update = false ) {
$keys = explode( '/', $path );
@@ -87,10 +106,13 @@ class Utils {
}
/**
- * @param string $path
- * @param string $regex
+ * Check if URL subpath matches a regex pattern
*
- * @return int|null
+ * Tests if the first segment of the current request URI matches the provided regex.
+ *
+ * @param string $regex Regular expression to match against the first path segment
+ * @return int|null 1 if match found, 0 if no match, null if host couldn't be determined
+ * @since 1.0.0
*/
public static function is_url_subpath_match( $regex ) {
$host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : false;
@@ -111,10 +133,12 @@ class Utils {
}
/**
- * @param string $path
- * @param string $regex
+ * Get time elapsed since request start
*
- * @return int|null
+ * Calculates the time elapsed since the request started in seconds.
+ *
+ * @return string|null Time elapsed in seconds with 3 decimal precision, or null if request time not available
+ * @since 1.0.0
*/
public static function get_time_elapsed() {
@@ -132,10 +156,12 @@ class Utils {
}
/**
- * @param string $path
- * @param string $regex
+ * Get remote IP address
*
- * @return int|null
+ * Safely retrieves the remote IP address of the client.
+ *
+ * @return string IP address of the client or '0.0.0.0' if not available or invalid
+ * @since 1.0.0
*/
public static function get_remote_ip() {
@@ -152,6 +178,15 @@ class Utils {
return $ip;
}
+ /**
+ * Get human-readable status string
+ *
+ * Converts a status code to a localized human-readable string.
+ *
+ * @param string $status Status code to convert
+ * @return string Localized human-readable status string
+ * @since 1.0.0
+ */
public static function get_status_string( $status ) {
switch ( $status ) {
case 'pending':
diff --git a/inc/server/update/class-update-server.php b/inc/server/update/class-update-server.php
index af3cb4c..d1f1675 100644
--- a/inc/server/update/class-update-server.php
+++ b/inc/server/update/class-update-server.php
@@ -288,6 +288,13 @@ class Update_Server {
}
}
+ /**
+ * Fires after pre-filtering package information
+ *
+ * Allows developers to perform actions after the package information has been initially filtered.
+ *
+ * @param array $info The package information array after pre-filtering
+ */
do_action( 'upserv_pre_filter_package_info', $info );
return $info;
@@ -314,6 +321,13 @@ class Update_Server {
*/
$info = apply_filters( 'upserv_filter_package_info', $info, $this->filter_packages_file_content );
+ /**
+ * Fires after filtering package information
+ *
+ * Allows developers to perform actions after the package information has been filtered.
+ *
+ * @param array $info The package information array after filtering
+ */
do_action( 'upserv_filter_package_info', $info );
return $info;
@@ -365,6 +379,13 @@ class Update_Server {
) ) {
$this->remove_package( $safe_slug, true );
+ /**
+ * Fires when a remote package download is aborted
+ *
+ * @param string $safe_slug The sanitized package slug
+ * @param string $type The package type
+ * @param array $info The package information
+ */
do_action( 'upserv_download_remote_package_aborted', $safe_slug, $this->type, $info );
return $info;
@@ -375,6 +396,13 @@ class Update_Server {
$package = $this->download_remote_package( $info['download_url'] );
+ /**
+ * Fires after a remote package has been downloaded
+ *
+ * @param string $package Path to the downloaded package file
+ * @param string $type The package type
+ * @param string $safe_slug The sanitized package slug
+ */
do_action( 'upserv_downloaded_remote_package', $package, $info['type'], $safe_slug );
$package_manager = new Zip_Package_Manager(
@@ -385,6 +413,13 @@ class Update_Server {
);
$local_ready = $package_manager->clean_package();
+ /**
+ * Fires after a remote package has been saved to local storage
+ *
+ * @param bool $local_ready Whether the package was successfully saved locally
+ * @param string $type The package type
+ * @param string $safe_slug The sanitized package slug
+ */
do_action(
'upserv_saved_remote_package_to_local',
$local_ready,
@@ -429,6 +464,11 @@ class Update_Server {
* @since 1.0.0
*/
public function check_remote_package_update( $slug ) {
+ /**
+ * Fires before checking if a remote package needs to be updated
+ *
+ * @param string $slug The package slug
+ */
do_action( 'upserv_check_remote_update', $slug );
$needs_update = true;
@@ -491,6 +531,13 @@ class Update_Server {
$needs_update = null;
}
+ /**
+ * Fires after checking if a remote package needs to be updated
+ *
+ * @param bool|null $needs_update Whether the package needs to be updated
+ * @param string $type The package type
+ * @param string $slug The package slug
+ */
do_action( 'upserv_checked_remote_package_update', $needs_update, $this->type, $slug );
return $needs_update;
@@ -552,6 +599,13 @@ class Update_Server {
$this->cache->clear( $cache_key );
}
+ /**
+ * Fires after a package has been removed
+ *
+ * @param bool $result Whether the package was successfully removed
+ * @param string $type The package type
+ * @param string $slug The package slug
+ */
do_action( 'upserv_removed_package', $result, $type, $slug );
self::unlock_update_from_remote( $slug );
@@ -709,6 +763,11 @@ class Update_Server {
* @since 1.0.0
*/
protected function action_download( Request $request ) {
+ /**
+ * Fires when processing a download action
+ *
+ * @param Request $request The current request object
+ */
do_action( 'upserv_update_server_action_download', $request );
/**
@@ -846,6 +905,13 @@ class Update_Server {
}
if ( null === $cached_value ) {
+ /**
+ * Fires when no cached package metadata is available
+ *
+ * @param string $safe_slug The sanitized package slug
+ * @param string $filename The local filename path
+ * @param Cache $cache The cache instance
+ */
do_action( 'upserv_find_package_no_cache', $safe_slug, $filename, $this->cache );
}
diff --git a/inc/server/update/class-zip-metadata-parser.php b/inc/server/update/class-zip-metadata-parser.php
index fbab5d0..74f4c52 100644
--- a/inc/server/update/class-zip-metadata-parser.php
+++ b/inc/server/update/class-zip-metadata-parser.php
@@ -14,14 +14,24 @@ use Anyape\UpdatePulse\Package_Parser\Parser;
class Zip_Metadata_Parser {
/**
- * @var int $cache_time How long the package metadata should be cached in seconds.
- * Defaults to 1 week ( 7 * 24 * 60 * 60 ).
- */
+ * Cache time
+ *
+ * How long the package metadata should be cached in seconds.
+ * Defaults to 1 week ( 7 * 24 * 60 * 60 ).
+ *
+ * @var int
+ * @since 1.0.0
+ */
public static $cache_time = 604800;
/**
- * @var array Package PHP header mapping, i.e. which tags to add to the metadata under which array key
- */
+ * Header map
+ *
+ * Package PHP header mapping, i.e. which tags to add to the metadata under which array key.
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected $header_map = array(
'Name' => 'name',
'Version' => 'version',
@@ -36,49 +46,76 @@ class Zip_Metadata_Parser {
'Depends' => 'depends',
'Provides' => 'provides',
);
-
/**
- * @var array Plugin readme file mapping, i.e. which tags to add to the metadata
- */
+ * Readme map
+ *
+ * Plugin readme file mapping, i.e. which tags to add to the metadata.
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected $readme_map = array(
'requires',
'tested',
'requires_php',
);
-
/**
- * @var array Package info as retrieved by the parser
- */
+ * Package info
+ *
+ * Package info as retrieved by the parser.
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected $package_info;
-
/**
- * @var string Path to the Zip archive that contains the package.
- */
+ * Filename
+ *
+ * Path to the Zip archive that contains the package.
+ *
+ * @var string
+ * @since 1.0.0
+ */
protected $filename;
-
/**
- * @var string Package slug.
- */
+ * Slug
+ *
+ * Package slug.
+ *
+ * @var string
+ * @since 1.0.0
+ */
protected $slug;
-
/**
- * @var Cache object.
- */
+ * Cache
+ *
+ * Cache object.
+ *
+ * @var object
+ * @since 1.0.0
+ */
protected $cache;
-
/**
- * @var array Package metadata in a format suitable for the update checker.
- */
+ * Metadata
+ *
+ * Package metadata in a format suitable for the update checker.
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected $metadata;
/**
- * Get the metadata from a zip file.
- *
- * @param string $slug
- * @param string $filename
- * @param $cache
- */
+ * Constructor
+ *
+ * Get the metadata from a zip file.
+ *
+ * @param string $slug Package slug.
+ * @param string $filename Path to the Zip archive.
+ * @param object $cache Cache object.
+ * @since 1.0.0
+ */
public function __construct( $slug, $filename, $cache = null ) {
$this->slug = $slug;
$this->filename = $filename;
@@ -88,8 +125,15 @@ class Zip_Metadata_Parser {
}
/**
- * Build the cache key (cache filename) for a file
- */
+ * Build cache key
+ *
+ * Build the cache key (cache filename) for a file.
+ *
+ * @param string $slug Package slug.
+ * @param string $filename Path to the Zip archive.
+ * @return string The cache key.
+ * @since 1.0.0
+ */
public static function build_cache_key( $slug, $filename ) {
$cache_key = $slug . '-b64-';
@@ -97,6 +141,15 @@ class Zip_Metadata_Parser {
$cache_key .= md5( $filename . '|' . filesize( $filename ) . '|' . filemtime( $filename ) );
}
+ /**
+ * Filter the cache key used for storing package metadata.
+ *
+ * @param string $cache_key The generated cache key for the package.
+ * @param string $slug The package slug.
+ * @param string $filename The path to the Zip archive.
+ * @return string The filtered cache key.
+ * @since 1.0.0
+ */
return apply_filters(
'upserv_zip_metadata_parser_cache_key',
$cache_key,
@@ -106,20 +159,27 @@ class Zip_Metadata_Parser {
}
/**
- * Get metadata.
- *
- * @return array
- */
+ * Get metadata
+ *
+ * Get the package metadata.
+ *
+ * @return array Package metadata.
+ * @since 1.0.0
+ */
public function get() {
return $this->metadata;
}
/**
- * Load metadata information from a cache or create it.
- *
- * We'll try to load processed metadata from the cache first (if available), and if that
- * fails we'll extract package details from the specified Zip file.
- */
+ * Set metadata
+ *
+ * Load metadata information from a cache or create it.
+ *
+ * We'll try to load processed metadata from the cache first (if available), and if that
+ * fails we'll extract package details from the specified Zip file.
+ *
+ * @since 1.0.0
+ */
protected function set_metadata() {
$cache_key = self::build_cache_key( $this->slug, $this->filename );
@@ -151,11 +211,14 @@ class Zip_Metadata_Parser {
}
/**
- * Extract package headers and readme contents from a ZIP file and convert them
- * into a structure compatible with the custom update checker.
- *
- * @throws Invalid_Package_Exception if the input file can't be parsed as a package.
- */
+ * Extract metadata
+ *
+ * Extract package headers and readme contents from a ZIP file and convert them
+ * into a structure compatible with the custom update checker.
+ *
+ * @throws Invalid_Package_Exception if the input file can't be parsed as a package.
+ * @since 1.0.0
+ */
protected function extract_metadata() {
$this->package_info = Parser::parse_package( $this->filename, true );
@@ -177,8 +240,12 @@ class Zip_Metadata_Parser {
}
/**
- * Extract relevant metadata from the package header information
- */
+ * Set info from header
+ *
+ * Extract relevant metadata from the package header information.
+ *
+ * @since 1.0.0
+ */
protected function set_info_from_header() {
if ( isset( $this->package_info['header'] ) && ! empty( $this->package_info['header'] ) ) {
@@ -188,8 +255,12 @@ class Zip_Metadata_Parser {
}
/**
- * Extract relevant metadata from the plugin readme
- */
+ * Set info from readme
+ *
+ * Extract relevant metadata from the plugin readme.
+ *
+ * @since 1.0.0
+ */
protected function set_info_from_readme() {
if ( ! empty( $this->package_info['readme'] ) ) {
@@ -202,15 +273,18 @@ class Zip_Metadata_Parser {
}
/**
- * Extract selected metadata from the retrieved package info
- *
- * @see http://codex.wordpress.org/File_Header
- * @see https://wordpress.org/plugins/about/readme.txt
- *
- * @param array $input The package info sub-array to use to retrieve the info from
- * @param array $map The key mapping for that sub-array where the key is the key as used in the
- * input array and the value is the key to use for the output array
- */
+ * Set mapped fields
+ *
+ * Extract selected metadata from the retrieved package info.
+ *
+ * @see http://codex.wordpress.org/File_Header
+ * @see https://wordpress.org/plugins/about/readme.txt
+ *
+ * @param array $input The package info sub-array to use to retrieve the info from.
+ * @param array $map The key mapping for that sub-array where the key is the key as used in the
+ * input array and the value is the key to use for the output array.
+ * @since 1.0.0
+ */
protected function set_mapped_fields( $input, $map ) {
foreach ( $map as $field_key => $meta_key ) {
@@ -222,12 +296,16 @@ class Zip_Metadata_Parser {
}
/**
- * Determine the details url for themes
- *
- * Theme metadata should include a "details_url" that specifies the page to display
- * when the user clicks "View version x.y.z details". If the developer didn't provide
- * it by setting the "Details URI" header, we'll default to the theme homepage ( "Theme URI" ).
- */
+ * Set theme details URL
+ *
+ * Determine the details url for themes.
+ *
+ * Theme metadata should include a "details_url" that specifies the page to display
+ * when the user clicks "View version x.y.z details". If the developer didn't provide
+ * it by setting the "Details URI" header, we'll default to the theme homepage ( "Theme URI" ).
+ *
+ * @since 1.0.0
+ */
protected function set_theme_details_url() {
if (
@@ -239,10 +317,13 @@ class Zip_Metadata_Parser {
}
/**
- * Extract the texual information sections from a readme file
- *
- * @see https://wordpress.org/plugins/about/readme.txt
- */
+ * Set readme sections
+ *
+ * Extract the texual information sections from a readme file.
+ *
+ * @see https://wordpress.org/plugins/about/readme.txt
+ * @since 1.0.0
+ */
protected function set_readme_sections() {
if (
@@ -263,10 +344,13 @@ class Zip_Metadata_Parser {
}
/**
- * Extract the upgrade notice for the current version from a readme file
- *
- * @see https://wordpress.org/plugins/about/readme.txt
- */
+ * Set readme upgrade notice
+ *
+ * Extract the upgrade notice for the current version from a readme file.
+ *
+ * @see https://wordpress.org/plugins/about/readme.txt
+ * @since 1.0.0
+ */
protected function set_readme_upgrade_notice() {
//Check if we have an upgrade notice for this version
@@ -282,8 +366,12 @@ class Zip_Metadata_Parser {
}
/**
- * Add last update date to the metadata ; this is tied to the version
- */
+ * Set last update date
+ *
+ * Add last update date to the metadata; this is tied to the version.
+ *
+ * @since 1.0.0
+ */
protected function set_last_update_date() {
if ( isset( $this->metadata['last_updated'] ) ) {
@@ -309,10 +397,24 @@ class Zip_Metadata_Parser {
$this->metadata['last_updated'] = $meta['version_time'];
}
+ /**
+ * Set type
+ *
+ * Set the package type in the metadata.
+ *
+ * @since 1.0.0
+ */
protected function set_type() {
$this->metadata['type'] = $this->package_info['type'];
}
+ /**
+ * Set slug
+ *
+ * Set the package slug in the metadata.
+ *
+ * @since 1.0.0
+ */
protected function set_slug() {
if ( 'plugin' === $this->package_info['type'] ) {
@@ -327,8 +429,12 @@ class Zip_Metadata_Parser {
}
/**
- * Extract icons and banners info for plugins
- */
+ * Set info from assets
+ *
+ * Extract icons and banners info for plugins.
+ *
+ * @since 1.0.0
+ */
protected function set_info_from_assets() {
if ( ! empty( $this->package_info['extra'] ) ) {
diff --git a/inc/table/class-licenses-table.php b/inc/table/class-licenses-table.php
index ded7149..3c76dee 100644
--- a/inc/table/class-licenses-table.php
+++ b/inc/table/class-licenses-table.php
@@ -9,13 +9,43 @@ if ( ! defined( 'ABSPATH' ) ) {
use WP_List_Table;
use Anyape\Utils\Utils;
+/**
+ * Licenses table class
+ *
+ * @since 1.0.0
+ */
class Licenses_Table extends WP_List_Table {
+ /**
+ * Bulk action error
+ *
+ * @var mixed
+ * @since 1.0.0
+ */
public $bulk_action_error;
+ /**
+ * Nonce action name
+ *
+ * @var string
+ * @since 1.0.0
+ */
public $nonce_action;
+ /**
+ * Table rows data
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected $rows;
+ /**
+ * Constructor
+ *
+ * Sets up the table properties and hooks
+ *
+ * @since 1.0.0
+ */
public function __construct() {
parent::__construct(
array(
@@ -34,6 +64,14 @@ class Licenses_Table extends WP_List_Table {
// Overrides ---------------------------------------------------
+ /**
+ * Get table columns
+ *
+ * Define the columns for the licenses table
+ *
+ * @return array The table columns
+ * @since 1.0.0
+ */
public function get_columns() {
return array(
'cb' => '',
@@ -48,10 +86,28 @@ class Licenses_Table extends WP_List_Table {
);
}
+ /**
+ * Default column rendering
+ *
+ * Default handler for displaying column data
+ *
+ * @param array $item The row item
+ * @param string $column_name The column name
+ * @return mixed The column value
+ * @since 1.0.0
+ */
public function column_default( $item, $column_name ) {
return $item[ $column_name ];
}
+ /**
+ * Get sortable columns
+ *
+ * Define which columns can be sorted
+ *
+ * @return array The sortable columns
+ * @since 1.0.0
+ */
public function get_sortable_columns() {
return array(
'col_status' => array( 'status', false ),
@@ -64,6 +120,13 @@ class Licenses_Table extends WP_List_Table {
);
}
+ /**
+ * Prepare table items
+ *
+ * Query the database and set up the items for display
+ *
+ * @since 1.0.0
+ */
public function prepare_items() {
global $wpdb;
@@ -174,6 +237,13 @@ class Licenses_Table extends WP_List_Table {
$this->items = $items;
}
+ /**
+ * Display table rows
+ *
+ * Output the HTML for each row in the table
+ *
+ * @since 1.0.0
+ */
public function display_rows() {
$records = $this->items;
$table = $this;
@@ -214,6 +284,14 @@ class Licenses_Table extends WP_List_Table {
// Misc. -------------------------------------------------------
+ /**
+ * Set table rows
+ *
+ * Set the row data for the table
+ *
+ * @param array $rows The rows data
+ * @since 1.0.0
+ */
public function set_rows( $rows ) {
$this->rows = $rows;
}
@@ -224,6 +302,16 @@ class Licenses_Table extends WP_List_Table {
// Overrides ---------------------------------------------------
+ /**
+ * Generate row actions
+ *
+ * Create action links for each row
+ *
+ * @param array $actions The actions array
+ * @param bool $always_visible Whether actions should be always visible
+ * @return string HTML for the row actions
+ * @since 1.0.0
+ */
protected function row_actions( $actions, $always_visible = false ) {
$action_count = count( $actions );
$i = 0;
@@ -248,6 +336,14 @@ class Licenses_Table extends WP_List_Table {
return $out;
}
+ /**
+ * Display extra tablenav
+ *
+ * Add additional controls above or below the table
+ *
+ * @param string $which The location ('top' or 'bottom')
+ * @since 1.0.0
+ */
protected function extra_tablenav( $which ) {
if ( 'bottom' === $which ) {
@@ -255,6 +351,14 @@ class Licenses_Table extends WP_List_Table {
}
}
+ /**
+ * Get bulk actions
+ *
+ * Define available bulk actions for the table
+ *
+ * @return array The available bulk actions
+ * @since 1.0.0
+ */
protected function get_bulk_actions() {
$actions = array(
'pending' => __( 'Set to Pending', 'updatepulse-server' ),
@@ -268,6 +372,14 @@ class Licenses_Table extends WP_List_Table {
return $actions;
}
+ /**
+ * Get table classes
+ *
+ * Define CSS classes for the table
+ *
+ * @return array The table CSS classes
+ * @since 1.0.0
+ */
protected function get_table_classes() {
$mode = get_user_setting( 'posts_list_mode', 'list' );
diff --git a/inc/table/class-packages-table.php b/inc/table/class-packages-table.php
index b2c5689..0ebe2bd 100644
--- a/inc/table/class-packages-table.php
+++ b/inc/table/class-packages-table.php
@@ -9,14 +9,51 @@ if ( ! defined( 'ABSPATH' ) ) {
use WP_List_Table;
use DateTimeZone;
+/**
+ * Packages Table class
+ *
+ * Manages the display of packages in the admin area
+ *
+ * @since 1.0.0
+ */
class Packages_Table extends WP_List_Table {
+ /**
+ * Bulk action error message
+ *
+ * @var string|null
+ * @since 1.0.0
+ */
public $bulk_action_error;
+ /**
+ * Nonce action name
+ *
+ * @var string
+ * @since 1.0.0
+ */
public $nonce_action;
+ /**
+ * Table rows data
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected $rows;
+ /**
+ * Package manager instance
+ *
+ * @var object
+ * @since 1.0.0
+ */
protected $package_manager;
+ /**
+ * Constructor
+ *
+ * @param object $package_manager The package manager instance
+ * @since 1.0.0
+ */
public function __construct( $package_manager ) {
parent::__construct(
array(
@@ -36,7 +73,22 @@ class Packages_Table extends WP_List_Table {
// Overrides ---------------------------------------------------
+ /**
+ * Get table columns
+ *
+ * Define the columns shown in the packages table.
+ *
+ * @return array Table columns
+ * @since 1.0.0
+ */
public function get_columns() {
+ /**
+ * Filter the columns shown in the packages table.
+ *
+ * @param array $columns The default columns for the packages table
+ * @return array The filtered columns
+ * @since 1.0.0
+ */
$columns = apply_filters(
'upserv_packages_table_columns',
array(
@@ -54,11 +106,36 @@ class Packages_Table extends WP_List_Table {
return $columns;
}
+ /**
+ * Default column renderer
+ *
+ * Default handler for columns without specific renderers.
+ *
+ * @param array $item The current row item
+ * @param string $column_name The current column name
+ * @return mixed Column content
+ * @since 1.0.0
+ */
public function column_default( $item, $column_name ) {
return $item[ $column_name ];
}
+ /**
+ * Get sortable columns
+ *
+ * Define which columns can be sorted in the table.
+ *
+ * @return array Sortable columns configuration
+ * @since 1.0.0
+ */
public function get_sortable_columns() {
+ /**
+ * Filter the sortable columns in the packages table.
+ *
+ * @param array $columns The default sortable columns
+ * @return array The filtered sortable columns
+ * @since 1.0.0
+ */
$columns = apply_filters(
'upserv_packages_table_sortable_columns',
array(
@@ -75,6 +152,13 @@ class Packages_Table extends WP_List_Table {
return $columns;
}
+ /**
+ * Prepare table items
+ *
+ * Process data for table display including pagination.
+ *
+ * @since 1.0.0
+ */
public function prepare_items() {
$total_items = count( $this->rows );
$offset = 0;
@@ -111,7 +195,13 @@ class Packages_Table extends WP_List_Table {
uasort( $this->items, array( &$this, 'uasort_reorder' ) );
}
-
+ /**
+ * Display table rows
+ *
+ * Render the rows of the packages table.
+ *
+ * @since 1.0.0
+ */
public function display_rows() {
$records = $this->items;
$table = $this;
@@ -206,10 +296,28 @@ class Packages_Table extends WP_List_Table {
// Misc. -------------------------------------------------------
+ /**
+ * Set table rows
+ *
+ * Set the rows data for the table.
+ *
+ * @param array $rows Table rows data
+ * @since 1.0.0
+ */
public function set_rows( $rows ) {
$this->rows = $rows;
}
+ /**
+ * Custom sorting function
+ *
+ * Sort table items based on request parameters.
+ *
+ * @param array $a First item to compare
+ * @param array $b Second item to compare
+ * @return int Comparison result
+ * @since 1.0.0
+ */
public function uasort_reorder( $a, $b ) {
$order_by = ! empty( $_REQUEST['orderby'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) : 'name'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order = ! empty( $_REQUEST['order'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) : 'asc'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
@@ -242,6 +350,14 @@ class Packages_Table extends WP_List_Table {
// Overrides ---------------------------------------------------
+ /**
+ * Display extra table navigation
+ *
+ * Add additional controls above or below the table.
+ *
+ * @param string $which Position ('top' or 'bottom')
+ * @since 1.0.0
+ */
protected function extra_tablenav( $which ) {
if ( 'top' === $which ) {
@@ -258,6 +374,14 @@ class Packages_Table extends WP_List_Table {
}
}
+ /**
+ * Get table CSS classes
+ *
+ * Define the CSS classes for the table.
+ *
+ * @return array Table CSS classes
+ * @since 1.0.0
+ */
protected function get_table_classes() {
$mode = get_user_setting( 'posts_list_mode', 'list' );
$mode_class = esc_attr( 'table-view-' . $mode );
@@ -265,7 +389,22 @@ class Packages_Table extends WP_List_Table {
return array( 'widefat', 'striped', $mode_class, $this->_args['plural'] );
}
+ /**
+ * Get bulk actions
+ *
+ * Define available bulk actions for the table.
+ *
+ * @return array Bulk actions
+ * @since 1.0.0
+ */
protected function get_bulk_actions() {
+ /**
+ * Filter the bulk actions available in the packages table.
+ *
+ * @param array $actions The default bulk actions
+ * @return array The filtered bulk actions
+ * @since 1.0.0
+ */
$actions = apply_filters(
'upserv_packages_table_bulk_actions',
array(
@@ -277,6 +416,15 @@ class Packages_Table extends WP_List_Table {
return $actions;
}
+ /**
+ * Get VCS icon class
+ *
+ * Get the appropriate icon class for a VCS provider.
+ *
+ * @param array $vcs_config VCS configuration
+ * @return string CSS class for the VCS icon
+ * @since 1.0.0
+ */
protected function get_vcs_class( $vcs_config ) {
switch ( $vcs_config['type'] ) {
diff --git a/readme.txt b/readme.txt
index 4e90486..00a9ff0 100644
--- a/readme.txt
+++ b/readme.txt
@@ -3,7 +3,7 @@ Contributors: frogerme
Tags: Plugin updates, Theme updates, WordPress updates, License
Requires at least: 6.7
Tested up to: 6.7
-Stable tag: 1.0.6
+Stable tag: 1.0.7
Requires PHP: 8.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -119,6 +119,9 @@ This section describes how to install the plugin and get it working.
== Changelog ==
+= 1.0.7 =
+* Full documentation of all classes and functions
+
= 1.0.6 =
* Fix webhook payload handling (thanks @eHtmlu on github)
* Fix webhook payload scheduling (thanks @BabaYaga0179 on github)
diff --git a/updatepulse-server.php b/updatepulse-server.php
index ec7fbd5..c8ffba8 100644
--- a/updatepulse-server.php
+++ b/updatepulse-server.php
@@ -3,7 +3,7 @@
* Plugin Name: UpdatePulse Server
* Plugin URI: https://github.com/anyape/updatepulse-server/
* Description: Run your own update server.
- * Version: 1.0.6
+ * Version: 1.0.7
* Author: Alexandre Froger
* Author URI: https://froger.me/
* License: GPLv2 or later
From 4bb83b483723b4a291e621a38ad0c8303d2f5c62 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Thu, 10 Apr 2025 13:14:29 +0800
Subject: [PATCH 02/14] Fix scheduled mode package overrides
---
inc/manager/class-package-manager.php | 47 +++++++++++++-------
inc/manager/class-remote-sources-manager.php | 2 +-
inc/server/update/class-update-server.php | 2 +-
readme.txt | 14 +++++-
updatepulse-server.php | 2 +-
5 files changed, 48 insertions(+), 19 deletions(-)
diff --git a/inc/manager/class-package-manager.php b/inc/manager/class-package-manager.php
index fdc28fe..b8b686a 100644
--- a/inc/manager/class-package-manager.php
+++ b/inc/manager/class-package-manager.php
@@ -18,6 +18,7 @@ use Anyape\UpdatePulse\Server\Server\Update\Package;
use Anyape\UpdatePulse\Server\Manager\Data_Manager;
use Anyape\UpdatePulse\Server\API\Package_API;
use Anyape\UpdatePulse\Server\Table\Packages_Table;
+use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
use Anyape\Utils\Utils;
/**
@@ -445,6 +446,25 @@ class Package_Manager {
upserv_set_package_metadata( $slug, $meta );
$result = upserv_download_remote_package( $slug, null );
+
+ if ( wp_cache_get( 'upserv_download_remote_package_aborted', 'updatepulse-server' ) ) {
+ $vcs_config = upserv_get_package_vcs_config( $slug );
+ $error = isset( $vcs_config['filter_packages'] ) && $vcs_config['filter_packages'] ?
+ new WP_Error(
+ __METHOD__,
+ __( 'Error - could not get remote package. The package was filtered out because it is not linked to this server.', 'updatepulse-server' )
+ ) :
+ new WP_Error(
+ __METHOD__,
+ __( 'Error - could not get remote package. The package was found and is valid, but the download was aborted. Please check the package is satisfying the requirements for this server.', 'updatepulse-server' )
+ );
+
+ wp_cache_delete( 'upserv_download_remote_package_aborted', 'updatepulse-server' );
+ }
+
+ if ( ! $result || isset( $result['abort_request'] ) ) {
+ upserv_set_package_metadata( $slug, null );
+ }
} else {
$error = new WP_Error(
__METHOD__,
@@ -458,21 +478,6 @@ class Package_Manager {
);
}
- if ( wp_cache_get( 'upserv_download_remote_package_aborted', 'updatepulse-server' ) ) {
- $vcs_config = upserv_get_package_vcs_config( $slug );
- $error = isset( $vcs_config['filter_packages'] ) && $vcs_config['filter_packages'] ?
- new WP_Error(
- __METHOD__,
- __( 'Error - could not get remote package. The package was filtered out because it is not linked to this server.', 'updatepulse-server' )
- ) :
- new WP_Error(
- __METHOD__,
- __( 'Error - could not get remote package. The package was found and is valid, but the download was aborted. Please check the package is satisfying the requirements for this server.', 'updatepulse-server' )
- );
-
- wp_cache_delete( 'upserv_download_remote_package_aborted', 'updatepulse-server' );
- }
-
/**
* Fired after a package has been registered from a VCS.
*
@@ -989,7 +994,19 @@ class Package_Manager {
do_action( 'upserv_package_manager_deleted_package', $slug, $result );
if ( $result ) {
+ $scheduled_hook = 'upserv_check_remote_' . $slug;
+
upserv_unwhitelist_package( $slug );
+ Scheduler::get_instance()->unschedule_all_actions( $scheduled_hook );
+
+ /**
+ * Fired after a remote check schedule event has been unscheduled for a package.
+ * Fired during client update API request.
+ *
+ * @param string $package_slug The slug of the package for which a remote check event has been unscheduled
+ * @param string $scheduled_hook The remote check event hook that has been unscheduled
+ */
+ do_action( 'upserv_cleared_check_remote_schedule', $slug, $scheduled_hook );
$deleted_package_slugs[] = $slug;
diff --git a/inc/manager/class-remote-sources-manager.php b/inc/manager/class-remote-sources-manager.php
index bd93012..e821bdd 100644
--- a/inc/manager/class-remote-sources-manager.php
+++ b/inc/manager/class-remote-sources-manager.php
@@ -885,7 +885,7 @@ class Remote_Sources_Manager {
}
}
- if ( empty( $slugs ) ) {
+ if ( ! empty( $slugs ) ) {
foreach ( $slugs as $idx => $slug ) {
$meta = upserv_get_package_metadata( $slug );
diff --git a/inc/server/update/class-update-server.php b/inc/server/update/class-update-server.php
index d1f1675..271eb1b 100644
--- a/inc/server/update/class-update-server.php
+++ b/inc/server/update/class-update-server.php
@@ -517,7 +517,7 @@ class Update_Server {
$remote_info = $this->update_checker->request_info();
- if ( $remote_info && ! is_wp_error( $remote_info ) ) {
+ if ( ! is_wp_error( $remote_info ) && isset( $remote_info['version'] ) ) {
$needs_update = version_compare( $remote_info['version'], $meta['header']['Version'], '>' );
} else {
Utils::php_log(
diff --git a/readme.txt b/readme.txt
index 00a9ff0..bebd86e 100644
--- a/readme.txt
+++ b/readme.txt
@@ -3,7 +3,7 @@ Contributors: frogerme
Tags: Plugin updates, Theme updates, WordPress updates, License
Requires at least: 6.7
Tested up to: 6.7
-Stable tag: 1.0.7
+Stable tag: 1.0.8
Requires PHP: 8.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -61,6 +61,15 @@ Each **bug** report will be addressed in a timely manner if properly documented
**Troubleshooting involving 3rd-party plugins or themes will not be addressed on the WordPress support forum.**
+== Upgrade Notice ==
+
+= 1.0.8 =
+
+For installations using VCS in schedule mode (as opposed to webhook mode):
+- delete all packages and re-register them
+- remove any remaining `json` files from `wp-content/uploads/updatepulse-server/metadata` folder
+- use the "Force Clear & Reschedule" button in the VCS settings
+
== FAQ ==
= How do I use UpdatePulse Server? =
@@ -119,6 +128,9 @@ This section describes how to install the plugin and get it working.
== Changelog ==
+= 1.0.8 =
+* Fix scheduled mode package overrides. After update, if using this mode: delete all packages and re-register them ; remove any remaining `json` files from `wp-content/uploads/updatepulse-server/metadata` folder ; use the "Force Clear & Reschedule" button in the VCS settings
+
= 1.0.7 =
* Full documentation of all classes and functions
diff --git a/updatepulse-server.php b/updatepulse-server.php
index c8ffba8..67ecd0b 100644
--- a/updatepulse-server.php
+++ b/updatepulse-server.php
@@ -3,7 +3,7 @@
* Plugin Name: UpdatePulse Server
* Plugin URI: https://github.com/anyape/updatepulse-server/
* Description: Run your own update server.
- * Version: 1.0.7
+ * Version: 1.0.8
* Author: Alexandre Froger
* Author URI: https://froger.me/
* License: GPLv2 or later
From 2589b5071b180b5f7905b08bd64516419c74b274 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Thu, 10 Apr 2025 17:04:36 +0800
Subject: [PATCH 03/14] Fix VCS candidates with webhook mode
---
inc/api/class-webhook-api.php | 1 -
readme.txt | 1 +
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/inc/api/class-webhook-api.php b/inc/api/class-webhook-api.php
index 9392ac4..4a4522a 100644
--- a/inc/api/class-webhook-api.php
+++ b/inc/api/class-webhook-api.php
@@ -424,7 +424,6 @@ class Webhook_API {
if ( 0 === strpos( $config['url'], trailingslashit( $url ) ) ) {
$vcs_candidates[] = $config;
- $vcs_candidates[] = $config;
}
}
}
diff --git a/readme.txt b/readme.txt
index bebd86e..3aa5e28 100644
--- a/readme.txt
+++ b/readme.txt
@@ -130,6 +130,7 @@ This section describes how to install the plugin and get it working.
= 1.0.8 =
* Fix scheduled mode package overrides. After update, if using this mode: delete all packages and re-register them ; remove any remaining `json` files from `wp-content/uploads/updatepulse-server/metadata` folder ; use the "Force Clear & Reschedule" button in the VCS settings
+* Fix VCS candidates with webhook mode
= 1.0.7 =
* Full documentation of all classes and functions
From 49adf7f6b4aaba60b60e01ae3e4d7c2bd92b3e74 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Sun, 13 Apr 2025 13:44:15 +0800
Subject: [PATCH 04/14] remove package metadata files when deleting packages ;
make sure to reinitialise the update checker to avoid slug conflicts
---
inc/manager/class-package-manager.php | 1 +
inc/server/update/class-update-server.php | 2 +-
readme.txt | 8 ++++++--
updatepulse-server.php | 2 +-
4 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/inc/manager/class-package-manager.php b/inc/manager/class-package-manager.php
index b8b686a..5fc4d8c 100644
--- a/inc/manager/class-package-manager.php
+++ b/inc/manager/class-package-manager.php
@@ -997,6 +997,7 @@ class Package_Manager {
$scheduled_hook = 'upserv_check_remote_' . $slug;
upserv_unwhitelist_package( $slug );
+ upserv_set_package_metadata( $slug, null );
Scheduler::get_instance()->unschedule_all_actions( $scheduled_hook );
/**
diff --git a/inc/server/update/class-update-server.php b/inc/server/update/class-update-server.php
index 271eb1b..ace147e 100644
--- a/inc/server/update/class-update-server.php
+++ b/inc/server/update/class-update-server.php
@@ -1315,7 +1315,7 @@ class Update_Server {
$this->self_hosted
);
- if ( $this->update_checker ) {
+ if ( $this->update_checker && $this->update_checker->slug === $slug ) {
return;
}
diff --git a/readme.txt b/readme.txt
index 3aa5e28..3c3bfe9 100644
--- a/readme.txt
+++ b/readme.txt
@@ -3,7 +3,7 @@ Contributors: frogerme
Tags: Plugin updates, Theme updates, WordPress updates, License
Requires at least: 6.7
Tested up to: 6.7
-Stable tag: 1.0.8
+Stable tag: 1.0.9
Requires PHP: 8.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -63,7 +63,7 @@ Each **bug** report will be addressed in a timely manner if properly documented
== Upgrade Notice ==
-= 1.0.8 =
+= 1.0.9 =
For installations using VCS in schedule mode (as opposed to webhook mode):
- delete all packages and re-register them
@@ -128,6 +128,10 @@ This section describes how to install the plugin and get it working.
== Changelog ==
+= 1.0.9 =
+* Schedule mode: remove package metadata files when deleting packages
+* Schedule mode: make sure to reinitialise the update checker to avoid slug conflicts
+
= 1.0.8 =
* Fix scheduled mode package overrides. After update, if using this mode: delete all packages and re-register them ; remove any remaining `json` files from `wp-content/uploads/updatepulse-server/metadata` folder ; use the "Force Clear & Reschedule" button in the VCS settings
* Fix VCS candidates with webhook mode
diff --git a/updatepulse-server.php b/updatepulse-server.php
index 67ecd0b..71c7a02 100644
--- a/updatepulse-server.php
+++ b/updatepulse-server.php
@@ -3,7 +3,7 @@
* Plugin Name: UpdatePulse Server
* Plugin URI: https://github.com/anyape/updatepulse-server/
* Description: Run your own update server.
- * Version: 1.0.8
+ * Version: 1.0.9
* Author: Alexandre Froger
* Author URI: https://froger.me/
* License: GPLv2 or later
From d4a206ab1f715b29188c25dca5fedf2eaee52f43 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Mon, 14 Apr 2025 07:47:20 +0800
Subject: [PATCH 05/14] donate link
---
readme.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/readme.txt b/readme.txt
index 3c3bfe9..b2fbde4 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,5 +1,6 @@
=== UpdatePulse Server ===
Contributors: frogerme
+Donate link: https://paypal.me/frogerme
Tags: Plugin updates, Theme updates, WordPress updates, License
Requires at least: 6.7
Tested up to: 6.7
From ec8856175ba9ae6377eafad6b30055f4f70a189f Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Tue, 22 Apr 2025 10:18:53 +0800
Subject: [PATCH 06/14] Introduce constant `PUC_FORCE_BRANCH` to bypass tags &
releases in VCS detection strategies
---
lib/package-update-checker/Vcs/BitbucketApi.php | 5 ++++-
lib/package-update-checker/Vcs/GitHubApi.php | 5 ++++-
lib/package-update-checker/Vcs/GitLabApi.php | 5 ++++-
readme.txt | 5 ++++-
updatepulse-server.php | 2 +-
5 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/lib/package-update-checker/Vcs/BitbucketApi.php b/lib/package-update-checker/Vcs/BitbucketApi.php
index ec310d1..2fd864b 100644
--- a/lib/package-update-checker/Vcs/BitbucketApi.php
+++ b/lib/package-update-checker/Vcs/BitbucketApi.php
@@ -84,7 +84,10 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
return $this->get_branch( $config_branch );
};
- if ( ( 'main' === $config_branch || 'master' === $config_branch ) ) {
+ if (
+ ( 'main' === $config_branch || 'master' === $config_branch ) &&
+ ( ! defined( PUC_FORCE_BRANCH ) || ! PUC_FORCE_BRANCH )
+ ) {
$strategies[ self::STRATEGY_LATEST_TAG ] = array( $this, 'get_latest_tag' );
}
diff --git a/lib/package-update-checker/Vcs/GitHubApi.php b/lib/package-update-checker/Vcs/GitHubApi.php
index 0526bb3..b39e35b 100644
--- a/lib/package-update-checker/Vcs/GitHubApi.php
+++ b/lib/package-update-checker/Vcs/GitHubApi.php
@@ -458,7 +458,10 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
protected function get_update_detection_strategies( $config_branch ) {
$strategies = array();
- if ( 'main' === $config_branch || 'master' === $config_branch ) {
+ if (
+ ( 'main' === $config_branch || 'master' === $config_branch ) &&
+ ( ! defined( PUC_FORCE_BRANCH ) || ! PUC_FORCE_BRANCH )
+ ) {
// Use the latest release.
$strategies[ self::STRATEGY_LATEST_RELEASE ] = array( $this, 'get_latest_release' );
// Failing that, use the tag with the highest version number.
diff --git a/lib/package-update-checker/Vcs/GitLabApi.php b/lib/package-update-checker/Vcs/GitLabApi.php
index df3dced..45cba1d 100644
--- a/lib/package-update-checker/Vcs/GitLabApi.php
+++ b/lib/package-update-checker/Vcs/GitLabApi.php
@@ -440,7 +440,10 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
protected function get_update_detection_strategies( $config_branch ) {
$strategies = array();
- if ( ( 'main' === $config_branch ) || ( 'master' === $config_branch ) ) {
+ if (
+ ( 'main' === $config_branch ) || ( 'master' === $config_branch ) &&
+ ( ! defined( PUC_FORCE_BRANCH ) || ! PUC_FORCE_BRANCH )
+ ) {
$strategies[ self::STRATEGY_LATEST_RELEASE ] = array( $this, 'get_latest_release' );
$strategies[ self::STRATEGY_LATEST_TAG ] = array( $this, 'get_latest_tag' );
}
diff --git a/readme.txt b/readme.txt
index b2fbde4..e4e26f7 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Donate link: https://paypal.me/frogerme
Tags: Plugin updates, Theme updates, WordPress updates, License
Requires at least: 6.7
Tested up to: 6.7
-Stable tag: 1.0.9
+Stable tag: 1.0.10
Requires PHP: 8.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -129,6 +129,9 @@ This section describes how to install the plugin and get it working.
== Changelog ==
+= 1.0.10 =
+* Introduce constant `PUC_FORCE_BRANCH` to bypass tags & releases in VCS detection strategies
+
= 1.0.9 =
* Schedule mode: remove package metadata files when deleting packages
* Schedule mode: make sure to reinitialise the update checker to avoid slug conflicts
diff --git a/updatepulse-server.php b/updatepulse-server.php
index 71c7a02..7a75ab4 100644
--- a/updatepulse-server.php
+++ b/updatepulse-server.php
@@ -3,7 +3,7 @@
* Plugin Name: UpdatePulse Server
* Plugin URI: https://github.com/anyape/updatepulse-server/
* Description: Run your own update server.
- * Version: 1.0.9
+ * Version: 1.0.10
* Author: Alexandre Froger
* Author URI: https://froger.me/
* License: GPLv2 or later
From c6155be1facfd4674b98bc070fcb82d71e3934b3 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Tue, 22 Apr 2025 11:33:02 +0800
Subject: [PATCH 07/14] Introduce constant `PUC_FORCE_BRANCH` to bypass tags &
releases in VCS detection strategies
---
README.md | 2 +-
.../admin/plugin-remote-sources-page.php | 22 +++++++++++++++++--
.../Vcs/BitbucketApi.php | 2 +-
lib/package-update-checker/Vcs/GitHubApi.php | 2 +-
lib/package-update-checker/Vcs/GitLabApi.php | 2 +-
5 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 62ae54a..ace678e 100644
--- a/README.md
+++ b/README.md
@@ -151,7 +151,7 @@ Name | Type | Description
Enable VCS | checkbox | Enables this server to download packages from a Version Control System before delivering updates. Supports Bitbucket, Github and Gitlab. If left unchecked, zip packages need to be manually uploaded to `wp-content/plugins/updatepulse-server/packages`.
VCS URL | text | The URL of the Version Control System where packages are hosted. Must follow the following pattern: `https://version-control-system.tld/username` where `https://version-control-system.tld` may be a self-hosted instance of Gitlab. Each package repository URL must follow the following pattern: `https://version-control-system.tld/username/package-slug/`; the package files must be located at the root of the repository, and in the case of WordPress plugins the main plugin file must follow the pattern `package-slug.php`.
Self-hosted VCS | checkbox | Check this only if the Version Control System is a self-hosted instance of Gitlab.
-Packages branch name | text | The branch to download when getting remote packages from the Version Control System.
+Packages branch name | text | The branch to download when getting remote packages from the Version Control System. If the VCS supports releases or tags, they will be prioritised over the branch name (release first, then tag, then branch). To bypass this behaviour, set the `PUC_FORCE_BRANCH` constant to `true` in `wp-config.php`.
VCS credentials | text | Credentials for non-publicly accessible repositories. In the case of Github and Gitlab, a Personal Access Token; in the case of Bitckucket, an App Password. **WARNING: Keep these credentials secret, do not share them, and take care of renewing them before they expire!**
Use Webhooks | checkbox | Check so that each repository of the Version Control System calls a Webhook when updates are pushed. When checked, UpdatePulse Server will not regularly poll repositories for package version changes, but relies on events sent by the repositories to schedule a package download. Webhook URL: `https://domain.tld/updatepulse-server-webhook/package-type/package-slug` - where `package-type` is the package type (`plugin`, `theme`, or `generic`) and `package-slug` is the slug of the package that needs updates. Note that UpdatePulse Server does not rely on the content of the payload to schedule a package download, so any type of event can be used to trigger the Webhook.
Remote Download Delay | number | Delay in minutes after which UpdatePulse Server will poll the Version Control System for package updates when the Webhook has been called. Leave at `0` to schedule a package update during the cron run happening immediately after the Webhook notification was received.
diff --git a/inc/templates/admin/plugin-remote-sources-page.php b/inc/templates/admin/plugin-remote-sources-page.php
index 243cfd4..6fca6e5 100644
--- a/inc/templates/admin/plugin-remote-sources-page.php
+++ b/inc/templates/admin/plugin-remote-sources-page.php
@@ -107,7 +107,16 @@
-
+ PUC_FORCE_BRANCH, %3$s is true, %4$s is wp-config.php
+ esc_html__( 'The branch to download when getting remote packages from the Version Control System.%1$sIf the VCS supports releases or tags, they will be prioritised over the branch name (release first, then tag, then branch).%1$sTo bypass this behaviour and exclusively rely on the branch, set the %2$s constant to %3$s in %4$s.', 'updatepulse-server' ),
+ ' ',
+ 'PUC_FORCE_BRANCH',
+ 'true',
+ 'wp-config.php'
+ );
+ ?>
@@ -295,7 +304,16 @@
-
+ PUC_FORCE_BRANCH, %3$s is true, %4$s is wp-config.php
+ esc_html__( 'The branch to download when getting remote packages from the Version Control System.%1$sIf the VCS supports releases or tags, they will be prioritised over the branch name (release first, then tag, then branch).%1$sTo bypass this behaviour and exclusively rely on the branch, set the %2$s constant to %3$s in %4$s.', 'updatepulse-server' ),
+ ' ',
+ 'PUC_FORCE_BRANCH',
+ 'true',
+ 'wp-config.php'
+ );
+ ?>
diff --git a/lib/package-update-checker/Vcs/BitbucketApi.php b/lib/package-update-checker/Vcs/BitbucketApi.php
index 2fd864b..754b2d3 100644
--- a/lib/package-update-checker/Vcs/BitbucketApi.php
+++ b/lib/package-update-checker/Vcs/BitbucketApi.php
@@ -86,7 +86,7 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
if (
( 'main' === $config_branch || 'master' === $config_branch ) &&
- ( ! defined( PUC_FORCE_BRANCH ) || ! PUC_FORCE_BRANCH )
+ ( ! defined( 'PUC_FORCE_BRANCH' ) || ! (bool) ( constant( 'PUC_FORCE_BRANCH' ) ) )
) {
$strategies[ self::STRATEGY_LATEST_TAG ] = array( $this, 'get_latest_tag' );
}
diff --git a/lib/package-update-checker/Vcs/GitHubApi.php b/lib/package-update-checker/Vcs/GitHubApi.php
index b39e35b..2bcddb4 100644
--- a/lib/package-update-checker/Vcs/GitHubApi.php
+++ b/lib/package-update-checker/Vcs/GitHubApi.php
@@ -460,7 +460,7 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
if (
( 'main' === $config_branch || 'master' === $config_branch ) &&
- ( ! defined( PUC_FORCE_BRANCH ) || ! PUC_FORCE_BRANCH )
+ ( ! defined( 'PUC_FORCE_BRANCH' ) || ! (bool) ( constant( 'PUC_FORCE_BRANCH' ) ) )
) {
// Use the latest release.
$strategies[ self::STRATEGY_LATEST_RELEASE ] = array( $this, 'get_latest_release' );
diff --git a/lib/package-update-checker/Vcs/GitLabApi.php b/lib/package-update-checker/Vcs/GitLabApi.php
index 45cba1d..886d06c 100644
--- a/lib/package-update-checker/Vcs/GitLabApi.php
+++ b/lib/package-update-checker/Vcs/GitLabApi.php
@@ -442,7 +442,7 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
if (
( 'main' === $config_branch ) || ( 'master' === $config_branch ) &&
- ( ! defined( PUC_FORCE_BRANCH ) || ! PUC_FORCE_BRANCH )
+ ( ! defined( 'PUC_FORCE_BRANCH' ) || ! (bool) ( constant( 'PUC_FORCE_BRANCH' ) ) )
) {
$strategies[ self::STRATEGY_LATEST_RELEASE ] = array( $this, 'get_latest_release' );
$strategies[ self::STRATEGY_LATEST_TAG ] = array( $this, 'get_latest_tag' );
From 15ce0595123210b504338f018b0b1e253051d44a Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Mon, 28 Apr 2025 10:35:17 +0800
Subject: [PATCH 08/14] email fix
---
inc/templates/admin/plugin-help-page.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inc/templates/admin/plugin-help-page.php b/inc/templates/admin/plugin-help-page.php
index c782712..001b6dd 100644
--- a/inc/templates/admin/plugin-help-page.php
+++ b/inc/templates/admin/plugin-help-page.php
@@ -285,7 +285,7 @@ Licensed With: another-plugin-or-theme-slug
// translators: %1$s is a link to opening an issue, %2$s is a contact email
esc_html__( 'After reading the documentation, for more help on how to use UpdatePulse Server, please %1$s - bugfixes are welcome via pull requests, detailed bug reports with accurate pointers as to where and how they occur in the code will be addressed in a timely manner, and a fee will apply for any other request (if they are addressed). If and only if you found a security issue, please contact %2$s with full details for responsible disclosure.', 'updatepulse-server' ),
'' . esc_html__( 'open an issue on Github', 'updatepulse-server' ) . '',
- 'updatepulse@anyape.com',
+ 'updatepulse@anyape.com',
);
?>
From deda3090c312127531db33b531777c639bdec5e8 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Mon, 28 Apr 2025 10:36:13 +0800
Subject: [PATCH 09/14] readme Companion Plugins
---
readme.txt | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/readme.txt b/readme.txt
index e4e26f7..992c08a 100644
--- a/readme.txt
+++ b/readme.txt
@@ -47,6 +47,15 @@ This plugin adds the following major features to WordPress:
* **API:** UpdatePulse Server provides APIs to manage packages and licenses. The APIs keys are secured with a system of tokens: the API keys are never shared over the network, acquiring a token requires signed payloads, and the tokens have a limited lifetime. For more details about tokens and security, see [the Nonce API documentation](https://github.com/anyape/updatepulse-server/blob/main/docs/misc.md#nonce-api).
To connect their plugins or themes and UpdatePulse Server, developers can find integration examples in the [UpdatePulse Server Integration Examples](https://github.com/Anyape/updatepulse-server-integration) repository - theme and plugin examples rely heavily on the popular [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker) by [Yahnis Elsts](https://github.com/YahnisElsts).
+== Companion Plugins ==
+
+The following plugins are compatible with UpdatePulse Server and can be used to extend its functionality:
+* [Updatepulse Blocks](https://store.anyape.com/product/updatepulse-blocks/): a seamless way to display packages from UpdatePulse Server directly within your site using the WordPress Block Editor or shortcodes.
+* [UpdatePulse for WooCommerce](https://store.anyape.com/product/updatepulse-for-woocommerce/): a WooCommerce connector for UpdatePulse Server, allowing you to sell licensed packages through your WooCommerce store, either on the same WordPress installation or a separate store site.
+
+Developers are encouraged to build plugins and themes [integrated](https://github.com/anyape/updatepulse-server/blob/main/README.md) with UpdatePulse Server, leveraging its publicly available functions, actions and filters, or by making use of the provided APIs.
+
+If you wish to see your plugin added to this list, please [contact the author](mailto:updatepulse@anyape.com).
== Troubleshooting ==
From e1fc90780e606ad1f186dc87021254f49c801bd7 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Mon, 28 Apr 2025 10:37:47 +0800
Subject: [PATCH 10/14] typo fix
---
inc/templates/admin/plugin-help-page.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inc/templates/admin/plugin-help-page.php b/inc/templates/admin/plugin-help-page.php
index 001b6dd..197876a 100644
--- a/inc/templates/admin/plugin-help-page.php
+++ b/inc/templates/admin/plugin-help-page.php
@@ -52,7 +52,7 @@
printf(
// translators: %s is upserv_download_remote_package( string $package_slug, string $type );
esc_html__( '[expert] calling the %s method in your own code, with the VCS-related parameters corresponding to a VCS configuration saved in UpdatePulse Server', 'updatepulse-server' ),
- 'upserv_download_remote_package( string $package_slug, string $type, string $vcs_url = false, string branch = \'main\');'
+ 'upserv_download_remote_package( string $package_slug, string $type, string $vcs_url = false, string branch = \'main\' );'
);
?>
From 575c7c3cdb21844a048028f9c09b7ba2400cd871 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Mon, 28 Apr 2025 10:38:14 +0800
Subject: [PATCH 11/14] readme update
---
readme.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/readme.txt b/readme.txt
index 992c08a..85edc62 100644
--- a/readme.txt
+++ b/readme.txt
@@ -140,6 +140,7 @@ This section describes how to install the plugin and get it working.
= 1.0.10 =
* Introduce constant `PUC_FORCE_BRANCH` to bypass tags & releases in VCS detection strategies
+* Minor fix
= 1.0.9 =
* Schedule mode: remove package metadata files when deleting packages
From 8efebb1774c0ed5c15a1caf8e3e38fa3b5601f2a Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Mon, 28 Apr 2025 10:41:48 +0800
Subject: [PATCH 12/14] wl to readme links
---
readme.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/readme.txt b/readme.txt
index 85edc62..10e48fa 100644
--- a/readme.txt
+++ b/readme.txt
@@ -50,8 +50,8 @@ To connect their plugins or themes and UpdatePulse Server, developers can find i
== Companion Plugins ==
The following plugins are compatible with UpdatePulse Server and can be used to extend its functionality:
-* [Updatepulse Blocks](https://store.anyape.com/product/updatepulse-blocks/): a seamless way to display packages from UpdatePulse Server directly within your site using the WordPress Block Editor or shortcodes.
-* [UpdatePulse for WooCommerce](https://store.anyape.com/product/updatepulse-for-woocommerce/): a WooCommerce connector for UpdatePulse Server, allowing you to sell licensed packages through your WooCommerce store, either on the same WordPress installation or a separate store site.
+* [Updatepulse Blocks](https://store.anyape.com/product/updatepulse-blocks/?wl=1): a seamless way to display packages from UpdatePulse Server directly within your site using the WordPress Block Editor or shortcodes.
+* [UpdatePulse for WooCommerce](https://store.anyape.com/product/updatepulse-for-woocommerce/?wl=1): a WooCommerce connector for UpdatePulse Server, allowing you to sell licensed packages through your WooCommerce store, either on the same WordPress installation or a separate store site.
Developers are encouraged to build plugins and themes [integrated](https://github.com/anyape/updatepulse-server/blob/main/README.md) with UpdatePulse Server, leveraging its publicly available functions, actions and filters, or by making use of the provided APIs.
From 8356ac6ca27dd109adab5abe7bab1ae696051a69 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Wed, 4 Jun 2025 21:40:26 +0800
Subject: [PATCH 13/14] fix nonce cleanup
---
inc/nonce/class-nonce.php | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/inc/nonce/class-nonce.php b/inc/nonce/class-nonce.php
index 2bce8a5..734ac66 100644
--- a/inc/nonce/class-nonce.php
+++ b/inc/nonce/class-nonce.php
@@ -588,15 +588,17 @@ class Nonce {
$sql = "DELETE FROM {$wpdb->prefix}upserv_nonce
WHERE expiry < %d
AND (
- JSON_VALID(`data`) = 1
- AND (
- JSON_EXTRACT(`data` , '$.permanent') IS NULL
- OR JSON_EXTRACT(`data` , '$.permanent') = 0
- OR JSON_EXTRACT(`data` , '$.permanent') = '0'
- OR JSON_EXTRACT(`data` , '$.permanent') = false
+ JSON_VALID(`data`) = 0
+ OR (
+ JSON_VALID(`data`) = 1
+ AND (
+ JSON_EXTRACT(`data` , '$.permanent') IS NULL
+ OR JSON_EXTRACT(`data` , '$.permanent') = 0
+ OR JSON_EXTRACT(`data` , '$.permanent') = '0'
+ OR JSON_EXTRACT(`data` , '$.permanent') = false
+ )
)
- ) OR
- JSON_VALID(`data`) = 0;";
+ );";
$sql_args = array( time() - self::DEFAULT_EXPIRY_LENGTH );
/**
From 84da98bc6a86fb865ec4a6b0f4d2f3d43bf3bd24 Mon Sep 17 00:00:00 2001
From: Alexandre Froger <3622456+froger-me@users.noreply.github.com>
Date: Fri, 6 Jun 2025 10:38:22 +0800
Subject: [PATCH 14/14] Fix activation issue - `WP_Filesystem` call
---
inc/manager/class-data-manager.php | 2 ++
readme.txt | 1 +
2 files changed, 3 insertions(+)
diff --git a/inc/manager/class-data-manager.php b/inc/manager/class-data-manager.php
index d4454c3..01037c0 100644
--- a/inc/manager/class-data-manager.php
+++ b/inc/manager/class-data-manager.php
@@ -172,6 +172,8 @@ class Data_Manager {
* @since 1.0.0
*/
public static function maybe_setup_mu_plugin() {
+ WP_Filesystem();
+
global $wp_filesystem;
$result = true;
diff --git a/readme.txt b/readme.txt
index 10e48fa..fc0fc1d 100644
--- a/readme.txt
+++ b/readme.txt
@@ -141,6 +141,7 @@ This section describes how to install the plugin and get it working.
= 1.0.10 =
* Introduce constant `PUC_FORCE_BRANCH` to bypass tags & releases in VCS detection strategies
* Minor fix
+* Fix activation issue - `WP_Filesystem` call
= 1.0.9 =
* Schedule mode: remove package metadata files when deleting packages