diff --git a/README.md b/README.md
index e6648d8..ace678e 100644
--- a/README.md
+++ b/README.md
@@ -50,9 +50,9 @@ Package updates may be either uploaded directly, or hosted in a Version Control
This plugin adds the following major features to WordPress:
-* **Packages Overview:** manage package updates with a table showing Package Name, Version, Type, File Name, Size, Last Modified and License Status; includes bulk operations to delete, download and change the license status, and the ability to delete all the packages. Upload updates from your local machine to UpdatePulse Server, or let the system to automatically download them to UpdatePulse Server from a Version Control System. Store packages either locally, or in the Cloud with an S3 compatible service. Packages can also be managed through their own API.
+* **Packages Overview:** manage package updates with a table showing Package Name, Version, Type, File Name, Size, File Modified and License Status; includes bulk operations to delete, download and change the license status, and the ability to delete all the packages. Upload updates from your local machine to UpdatePulse Server, or let the system to automatically download them to UpdatePulse Server from a Version Control System. Store packages either locally, or in the Cloud with an S3 compatible service. Packages can also be managed through their own API.
* **Version Control Systems:** configure the Version Control Systems of your choice (Bitbucket, Github, Gitlab, or a self-hosted installation of Gitlab) with secure credentials and a branch name where the updates are hosted; choose to check for updates recurringly, or when receiving a webhook notification. UpdatePulse Server acts as a middleman between your reposiroty, your udpates storage (local or Cloud), and your clients.
-* **Licenses:** manage licenses with a table showing ID, License Key, Registered Email, Status, Package Type, Package Slug, Creation Date, and Expiry Date; add and edit them with a form, or use the API for more control. Licenses prevent packages installed on client machines from being updated without a valid license. Licenses are generated automatically by default and the values are unguessable (it is recommended to keep the default). When checking the validity of licenses an extra license signature is also checked to prevent the use of a license on more than the configured allowed domains.
+* **Licenses:** manage licenses with a table showing ID, License Key, Registered Email, Status, Package Type, Package Slug, Creation Date, and Expiration Date; add and edit them with a form, or use the API for more control. Licenses prevent packages installed on client machines from being updated without a valid license. Licenses are generated automatically by default and the values are unguessable (it is recommended to keep the default). When checking the validity of licenses an extra license signature is also checked to prevent the use of a license on more than the configured allowed domains.
* **Not limited to WordPress:** with a platform-agnostic API, updates can be served for any type of package, not just WordPress plugins & themes. Basic examples of integration with Node.js, PHP, bash, and Python are provided in the [documentation](https://github.com/anyape/updatepulse-server/blob/main/docs/generic.md).
* **API & Webhooks:** Use the Package API to administer packages (browse, read, edit, add, delete), and request for expirable signed URLs of packages to allow secure downloads. Use the License API to administer licenses (browse, read, edit, add, delete) and check, activate or deactivate licenses. Fire Webhooks to notify any URL of your choice of key events affecting packages and licenses.
@@ -117,7 +117,7 @@ UpdatePulse Server provides a user interface to manage packages, manage licenses
### Packages Overview
This tab allows administrators to:
-- View the list of packages currently available in UpdatePulse Server, with Package Name, Version, Type (Plugin or Theme), File Name, Size, Last Modified and License Status (if enabled)
+- View the list of packages currently available in UpdatePulse Server, with Package Name, Version, Type (Plugin or Theme), File Name, Size, File Modified (when the package file was changed on the file system) and License Status (if enabled)
- Download a package
- Delete a package
- Apply bulk actions on the list of packages (download, delete)
@@ -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.
@@ -169,7 +169,7 @@ In case Webhooks are not used, the following actions are available to forcefully
This tab allows administrators to:
- Entirely enable/disable package licenses. **It affects all the packages with a "Requires License" license status delivered by UpdatePulse Server.**
-- View the list of licenses currently stored by UpdatePulse Server, with License Key, Registered Email, Status, Package Type (Plugin or Theme), Package Slug, Creation Date, Expiry Date, ID
+- View the list of licenses currently stored by UpdatePulse Server, with License Key, Registered Email, Status, Package Type (Plugin or Theme), Package Slug, Creation Date, Expiration Date, ID
- Add a license
- Edit a license
- Delete a license
diff --git a/autoload.php b/autoload.php
index b2684d1..9fc8ec2 100644
--- a/autoload.php
+++ b/autoload.php
@@ -1,7 +1,27 @@
span {
+ margin: 0 1em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#upserv-licenses-list .col_status.pending mark {
+ background: #c8d7e1;
+ color: #003d66;
+}
+
+#upserv-licenses-list .col_status.blocked mark {
+ background: #eba3a3;
+ color: #570000;
+}
+
+#upserv-licenses-list .col_status.activated mark,
+#upserv-licenses-list .col_status.deactivated mark {
+ background: #c6e1c6;
+ color: #2c4700;
+}
+
+#upserv-licenses-list .col_status.expired mark,
+#upserv-licenses-list .col_status.on-hold mark {
+ background: #f8dda7;
+ color: #573b00;
+}
diff --git a/deploy.sh b/deploy.sh
index 5cd36b7..f222bac 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -5,7 +5,7 @@ DIR=$(pwd)
PLUGINSLUG=$(basename "$DIR")
MAINFILE="$PLUGINSLUG.php"
# SVN user
-SVNUSER=$1
+SVNUSER=""
# Verbose mode
VERBOSE=false
# Deploy mode
@@ -18,32 +18,51 @@ SCRIPT_NAME=$(basename "$0")
GITBRANCH="main"
# Parse arguments
-for arg in "$@"; do
- case "$arg" in
+while [[ $# -gt 0 ]]; do
+ case "$1" in
-d|--deploy)
DEPLOY=true
+ shift
;;
-v|--verbose)
VERBOSE=true
+ shift
;;
-sa|--skip-assets)
SKIP_ASSETS=true
+ shift
;;
-b|--branch)
+ if [[ -z "$2" || "$2" == -* ]]; then
+ echo "Error: Missing value for --branch option."
+ exit 1
+ fi
GITBRANCH="$2"
- shift
+ shift 2
;;
-mf|--mainfile)
+ if [[ -z "$2" || "$2" == -* ]]; then
+ echo "Error: Missing value for --mainfile option."
+ exit 1
+ fi
MAINFILE="$2"
- shift
+ shift 2
;;
-p|--path)
+ if [[ -z "$2" || "$2" == -* ]]; then
+ echo "Error: Missing value for --path option."
+ exit 1
+ fi
DIR="$2"
- shift
+ shift 2
;;
-s|--slug)
+ if [[ -z "$2" || "$2" == -* ]]; then
+ echo "Error: Missing value for --slug option."
+ exit 1
+ fi
PLUGINSLUG="$2"
- shift
+ shift 2
;;
-h|--help)
echo "Deploy WordPress plugin to the official repository."
@@ -52,9 +71,13 @@ for arg in "$@"; do
exit 0
;;
*)
- # Assume the first non-flag argument is PARAM1
+ # Assume the first non-flag argument is SVNUSER
if [[ -z "$SVNUSER" ]]; then
- SVNUSER="$arg"
+ SVNUSER="$1"
+ shift
+ else
+ echo "Error: Unexpected argument '$1'."
+ exit 1
fi
;;
esac
@@ -67,12 +90,37 @@ if [[ -z "$SVNUSER" ]]; then
exit 1
fi
+# Debug output (optional, for testing purposes)
+if [[ "$VERBOSE" == true ]]; then
+ echo "SVNUSER: $SVNUSER"
+ echo "DEPLOY: $DEPLOY"
+ echo "VERBOSE: $VERBOSE"
+ echo "SKIP_ASSETS: $SKIP_ASSETS"
+ echo "GITBRANCH: $GITBRANCH"
+ echo "MAINFILE: $MAINFILE"
+ echo "DIR: $DIR"
+ echo "PLUGINSLUG: $PLUGINSLUG"
+fi
+
# Git config
GITPATH="$DIR/"
# SVN config
SVNPATH="/tmp/$PLUGINSLUG" # path to a temp SVN repo. No trailing slash required and don't add trunk.
SVNURL="http://plugins.svn.wordpress.org/$PLUGINSLUG/" # Remote SVN repo on wordpress.org, with no trailing slash
+# Function to handle command errors
+handle_error() {
+ local cmd="$1"
+ local exit_code="$2"
+ echo "Error: $cmd command failed with exit code $exit_code"
+ cd "$GITPATH" || {
+ echo "Error: Unable to change directory to $GITPATH"
+ exit 1
+ }
+ git checkout "$CURRENTBRANCH"
+ exit 1
+}
+
# Function to execute or echo commands based on deploy mode
execute_or_echo() {
local command="$1"
@@ -90,6 +138,18 @@ execute_or_echo() {
echo "$command ${args[*]}"
fi
"$command" "${args[@]}"
+ local exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ # Make exception for git commit when there's nothing to commit
+ if [[ "$command" == "git" && "${args[0]}" == "commit" && $exit_code -eq 1 ]]; then
+ # Check if the error is about "nothing to commit"
+ if git status | grep -q "nothing to commit"; then
+ echo "Notice: Nothing to commit, continuing with deployment"
+ return 0
+ fi
+ fi
+ handle_error "$command ${args[*]}" "$exit_code"
+ fi
fi
;;
svn)
@@ -101,6 +161,10 @@ execute_or_echo() {
echo "$command ${args[*]}"
fi
"$command" "${args[@]}"
+ local exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ handle_error "$command ${args[*]}"
+ fi
fi
;;
gh)
@@ -112,6 +176,10 @@ execute_or_echo() {
echo "gh ${args[*]}"
fi
"$command" "${args[@]}"
+ local exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ handle_error "$command ${args[*]}"
+ fi
fi
;;
*)
@@ -120,6 +188,10 @@ execute_or_echo() {
echo "$command ${args[*]}"
fi
"$command" "${args[@]}"
+ local exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ handle_error "$command ${args[*]}"
+ fi
;;
esac
}
@@ -145,7 +217,7 @@ fi
# Check version in readme.txt is the same as plugin file
NEWVERSION1=$(grep "^Stable tag:" "$GITPATH"/readme.txt | awk -F' ' '{print $NF}')
-NEWVERSION2=$(grep "^Version:" "$GITPATH"/"$MAINFILE" | awk -F' ' '{print $NF}')
+NEWVERSION2=$(grep -E "^[[:space:]]*\*?[[:space:]]*Version:" "$GITPATH"/"$MAINFILE" | awk -F' ' '{print $NF}')
echo "readme.txt version: $NEWVERSION1"
echo "$MAINFILE version: $NEWVERSION2"
@@ -270,8 +342,8 @@ fi
# Export HEAD of branch from git to SVN trunk
execute_or_echo git checkout-index -a -f --prefix="$SVNPATH"/trunk/
-# Ignore GitHub-specific files
-execute_or_echo svn propset svn:ignore "deploy.sh
+# Ignore files
+execute_or_echo svn propset svn:ignore "*.sh
.DS_Store
.vscode
.git
@@ -283,7 +355,23 @@ execute_or_echo svn add readme.txt
# Create new SVN tag
execute_or_echo cd "$SVNPATH"
-execute_or_echo svn copy trunk/ tags/"$NEWVERSION1"/
+
+# Check if the tag already exists
+if svn list "$SVNURL"/tags/ | grep -q "$NEWVERSION1"; then
+
+ # Switch back to the original branch
+ execute_or_echo cd "$GITPATH"
+ execute_or_echo git checkout "$CURRENTBRANCH"
+
+ echo "Tag $NEWVERSION1 already exists. Exiting."
+
+ # Clean up temporary directory
+ execute_or_echo rm -fr "${SVNPATH:?}/"
+
+ exit 1
+fi
+
+execute_or_echo svn copy trunk tags/"$NEWVERSION1"
# Add all new files in the tag folder
execute_or_echo cd "$SVNPATH"/tags/"$NEWVERSION1"
diff --git a/docs/licenses.md b/docs/licenses.md
index 2f0be04..5de29ff 100644
--- a/docs/licenses.md
+++ b/docs/licenses.md
@@ -66,7 +66,8 @@ UpdatePulse Server provides an API and offers a series of functions, actions and
* [upserv\_license\_api\_request\_authorized](#upserv_license_api_request_authorized)
* [upserv\_license\_bypass\_signature](#upserv_license_bypass_signature)
* [upserv\_api\_license\_actions](#upserv_api_license_actions)
- * [upserv\_api\_license\_actions](#upserv_api_license_actions-1)
+ * [upserv\_license\_update\_server\_prepare\_license\_for\_output](#upserv_license_update_server_prepare_license_for_output)
+ * [upserv\_schedule\_license\_frequency](#upserv_schedule_license_frequency)
___
## The License Query
@@ -631,7 +632,7 @@ $payload = array(
'txn_id' => '#999999999', // If applicable, the transaction identifier associated to the purchase of the license (optional)
'date_created' => '3099-12-01', // Creation date of the license - YYYY-MM-DD (required)
'date_renewed' => '3099-12-015', // Date of the last time the license was renewed -\n YYYY-MM-DD (optional)
- 'date_expiry' => '3099-12-31', // Expiry date of the license - YYY-MM-DD - if omitted, no expiry (optional)
+ 'date_expiry' => '3099-12-31', // Expiration date of the license - YYY-MM-DD - if omitted, no expiry (optional)
'package_slug' => 'new-package', // The package slug - only alphanumeric characters and dashes are allowed (required)
'package_type' => 'theme', // Type of package the license is for - one of plugin, theme, generic (required)
'api_token' => 'token', // The authentication token (optional - must provided via X-UpdatePulse-Token header if absent)
@@ -707,7 +708,7 @@ $payload = array(
'txn_id' => '#111111111', // If applicable, the transaction identifier associated to the purchase of the license (optional)
'date_created' => '2099-12-01', // Creation date of the license - YYYY-MM-DD (required)
'date_renewed' => '2099-12-015', // Date of the last time the license was renewed -\n YYYY-MM-DD (optional)
- 'date_expiry' => '2099-12-31', // Expiry date of the license - YYY-MM-DD - if omitted, no expiry (optional)
+ 'date_expiry' => '2099-12-31', // Expiration date of the license - YYY-MM-DD - if omitted, no expiry (optional)
'package_slug' => 'example-package', // The package slug - only alphanumeric characters and dashes are allowed (required)
'package_type' => 'plugin', // Type of package the license is for - one of plugin, theme, generic (required)
'api_token' => 'token', // The authentication token (optional - must provided via X-UpdatePulse-Token header if absent)
@@ -1622,7 +1623,7 @@ Filter the License API actions available for API access control.
> (array) the API actions
___
-### upserv_api_license_actions
+### upserv_license_update_server_prepare_license_for_output
```php
apply_filters( 'upserv_license_update_server_prepare_license_for_output', array $output, object $license );
@@ -1639,3 +1640,15 @@ Filter the license data to send to the remote client.
> (array) the original license object
___
+### upserv_schedule_license_frequency
+
+```php
+apply_filters( 'upserv_schedule_license_frequency', string $frequency );
+```
+**Description**
+Filter the frequency at which the license maintenance task runs.
+
+**Parameters**
+`$frequency`
+> (string) the frequency at which the license maintenance task runs. Default is `hourly` (see [WP_Cron](https://developer.wordpress.org/reference/classes/wp_cron/) for more information on the available frequencies)
+___
\ No newline at end of file
diff --git a/docs/misc.md b/docs/misc.md
index 2b3f526..cc6fc7e 100644
--- a/docs/misc.md
+++ b/docs/misc.md
@@ -8,6 +8,9 @@ UpdatePulse Server provides an API and offers a series of functions, actions and
* [Acquiring a reusable token or a true nonce - payload](#acquiring-a-reusable-token-or-a-true-nonce---payload)
* [Responses](#responses)
* [Building API credentials and API signature](#building-api-credentials-and-api-signature)
+ * [Update API](#update-api)
+ * [The `get_metadata` action](#the-get_metadata-action)
+ * [The `download` action](#the-download-action)
* [WP CLI](#wp-cli)
* [Consuming Webhooks](#consuming-webhooks)
* [Functions](#functions)
@@ -248,6 +251,174 @@ echo '
Update Plugin description. Basic HTML<\/strong> can be used in all sections.<\/p><\/div>",
+ "dummy_section": " An extra, dummy section.<\/p><\/div>",
+ "installation": " Installation instructions.<\/p><\/div>",
+ "changelog": " This section will be displayed by default when the user clicks 'View version x.y.z details'.<\/p><\/div>",
+ "frequently_asked_questions": " Answer<\/p><\/div>",
+ },
+ "icons": {
+ "1x": "https:\/\/domain.tld\/path\/to\/icon-128x128.png",
+ "2x": "https:\/\/domain.tld\/path\/to\/icon-256x256.png",
+ },
+ "banners": {
+ "low": "https:\/\/domain.tld\/path\/to\/banner-772x250.png",
+ "high": "https:\/\/domain.tld\/path\/to\/banner-1544x500.png",
+ },
+ "require_license": "1",
+ "slug": "dummy-plugin",
+ "type": "plugin",
+ "download_url": "https:\/\/server.domain.tld\/updatepulse-server-update-api\/?action=download&package_id=dummy-plugin&token=tokenabcdef1234567890&license_key=abcdef1234567890&license_signature=signabcdef1234567890&update_type=Plugin",
+ "license": {
+ "license_key": "abcdef1234567890",
+ "max_allowed_domains": 2,
+ "allowed_domains": [
+ "domain.tld",
+ "domain2.tld"
+ ],
+ "status": "activated",
+ "txn_id": "",
+ "date_created": "2025-02-04",
+ "date_renewed": "0000-00-00",
+ "date_expiry": "2027-02-04",
+ "package_slug": "dummy-plugin",
+ "package_type": "plugin",
+ "result": "success",
+ "message": "License key details retrieved."
+ },
+ "time_elapsed": "0.139s"
+}
+```
+
+Examples of a response (failure - invalid package):
+```json
+{
+ "error": "no_server",
+ "message": "No server found for this package."
+}
+```
+
+Examples of a response (failure - invalid license):
+```json
+{
+ "name": "Dummy Plugin",
+ "version": "1.5.0",
+ "homepage": "https:\/\/domain.tld\/",
+ "author": "A Developer",
+ "author_homepage": "https:\/\/domain.tld\/",
+ "description": "Updated Empty plugin to demonstrate the UpdatePulse Updater.",
+ "details_url": "https:\/\/domain.tld\/",
+ "requires": "4.9.8",
+ "tested": "4.9.8",
+ "requires_php": "7.0",
+ "sections": {
+ "description": " Update Plugin description. Basic HTML<\/strong> can be used in all sections.<\/p><\/div>",
+ "dummy_section": " An extra, dummy section.<\/p><\/div>",
+ "installation": " Installation instructions.<\/p><\/div>",
+ "changelog": " This section will be displayed by default when the user clicks 'View version x.y.z details'.<\/p><\/div>",
+ "frequently_asked_questions": " Answer<\/p><\/div>",
+ },
+ "icons": {
+ "1x": "https:\/\/domain.tld\/path\/to\/icon-128x128.png",
+ "2x": "https:\/\/domain.tld\/path\/to\/icon-256x256.png",
+ },
+ "banners": {
+ "low": "https:\/\/domain.tld\/path\/to\/banner-772x250.png",
+ "high": "https:\/\/domain.tld\/path\/to\/banner-1544x500.png",
+ },
+ "require_license": "1",
+ "slug": "dummy-plugin",
+ "type": "plugin",
+ "license_error": {
+ "code": "invalid_license",
+ "message": "The license key or signature is invalid.",
+ "data": {
+ "license": false
+ }
+ },
+ "time_elapsed": "0.139s"
+}
+```
+
+### The `download` action
+
+The `download` action is used to download the package. It accepts the following parameters:
+
+| Parameter | Description | Required |
+| --- | --- | --- |
+| `action` | The action to perform. Must be `download`. | Yes |
+| `package_id` | The ID of the package to download. | Yes |
+| `token` | The cryptographic token to use to download the package. Generated by the Nonce API. | Yes |
+| `license_key` | The license key of the package | Yes (if the package requires a license) |
+| `license_signature` | The license signature of the package | Yes (if the package requires a license) |
+| `update_type` | The type of update. Must be one of `Plugin`, `Theme`, or `Generic`. | Yes |
+
+Generally, the URL to request this API endpoint would not be put together manually, but rather taken from the field `download_url` in the response of `get_metadata` action.
+
+Example of a request to the Update API with:
+- `download` action
+- `package_id` set to `dummy-plugin`
+- `token` set to `tokenabcdef1234567890`
+- `license_key` set to `abcdef1234567890`
+- `license_signature` set to `signabcdef1234567890`
+- `update_type` set to `Plugin`
+
+```bash
+curl -X GET "https://server.domain.tld/updatepulse-server-update-api/?action=download&package_id=dummy-plugin&token=tokenabcdef1234567890&license_key=abcdef1234567890&license_signature=signabcdef1234567890&update_type=Plugin"
+```
+
+The response is a `zip` file containing the package.
+
## WP CLI
UpdatePulse Server provides a series of commands to interact with the plugin:
@@ -641,7 +812,7 @@ upserv_fire_webhook( string $url, string $secret, string $body, string $action )
```
**Description**
-Immediately send a event notification to `$url`, signed with `$secret` with resulting has stored in `X-UpdatePulse-Signature-256`, with `$action` in `X-UpdatePulse-Action`.
+Immediately send a event notification to `$url`, signed with `$secret` with resulting hash stored in `X-UpdatePulse-Signature-256`, with `$action` in `X-UpdatePulse-Action`.
**Parameters**
`$url`
diff --git a/docs/packages.md b/docs/packages.md
index 1aed023..15a7cf0 100644
--- a/docs/packages.md
+++ b/docs/packages.md
@@ -486,7 +486,7 @@ Values format in case of a plugin package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "plugin-slug",
"type": "plugin",
"file_name": "plugin-slug.zip",
@@ -523,7 +523,7 @@ Values format in case of a theme package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "theme-slug",
"type": "theme",
"file_name": "theme-slug.zip",
@@ -560,7 +560,7 @@ Values format in case of a generic package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "generic-slug",
"type": "generic",
"file_name": "generic-slug.zip",
@@ -650,7 +650,7 @@ Values format in case of a plugin package:
"low": "https:\/\/domain.tld\/banner-722x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "plugin-slug",
"type": "plugin",
"file_name": "plugin-slug.zip",
@@ -687,7 +687,7 @@ Values format in case of a theme package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "theme-slug",
"type": "theme",
"file_name": "theme-slug.zip",
@@ -724,7 +724,7 @@ Values format in case of a generic package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "generic-slug",
"type": "generic",
"file_name": "generic-slug.zip",
@@ -885,7 +885,7 @@ Values format in case of a theme package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "theme-slug",
"type": "theme",
"file_name": "theme-slug.zip",
@@ -922,7 +922,7 @@ Values format in case of a generic package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "generic-slug",
"type": "generic",
"file_name": "generic-slug.zip",
@@ -1426,7 +1426,7 @@ Values format in case of a plugin package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "plugin-slug",
"type": "plugin",
"file_name": "plugin-slug.zip",
@@ -1454,7 +1454,7 @@ Values format in case of a theme package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "theme-slug",
"type": "theme",
"file_name": "theme-slug.zip",
@@ -1482,7 +1482,7 @@ Values format in case of a generic package:
"low": "https:\/\/domain.tld\/banner-772x250.png",
"high": "https:\/\/domain.tld\/banner-1544x500.png"
},
- "require_license": false,
+ "require_license": "",
"slug": "generic-slug",
"type": "generic",
"file_name": "generic-slug.zip",
diff --git a/functions.php b/functions.php
index fdd755b..4411570 100644
--- a/functions.php
+++ b/functions.php
@@ -1,4 +1,13 @@
get_options();
}
}
if ( ! function_exists( 'upserv_update_options' ) ) {
+ /**
+ * Updates all plugin options
+ *
+ * Replaces the entire options array with the provided options.
+ *
+ * @since 1.0
+ *
+ * @param array $options The new options to save
+ * @return bool True on success, false on failure
+ */
function upserv_update_options( $options ) {
return UPServ::get_instance()->update_options( $options );
}
}
if ( ! function_exists( 'upserv_get_option' ) ) {
+ /**
+ * Gets a specific option by path
+ *
+ * Retrieves an option value using slash notation path.
+ *
+ * @since 1.0
+ *
+ * @param string $path The path to the option using slash notation
+ * @param mixed $_default Default value if option doesn't exist
+ * @return mixed The option value or default if not found
+ */
function upserv_get_option( $path, $_default = null ) {
return UPServ::get_instance()->get_option( $path, $_default );
}
}
if ( ! function_exists( 'upserv_set_option' ) ) {
+ /**
+ * Sets a specific option by path
+ *
+ * Set an option value within the current request using slash notation path.
+ * Does NOT commit the changes to persistence.
+ * To persist the data, call @see upserv_update_options()
+ * with the return value of this function.
+ *
+ * @since 1.0
+ *
+ * @param string $path The path to the option using slash notation
+ * @param mixed $value The value to set
+ * @return array The updated options array
+ */
function upserv_set_option( $path, $value ) {
return UPServ::get_instance()->set_option( $path, $value );
}
}
if ( ! function_exists( 'upserv_update_option' ) ) {
+ /**
+ * Updates a specific option by path
+ *
+ * Updates an existing option value using slash notation path.
+ * Commits the changes to persistence.
+ *
+ * @since 1.0
+ *
+ * @param string $path The path to the option using slash notation
+ * @param mixed $value The value to set
+ * @return bool True on success, false on failure
+ */
function upserv_update_option( $path, $value ) {
return UPServ::get_instance()->update_option( $path, $value );
}
}
if ( ! function_exists( 'upserv_assets_suffix' ) ) {
+ /**
+ * Gets the appropriate asset file suffix based on debug mode
+ *
+ * Returns an empty string in debug mode, or '.min' in production,
+ * to be used for loading appropriate CSS/JS file versions.
+ *
+ * @since 1.0
+ *
+ * @return string '.min' if WP_DEBUG is false, empty string otherwise
+ */
function upserv_assets_suffix() {
return (bool) ( constant( 'WP_DEBUG' ) ) ? '' : '.min';
}
@@ -78,30 +165,68 @@ if ( ! function_exists( 'upserv_assets_suffix' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_is_doing_license_api_request' ) ) {
+ /**
+ * Determines if the current request is a License API request
+ *
+ * Checks whether the current request is made by a client plugin or theme
+ * interacting with the plugin's license API.
+ *
+ * @since 1.0
+ *
+ * @return bool True if the current request is a License API request, false otherwise
+ */
function upserv_is_doing_license_api_request() {
return License_API::is_doing_api_request();
}
}
if ( ! function_exists( 'upserv_is_doing_update_api_request' ) ) {
+ /**
+ * Determine whether the current request is made by a client plugin, theme, or generic package interacting with the plugin's API.
+ *
+ * @since 1.0
+ *
+ * @return bool `true` if the current request is a client plugin, theme, or generic package interacting with the plugin's API, `false` otherwise
+ */
function upserv_is_doing_update_api_request() {
return Update_API::is_doing_api_request();
}
}
if ( ! function_exists( 'upserv_is_doing_webhook_api_request' ) ) {
+ /**
+ * Determine whether the current request is made by a Webhook.
+ *
+ * @since 1.0
+ *
+ * @return bool `true` if the current request is made by a Webhook, `false` otherwise
+ */
function upserv_is_doing_webhook_api_request() {
return Webhook_API::is_doing_api_request();
}
}
if ( ! function_exists( 'upserv_is_doing_package_api_request' ) ) {
+ /**
+ * Determine whether the current request is made by a remote client interacting with the plugin's package API.
+ *
+ * @since 1.0
+ *
+ * @return bool `true` if the current request is made by a remote client interacting with the plugin's package API, `false` otherwise
+ */
function upserv_is_doing_package_api_request() {
return Package_API::is_doing_api_request();
}
}
if ( ! function_exists( 'upserv_is_doing_api_request' ) ) {
+ /**
+ * Determine whether the current request is made by a remote client interacting with any of the APIs.
+ *
+ * @since 1.0
+ *
+ * @return bool `true` if the current request is made by a remote client interacting with any of the APIs, `false` otherwise
+ */
function upserv_is_doing_api_request() {
$mu_doing_api = wp_cache_get( 'upserv_mu_doing_api', 'updatepulse-server' );
$is_api_request = $mu_doing_api ?
@@ -118,10 +243,18 @@ if ( ! function_exists( 'upserv_is_doing_api_request' ) ) {
}
/*******************************************************************
- * Data ditectories functions
+ * Data directories functions
*******************************************************************/
if ( ! function_exists( 'upserv_get_data_dir' ) ) {
+ /**
+ * Get the path to a specific directory within the plugin's content directory.
+ *
+ * @since 1.0
+ *
+ * @param string $dir The directory to get the path for
+ * @return string The path to the specified directory within the plugin's content directory
+ */
function upserv_get_data_dir( $dir ) {
return Data_Manager::get_data_dir( $dir );
}
@@ -134,24 +267,52 @@ if ( ! function_exists( 'upserv_get_root_data_dir' ) ) {
}
if ( ! function_exists( 'upserv_get_packages_data_dir' ) ) {
+ /**
+ * Get the path to the packages directory on the file system.
+ *
+ * @since 1.0
+ *
+ * @return string The path to the packages directory on the file system
+ */
function upserv_get_packages_data_dir() {
return Data_Manager::get_data_dir( 'packages' );
}
}
if ( ! function_exists( 'upserv_get_logs_data_dir' ) ) {
+ /**
+ * Get the path to the plugin's log directory.
+ *
+ * @since 1.0
+ *
+ * @return string The path to the plugin's log directory
+ */
function upserv_get_logs_data_dir() {
return Data_Manager::get_data_dir( 'logs' );
}
}
if ( ! function_exists( 'upserv_get_cache_data_dir' ) ) {
+ /**
+ * Get the path to the plugin's package cache directory.
+ *
+ * @since 1.0
+ *
+ * @return string The path to the plugin's package cache directory
+ */
function upserv_get_cache_data_dir() {
return Data_Manager::get_data_dir( 'cache' );
}
}
if ( ! function_exists( 'upserv_get_package_metadata_data_dir' ) ) {
+ /**
+ * Get the path to the plugin's package metadata directory.
+ *
+ * @since 1.0
+ *
+ * @return string The path to the plugin's package metadata directory
+ */
function upserv_get_package_metadata_data_dir() {
return Data_Manager::get_data_dir( 'metadata' );
}
@@ -162,18 +323,42 @@ if ( ! function_exists( 'upserv_get_package_metadata_data_dir' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_is_package_whitelisted' ) ) {
+ /**
+ * Determine whether a package is whitelisted.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @return bool `true` if the package is whitelisted, `false` otherwise
+ */
function upserv_is_package_whitelisted( $package_slug ) {
return Package_Manager::get_instance()->is_package_whitelisted( $package_slug );
}
}
if ( ! function_exists( 'upserv_whitelist_package' ) ) {
+ /**
+ * Whitelist a package.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @return bool `true` if the package was successfully whitelisted, `false` otherwise
+ */
function upserv_whitelist_package( $package_slug ) {
return Package_Manager::get_instance()->whitelist_package( $package_slug );
}
}
if ( ! function_exists( 'upserv_unwhitelist_package' ) ) {
+ /**
+ * Unwhitelist a package.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @return bool `true` if the package was successfully unwhitelisted, `false` otherwise
+ */
function upserv_unwhitelist_package( $package_slug ) {
return Package_Manager::get_instance()->unwhitelist_package( $package_slug );
}
@@ -184,6 +369,15 @@ if ( ! function_exists( 'upserv_unwhitelist_package' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_get_package_metadata' ) ) {
+ /**
+ * Get metadata of a package.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @param bool $json_encode Whether to return a JSON object (default) or a PHP associative array
+ * @return mixed The package metadata
+ */
function upserv_get_package_metadata( $package_slug, $json_encode = false ) {
return Package_Manager::get_instance()->get_package_metadata(
$package_slug,
@@ -193,6 +387,15 @@ if ( ! function_exists( 'upserv_get_package_metadata' ) ) {
}
if ( ! function_exists( 'upserv_set_package_metadata' ) ) {
+ /**
+ * Set metadata of a package.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @param array $metadata The metadata to set
+ * @return bool `true` if the metadata was successfully set, `false` otherwise
+ */
function upserv_set_package_metadata( $package_slug, $metadata ) {
return Package_Manager::get_instance()->set_package_metadata(
$package_slug,
@@ -206,18 +409,39 @@ if ( ! function_exists( 'upserv_set_package_metadata' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_force_cleanup_cache' ) ) {
+ /**
+ * Force clean up the `cache` plugin data.
+ *
+ * @since 1.0
+ *
+ * @return bool `true` in case of success, `false` otherwise
+ */
function upserv_force_cleanup_cache() {
return Data_Manager::maybe_cleanup( 'cache', true );
}
}
if ( ! function_exists( 'upserv_force_cleanup_logs' ) ) {
+ /**
+ * Force clean up the `logs` plugin data.
+ *
+ * @since 1.0
+ *
+ * @return bool `true` in case of success, `false` otherwise
+ */
function upserv_force_cleanup_logs() {
return Data_Manager::maybe_cleanup( 'logs', true );
}
}
if ( ! function_exists( 'upserv_force_cleanup_tmp' ) ) {
+ /**
+ * Force clean up the `tmp` plugin data.
+ *
+ * @since 1.0
+ *
+ * @return bool `true` in case of success, `false` otherwise
+ */
function upserv_force_cleanup_tmp() {
return Data_Manager::maybe_cleanup( 'tmp', true );
}
@@ -228,18 +452,43 @@ if ( ! function_exists( 'upserv_force_cleanup_tmp' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_check_remote_plugin_update' ) ) {
+ /**
+ * Determine whether the remote plugin package is an updated version compared to one on the file system.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the plugin package to check
+ * @return bool `true` if the remote plugin package is an updated version, `false` otherwise. If the local package does not exist, returns `true`
+ */
function upserv_check_remote_plugin_update( $slug ) {
return upserv_check_remote_package_update( $slug, 'plugin' );
}
}
if ( ! function_exists( 'upserv_check_remote_theme_update' ) ) {
+ /**
+ * Determine whether the remote theme package is an updated version compared to the one on the file system.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the theme package to check
+ * @return bool `true` if the remote theme package is an updated version, `false` otherwise. If the package does not exist on the file system, returns `true`
+ */
function upserv_check_remote_theme_update( $slug ) {
return upserv_check_remote_package_update( $slug, 'theme' );
}
}
if ( ! function_exists( 'upserv_check_remote_package_update' ) ) {
+ /**
+ * Determine whether the remote package is an updated version compared to the one on the file system.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the package to check
+ * @param string $type The type of the package
+ * @return bool `true` if the remote package is an updated version, `false` otherwise. If the local package does not exist, returns `true`
+ */
function upserv_check_remote_package_update( $slug, $type ) {
$api = Update_API::get_instance();
@@ -248,18 +497,52 @@ if ( ! function_exists( 'upserv_check_remote_package_update' ) ) {
}
if ( ! function_exists( 'upserv_download_remote_plugin' ) ) {
+ /**
+ * Download a plugin package from the Version Control System to the package directory on the file system.
+ * If `$vcs_url` and `$branch` are provided, the plugin will attempt to get an existing VCS configuration and register the package with it.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the plugin package to download
+ * @param string $vcs_url The URL of a VCS configured in UpdatePulse Server; default to `false`
+ * @param string $branch The branch as provided in a VCS configured in UpdatePulse Server; default to `'main'`
+ * @return bool `true` if the plugin package was successfully downloaded, `false` otherwise
+ */
function upserv_download_remote_plugin( $slug, $vcs_url = false, $branch = 'main' ) {
return upserv_download_remote_package( $slug, 'plugin', $vcs_url, $branch );
}
}
if ( ! function_exists( 'upserv_download_remote_theme' ) ) {
+ /**
+ * Download a theme package from the Version Control System to the package directory on the file system.
+ * If `$vcs_url` and `$branch` are provided, the plugin will attempt to get an existing VCS configuration and register the package with it.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the theme package to download
+ * @param string $vcs_url The URL of a VCS configured in UpdatePulse Server; default to `false`
+ * @param string $branch The branch as provided in a VCS configured in UpdatePulse Server; default to `'main'`
+ * @return bool `true` if the theme package was successfully downloaded, `false` otherwise
+ */
function upserv_download_remote_theme( $slug, $vcs_url = false, $branch = 'main' ) {
return upserv_download_remote_package( $slug, 'theme', $vcs_url, $branch );
}
}
if ( ! function_exists( 'upserv_download_remote_package' ) ) {
+ /**
+ * Download a package from the Version Control System to the package directory on the file system.
+ * If `$vcs_url` and `$branch` are provided, the plugin will attempt to get an existing VCS configuration and register the package with it.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the package to download
+ * @param string $type The type of the package; default to `'generic'`
+ * @param string $vcs_url The URL of a VCS configured in UpdatePulse Server; default to `false`
+ * @param string $branch The branch as provided in a VCS configured in UpdatePulse Server; default to `'main'`
+ * @return bool|WP_Error `WP_Error` if provided VCS information is invalid, `true` if the package was successfully downloaded, `false` otherwise
+ */
function upserv_download_remote_package( $slug, $type = 'generic', $vcs_url = false, $branch = 'main' ) {
if ( $vcs_url ) {
@@ -286,6 +569,14 @@ if ( ! function_exists( 'upserv_download_remote_package' ) ) {
}
if ( ! function_exists( 'upserv_get_package_vcs_config' ) ) {
+ /**
+ * Get the Version Control System (VCS) configuration for a package.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the package
+ * @return array The VCS configuration for the package
+ */
function upserv_get_package_vcs_config( $slug ) {
$meta = upserv_get_package_metadata( $slug );
@@ -298,6 +589,14 @@ if ( ! function_exists( 'upserv_get_package_vcs_config' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_delete_package' ) ) {
+ /**
+ * Delete a package on the file system.
+ *
+ * @since 1.0
+ *
+ * @param string $slug The slug of the package to delete
+ * @return bool `true` if the package was successfully deleted, `false` otherwise
+ */
function upserv_delete_package( $slug ) {
$package_manager = Package_Manager::get_instance();
@@ -306,6 +605,15 @@ if ( ! function_exists( 'upserv_delete_package' ) ) {
}
if ( ! function_exists( 'upserv_get_package_info' ) ) {
+ /**
+ * Get information about a package on the file system.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @param bool $json_encode Whether to return a JSON object (default) or a PHP associative array
+ * @return mixed The package information as a PHP associative array or a JSON object
+ */
function upserv_get_package_info( $package_slug, $json_encode = true ) {
$result = $json_encode ? '{}' : array();
$package_manager = Package_Manager::get_instance();
@@ -320,6 +628,14 @@ if ( ! function_exists( 'upserv_get_package_info' ) ) {
}
if ( ! function_exists( 'upserv_is_package_require_license' ) ) {
+ /**
+ * Determine whether a package requires a license key.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @return bool `true` if the package requires a license key, `false` otherwise
+ */
function upserv_is_package_require_license( $package_slug ) {
$api = License_API::get_instance();
@@ -328,6 +644,15 @@ if ( ! function_exists( 'upserv_is_package_require_license' ) ) {
}
if ( ! function_exists( 'upserv_get_batch_package_info' ) ) {
+ /**
+ * Get batch information of packages on the file system.
+ *
+ * @since 1.0
+ *
+ * @param string $search Search string to be used in package's slug and package's name (case insensitive)
+ * @param bool $json_encode Whether to return a JSON object (default) or a PHP associative array
+ * @return mixed The batch information as a PHP associative array or a JSON object; each entry is formatted like in `upserv_get_package_info`
+ */
function upserv_get_batch_package_info( $search, $json_encode = true ) {
$result = $json_encode ? '{}' : array();
$package_manager = Package_Manager::get_instance();
@@ -342,6 +667,16 @@ if ( ! function_exists( 'upserv_get_batch_package_info' ) ) {
}
if ( ! function_exists( 'upserv_download_local_package' ) ) {
+ /**
+ * Start a download of a package from the file system and exits.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @param string $package_path The path of the package on the local file system - if `null`, will attempt to find it using `upserv_get_local_package_path( $package_slug )`; default `null`
+ * @param bool $exit_or_die Whether to exit or die after the download; default `true`
+ * @return void
+ */
function upserv_download_local_package( $package_slug, $package_path = null, $exit_or_die = true ) {
$package_manager = Package_Manager::get_instance();
@@ -354,6 +689,14 @@ if ( ! function_exists( 'upserv_download_local_package' ) ) {
}
if ( ! function_exists( 'upserv_get_local_package_path' ) ) {
+ /**
+ * Get the path of a plugin, theme, or generic package on the file system.
+ *
+ * @since 1.0
+ *
+ * @param string $package_slug The slug of the package
+ * @return string|false The path of the package on the local file system or `false` if it does not exist
+ */
function upserv_get_local_package_path( $package_slug ) {
WP_Filesystem();
@@ -376,8 +719,16 @@ if ( ! function_exists( 'upserv_get_local_package_path' ) ) {
/*******************************************************************
* Licenses functions
*******************************************************************/
-
if ( ! function_exists( 'upserv_browse_licenses' ) ) {
+ /**
+ * Browse the license records filtered using various criteria.
+ *
+ * @since 1.0
+ *
+ * @param array $license_query The License Query
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#the-license-query
+ * @return array An array of license objects matching the License Query.
+ */
function upserv_browse_licenses( $license_query ) {
$api = License_API::get_instance();
@@ -386,6 +737,16 @@ if ( ! function_exists( 'upserv_browse_licenses' ) ) {
}
if ( ! function_exists( 'upserv_read_license' ) ) {
+ /**
+ * Read a license record.
+ *
+ * @since 1.0
+ *
+ * @param array $license_data The License payload data.
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#read
+ * @return mixed An object in case of success or an empty array otherwise.
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#read the object is the decoded value of the JSON string
+ */
function upserv_read_license( $license_data ) {
$api = License_API::get_instance();
@@ -394,6 +755,16 @@ if ( ! function_exists( 'upserv_read_license' ) ) {
}
if ( ! function_exists( 'upserv_add_license' ) ) {
+ /**
+ * Add a license.
+ *
+ * @since 1.0
+ *
+ * @param array $license_data The License payload data
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#add
+ * @return mixed An object in case of success or an array of errors otherwise.
+ * * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#add the object is the decoded value of the JSON string
+ */
function upserv_add_license( $license_data ) {
if ( is_array( $license_data ) && ! isset( $license_data['data'] ) ) {
@@ -410,6 +781,16 @@ if ( ! function_exists( 'upserv_add_license' ) ) {
}
if ( ! function_exists( 'upserv_edit_license' ) ) {
+ /**
+ * Edit a license record.
+ *
+ * @since 1.0
+ *
+ * @param array $license_data The License payload data.
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#edit
+ * @return mixed An object in case of success or an array of errors otherwise.
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#edit the object is the decoded value of the JSON string
+ */
function upserv_edit_license( $license_data ) {
if ( is_array( $license_data ) && ! isset( $license_data['data'] ) ) {
@@ -426,6 +807,16 @@ if ( ! function_exists( 'upserv_edit_license' ) ) {
}
if ( ! function_exists( 'upserv_delete_license' ) ) {
+ /**
+ * Delete a license record.
+ *
+ * @since 1.0
+ *
+ * @param array $license_data The License payload data.
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#delete
+ * @return mixed An object in case of success or an empty array otherwise.
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#delete the object is the decoded value of the JSON string
+ */
function upserv_delete_license( $license_data ) {
if ( is_array( $license_data ) && ! isset( $license_data['data'] ) ) {
@@ -442,6 +833,16 @@ if ( ! function_exists( 'upserv_delete_license' ) ) {
}
if ( ! function_exists( 'upserv_check_license' ) ) {
+ /**
+ * Check a License information.
+ *
+ * @since 1.0
+ *
+ * @param array $license_data An associative array with a single value - `array( 'license_key' => 'key_of_the_license_to_check' )`.
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#check
+ * @return mixed An object in case of success, and associative array in case of failure
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#check the object is the decoded value of the JSON string
+ */
function upserv_check_license( $license_data ) {
$api = License_API::get_instance();
@@ -450,6 +851,15 @@ if ( ! function_exists( 'upserv_check_license' ) ) {
}
if ( ! function_exists( 'upserv_activate_license' ) ) {
+ /**
+ * Activate a License.
+ *
+ * @since 1.0
+ *
+ * @param array $license_data An associative array with 2 values - `array( 'license_key' => 'key_of_the_license_to_activate', 'allowed_domains' => 'domain_to_activate' )`.
+ * @return mixed An object in case of success, and associative array in case of failure
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#activate the object is the decoded value of the JSON string
+ */
function upserv_activate_license( $license_data ) {
$api = License_API::get_instance();
@@ -458,6 +868,15 @@ if ( ! function_exists( 'upserv_activate_license' ) ) {
}
if ( ! function_exists( 'upserv_deactivate_license' ) ) {
+ /**
+ * Deactivate a License.
+ *
+ * @since 1.0
+ *
+ * @param array $license_data An associative array with 2 values - `array( 'license_key' => 'key_of_the_license_to_deactivate', 'allowed_domains' => 'domain_to_deactivate' )`.
+ * @return mixed An object in case of success, and associative array in case of failure
+ * @see https://github.com/Anyape/updatepulse-server/blob/main/docs/licenses.md#deactivate the object is the decoded value of the JSON string
+ */
function upserv_deactivate_license( $license_data ) {
$api = License_API::get_instance();
@@ -470,6 +889,21 @@ if ( ! function_exists( 'upserv_deactivate_license' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_get_template' ) ) {
+ /**
+ * Loads a template file from the plugin's template directory
+ *
+ * This function locates and loads template files for the frontend of the plugin.
+ * It applies filters to the template name and arguments, sets up query variables,
+ * and then passes the template to the UPServ template loader.
+ *
+ * @since 1.0
+ *
+ * @param string $template_name The name of the template to load
+ * @param array $args Arguments to pass to the template
+ * @param boolean $load Whether to load the template file
+ * @param boolean $require_file Whether to require or require_once the template file
+ * @return string|bool Path to the template file or false if not found
+ */
function upserv_get_template( $template_name, $args = array(), $load = true, $require_file = false ) {
$template_name = apply_filters( 'upserv_get_template_name', $template_name, $args );
$template_args = apply_filters( 'upserv_get_template_args', $args, $template_name );
@@ -488,6 +922,21 @@ if ( ! function_exists( 'upserv_get_template' ) ) {
}
if ( ! function_exists( 'upserv_get_admin_template' ) ) {
+ /**
+ * Loads a template file from the plugin's admin template directory
+ *
+ * This function locates and loads template files for the admin area of the plugin.
+ * It applies filters to the template name and arguments, sets up query variables,
+ * and then passes the template to the UPServ admin template loader.
+ *
+ * @since 1.0
+ *
+ * @param string $template_name The name of the admin template to load
+ * @param array $args Arguments to pass to the template
+ * @param boolean $load Whether to load the template file
+ * @param boolean $require_file Whether to require or require_once the template file
+ * @return string|bool Path to the template file or false if not found
+ */
function upserv_get_admin_template( $template_name, $args = array(), $load = true, $require_file = false ) {
$template_name = apply_filters( 'upserv_get_admin_template_name', $template_name, $args );
$template_args = apply_filters( 'upserv_get_admin_template_args', $args, $template_name );
@@ -510,12 +959,33 @@ if ( ! function_exists( 'upserv_get_admin_template' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_init_nonce_auth' ) ) {
+ /**
+ * Initialize the nonce authentication.
+ *
+ * @since 1.0
+ *
+ * @param string $private_auth_key The private authentication key
+ */
function upserv_init_nonce_auth( $private_auth_key ) {
Nonce::init_auth( $private_auth_key );
}
}
if ( ! function_exists( 'upserv_create_nonce' ) ) {
+ /**
+ * Create a nonce
+ *
+ * Creates a cryptographic token - allows creation of tokens that are true one-time-use nonces, with custom expiry length and custom associated data.
+ *
+ * @since 1.0
+ *
+ * @param bool $true_nonce Whether the nonce is one-time-use; default `true`
+ * @param int $expiry_length The number of seconds after which the nonce expires; default `UPServ_Nonce::DEFAULT_EXPIRY_LENGTH` - 30 seconds
+ * @param array $data Custom data to save along with the nonce; set an element with key `permanent` to a truthy value to create a nonce that never expires; default `array()`
+ * @param int $return_type Whether to return the nonce, or an array of information; default `UPServ_Nonce::NONCE_ONLY`; other accepted value is `UPServ_Nonce::NONCE_INFO_ARRAY`
+ * @param bool $store Whether to store the nonce, or let a third party mechanism take care of it; default `true`
+ * @return bool|string|array `false` in case of failure; the cryptographic token string if `$return_type` is set to `UPServ_Nonce::NONCE_ONLY`; an array of information if `$return_type` is set to `UPServ_Nonce::NONCE_INFO_ARRAY`
+ */
function upserv_create_nonce(
$true_nonce = true,
$expiry_length = Nonce::DEFAULT_EXPIRY_LENGTH,
@@ -528,36 +998,86 @@ if ( ! function_exists( 'upserv_create_nonce' ) ) {
}
if ( ! function_exists( 'upserv_get_nonce_expiry' ) ) {
+ /**
+ * Get the expiry timestamp of a nonce.
+ *
+ * @since 1.0
+ *
+ * @param string $nonce The nonce
+ * @return int The expiry timestamp
+ */
function upserv_get_nonce_expiry( $nonce ) {
return Nonce::get_nonce_expiry( $nonce );
}
}
if ( ! function_exists( 'upserv_get_nonce_data' ) ) {
+ /**
+ * Get the data stored along a nonce.
+ *
+ * @since 1.0
+ *
+ * @param string $nonce The nonce
+ * @return array The data stored along the nonce
+ */
function upserv_get_nonce_data( $nonce ) {
return Nonce::get_nonce_data( $nonce );
}
}
if ( ! function_exists( 'upserv_validate_nonce' ) ) {
+ /**
+ * Check whether the value is a valid nonce.
+ *
+ * @since 1.0
+ *
+ * @param string $value The value to check
+ * @return bool Whether the value is a valid nonce
+ */
function upserv_validate_nonce( $value ) {
return Nonce::validate_nonce( $value );
}
}
if ( ! function_exists( 'upserv_delete_nonce' ) ) {
+ /**
+ * Delete a nonce from the system if the corresponding value exists.
+ *
+ * @since 1.0
+ *
+ * @param string $value The value to delete
+ * @return bool Whether the nonce was deleted
+ */
function upserv_delete_nonce( $value ) {
return Nonce::delete_nonce( $value );
}
}
if ( ! function_exists( 'upserv_clear_nonces' ) ) {
+ /**
+ * Clear expired nonces from the system.
+ *
+ * @since 1.0
+ *
+ * @return bool Whether some nonces were cleared
+ */
function upserv_clear_nonces() {
return Nonce::upserv_nonce_cleanup();
}
}
if ( ! function_exists( 'upserv_build_nonce_api_signature' ) ) {
+ /**
+ * Build credentials and signature for UpdatePulse Server Nonce API.
+ *
+ * @since 1.0
+ *
+ * @param string $api_key_id The ID of the Private API Key
+ * @param string $api_key The Private API Key - will not be sent over the Internet
+ * @param int $timestamp The timestamp used to limit the validity of the signature (validity is MINUTE_IN_SECONDS)
+ * @param int $payload The payload to acquire a reusable token or a true nonce
+ * @return array An array with keys `credentials` and `signature`
+ */
function upserv_build_nonce_api_signature( $api_key_id, $api_key, $timestamp, $payload ) {
unset( $payload['api_signature'] );
unset( $payload['api_credentials'] );
@@ -595,6 +1115,16 @@ if ( ! function_exists( 'upserv_build_nonce_api_signature' ) ) {
*******************************************************************/
if ( ! function_exists( 'upserv_schedule_webhook' ) ) {
+ /**
+ * Schedule an event notification to be sent to registered Webhook URLs at next cron run.
+ *
+ * @since 1.0
+ *
+ * @param array $payload The data used to schedule the notification
+ * @param string $event_type The type of event; the payload will only be delivered to URLs subscribed to this type
+ * @param boolean $instant Whether to send the notification immediately; default `false`
+ * @return null|WP_Error `null` in case of success, a `WP_Error` otherwise
+ */
function upserv_schedule_webhook( $payload, $event_type, $instant = false ) {
if ( isset( $payload['event'], $payload['content'] ) ) {
@@ -611,6 +1141,17 @@ if ( ! function_exists( 'upserv_schedule_webhook' ) ) {
}
if ( ! function_exists( 'upserv_fire_webhook' ) ) {
+ /**
+ * Immediately send a event notification to `$url`, signed with `$secret` with resulting hash stored in `X-UpdatePulse-Signature-256`, with `$action` in `X-UpdatePulse-Action`.
+ *
+ * @since 1.0
+ *
+ * @param string $url The destination of the notification
+ * @param string $secret The secret used to sign the notification
+ * @param string $body The JSON string sent in the notification
+ * @param string $action The WordPress action responsible for firing the webhook
+ * @return array|WP_Error The response of the request in case of success, a `WP_Error` otherwise
+ */
function upserv_fire_webhook( $url, $secret, $body, $action ) {
if (
diff --git a/inc/api/class-license-api.php b/inc/api/class-license-api.php
index a67d78a..1c84584 100644
--- a/inc/api/class-license-api.php
+++ b/inc/api/class-license-api.php
@@ -11,17 +11,71 @@ use Exception;
use Anyape\UpdatePulse\Server\Server\License\License_Server;
use Anyape\Utils\Utils;
+/**
+ * License API
+ *
+ * @since 1.0.0
+ */
class License_API {
- protected $license_server;
- protected $http_response_code = null;
- protected $api_key_id;
- protected $api_access;
-
+ /**
+ * Is doing API request
+ *
+ * @var boolean|null
+ * @since 1.0.0
+ */
protected static $doing_api_request = null;
+ /**
+ * Instance
+ *
+ * @var License_API|null
+ * @since 1.0.0
+ */
protected static $instance;
+ /**
+ * Config
+ *
+ * @var array|null
+ * @since 1.0.0
+ */
protected static $config;
+ /**
+ * License server
+ *
+ * @var License_Server
+ * @since 1.0.0
+ */
+ protected $license_server;
+ /**
+ * HTTP response code
+ *
+ * @var int|null
+ * @since 1.0.0
+ */
+ protected $http_response_code = null;
+ /**
+ * API key ID
+ *
+ * @var string|null
+ * @since 1.0.0
+ */
+ protected $api_key_id;
+ /**
+ * API access
+ *
+ * @var array|null
+ * @since 1.0.0
+ */
+ protected $api_access;
+
+ /**
+ * Constructor
+ *
+ * @param boolean $init_hooks
+ * @param boolean $local_request
+ * @since 1.0.0
+ */
public function __construct( $init_hooks = false, $local_request = true ) {
if ( upserv_get_option( 'use_licenses' ) ) {
@@ -56,6 +110,13 @@ class License_API {
// API action --------------------------------------------------
+ /**
+ * Browse licenses
+ *
+ * @param string $query
+ * @return object Result of the browse operation
+ * @since 1.0.0
+ */
public function browse( $query ) {
$payload = json_decode( wp_unslash( $query ), true );
@@ -144,6 +205,13 @@ class License_API {
return (object) $result;
}
+ /**
+ * Read license
+ *
+ * @param array $license_data
+ * @return object Result of the read operation
+ * @since 1.0.0
+ */
public function read( $license_data ) {
$result = wp_cache_get(
'upserv_license_' . md5( wp_json_encode( $license_data ) ),
@@ -188,6 +256,13 @@ class License_API {
return (object) $result;
}
+ /**
+ * Edit license
+ *
+ * @param array $license_data
+ * @return object Result of the edit operation
+ * @since 1.0.0
+ */
public function edit( $license_data ) {
if ( upserv_is_doing_api_request() ) {
@@ -246,6 +321,13 @@ class License_API {
return (object) $result;
}
+ /**
+ * Add license
+ *
+ * @param array $license_data
+ * @return object Result of the add operation
+ * @since 1.0.0
+ */
public function add( $license_data ) {
if ( $this->api_key_id ) {
@@ -284,6 +366,13 @@ class License_API {
return (object) $result;
}
+ /**
+ * Delete license
+ *
+ * @param array $license_data
+ * @return object Result of the delete operation
+ * @since 1.0.0
+ */
public function delete( $license_data ) {
$result = $this->license_server->delete_license( $license_data );
@@ -313,7 +402,20 @@ class License_API {
return (object) $result;
}
+ /**
+ * Check license
+ *
+ * @param array $license_data
+ * @return object Result of the check operation
+ * @since 1.0.0
+ */
public function check( $license_data ) {
+ /**
+ * Filter the license data payload before checking a license.
+ *
+ * @param array $license_data The license data payload.
+ * @since 1.0.0
+ */
$license_data = apply_filters( 'upserv_check_license_dirty_payload', $license_data );
$license = $this->license_server->read_license( $license_data );
$raw_result = array();
@@ -327,8 +429,21 @@ class License_API {
$result = null;
}
+ /**
+ * Filter the result of the license check operation.
+ *
+ * @param object|null $license The license object or null if not found.
+ * @param array $license_data The license data payload.
+ * @since 1.0.0
+ */
$result = apply_filters( 'upserv_check_license_result', $license, $license_data );
+ /**
+ * Fired after checking a license.
+ *
+ * @param mixed $raw_result The raw result of the license check.
+ * @since 1.0.0
+ */
do_action( 'upserv_did_check_license', $raw_result );
if ( ! is_object( $result ) ) {
@@ -338,7 +453,20 @@ class License_API {
return (object) $result;
}
+ /**
+ * Activate license
+ *
+ * @param array $license_data
+ * @return object Result of the activate operation
+ * @since 1.0.0
+ */
public function activate( $license_data ) {
+ /**
+ * Filter the license data payload before activating a license.
+ *
+ * @param array $license_data The license data payload.
+ * @since 1.0.0
+ */
$license_data = apply_filters( 'upserv_activate_license_dirty_payload', $license_data );
$this->normalize_allowed_domains( $license_data );
@@ -347,6 +475,12 @@ class License_API {
$license = $this->license_server->read_license( $license_data );
$domain = $this->extract_domain_from_license_data( $license_data );
+ /**
+ * Fired before activating a license.
+ *
+ * @param object $license The license object.
+ * @since 1.0.0
+ */
do_action( 'upserv_pre_activate_license', $license );
if ( $this->is_valid_license_for_state_transition( $license, $request_slug, $domain ) ) {
@@ -357,14 +491,43 @@ class License_API {
$raw_result = isset( $result['raw_result'] ) ? $result['raw_result'] : $result;
$result = isset( $result['result'] ) ? $result['result'] : $result;
- $result = apply_filters( 'upserv_activate_license_result', $result, $license_data, $license );
+ /**
+ * Filter the result of the license activation operation.
+ *
+ * @param object $result The result of the license activation.
+ * @param array $license_data The license data payload.
+ * @param object $license The license object.
+ * @since 1.0.0
+ */
+ $result = apply_filters( 'upserv_activate_license_result', $result, $license_data, $license );
+
+ /**
+ * Fired after activating a license.
+ *
+ * @param mixed $raw_result The raw result of the license activation.
+ * @param array $license_data The license data payload.
+ * @since 1.0.0
+ */
do_action( 'upserv_did_activate_license', $raw_result, $license_data );
return (object) $result;
}
+ /**
+ * Deactivate license
+ *
+ * @param array $license_data
+ * @return object Result of the deactivate operation
+ * @since 1.0.0
+ */
public function deactivate( $license_data ) {
+ /**
+ * Filter the license data payload before deactivating a license.
+ *
+ * @param array $license_data The license data payload.
+ * @since 1.0.0
+ */
$license_data = apply_filters( 'upserv_deactivate_license_dirty_payload', $license_data );
$this->normalize_allowed_domains( $license_data );
@@ -373,6 +536,12 @@ class License_API {
$license = $this->license_server->read_license( $license_data );
$domain = $this->extract_domain_from_license_data( $license_data );
+ /**
+ * Fired before deactivating a license.
+ *
+ * @param object $license The license object.
+ * @since 1.0.0
+ */
do_action( 'upserv_pre_deactivate_license', $license );
if ( $this->is_valid_license_for_state_transition( $license, $request_slug, $domain ) ) {
@@ -383,8 +552,24 @@ class License_API {
$raw_result = isset( $result['raw_result'] ) ? $result['raw_result'] : $result;
$result = isset( $result['result'] ) ? $result['result'] : $result;
- $result = apply_filters( 'upserv_deactivate_license_result', $result, $license_data, $license );
+ /**
+ * Filter the result of the license deactivation operation.
+ *
+ * @param object $result The result of the license deactivation.
+ * @param array $license_data The license data payload.
+ * @param object $license The license object.
+ * @since 1.0.0
+ */
+ $result = apply_filters( 'upserv_deactivate_license_result', $result, $license_data, $license );
+
+ /**
+ * Fired after deactivating a license.
+ *
+ * @param mixed $raw_result The raw result of the license deactivation.
+ * @param array $license_data The license data payload.
+ * @since 1.0.0
+ */
do_action( 'upserv_did_deactivate_license', $raw_result, $license_data );
return (object) $result;
@@ -392,6 +577,11 @@ class License_API {
// WordPress hooks ---------------------------------------------
+ /**
+ * Add endpoints
+ *
+ * @since 1.0.0
+ */
public function add_endpoints() {
add_rewrite_rule(
'^updatepulse-server-license-api/*$',
@@ -405,6 +595,11 @@ class License_API {
);
}
+ /**
+ * Parse request
+ *
+ * @since 1.0.0
+ */
public function parse_request() {
global $wp;
@@ -415,6 +610,13 @@ class License_API {
}
}
+ /**
+ * Query vars filter
+ *
+ * @param array $query_vars
+ * @return array The filtered query vars
+ * @since 1.0.0
+ */
public function query_vars( $query_vars ) {
$query_vars = array_merge(
$query_vars,
@@ -434,6 +636,13 @@ class License_API {
return $query_vars;
}
+ /**
+ * Filter update request params
+ *
+ * @param array $params
+ * @return array The filtered params
+ * @since 1.0.0
+ */
public function upserv_handle_update_request_params( $params ) {
global $wp;
@@ -448,6 +657,13 @@ class License_API {
return $params;
}
+ /**
+ * Filter license actions
+ *
+ * @param array $actions
+ * @return array The filtered actions
+ * @since 1.0.0
+ */
public function upserv_api_license_actions( $actions ) {
$actions['browse'] = __( 'Browse multiple license records', 'updatepulse-server' );
$actions['read'] = __( 'Get single license records', 'updatepulse-server' );
@@ -458,6 +674,13 @@ class License_API {
return $actions;
}
+ /**
+ * Filter webhook events
+ *
+ * @param array $webhook_events
+ * @return array The filtered webhook events
+ * @since 1.0.0
+ */
public function upserv_api_webhook_events( $webhook_events ) {
if ( isset( $webhook_events['license'], $webhook_events['license']['events'] ) ) {
@@ -471,10 +694,23 @@ class License_API {
return $webhook_events;
}
+ /**
+ * Bypass license action
+ *
+ * @since 1.0.0
+ */
public function upserv_bypass_did_edit_license_action() {
remove_action( 'upserv_did_edit_license', array( $this, 'upserv_did_license_action' ), 20 );
}
+ /**
+ * License action
+ *
+ * @param object $result
+ * @param array $payload
+ * @param object $original
+ * @since 1.0.0
+ */
public function upserv_did_license_action( $result, $payload, $original = null ) {
$format = '';
$event = 'license_' . str_replace(
@@ -546,6 +782,16 @@ class License_API {
remove_filter( 'upserv_webhook_fire', array( $this, 'upserv_webhook_fire' ), 10 );
}
+ /**
+ * Webhook fire filter
+ *
+ * @param boolean $fire
+ * @param array $payload
+ * @param string $url
+ * @param array $info
+ * @return boolean The filtered fire value
+ * @since 1.0.0
+ */
public function upserv_webhook_fire( $fire, $payload, $url, $info ) {
if ( ! isset( $info['licenseAPIKey'] ) || empty( $info['licenseAPIKey'] ) ) {
@@ -608,6 +854,16 @@ class License_API {
return $fire;
}
+ /**
+ * Fetch nonce filter
+ *
+ * @param string $nonce
+ * @param string $true_nonce
+ * @param int $expiry
+ * @param array $data
+ * @return string|null The filterd nonce or null if invalid
+ * @since 1.0.0
+ */
public function upserv_fetch_nonce_private( $nonce, $true_nonce, $expiry, $data ) {
$config = self::get_config();
$valid = false;
@@ -647,6 +903,13 @@ class License_API {
return $nonce;
}
+ /**
+ * Nonce API payload filter
+ *
+ * @param array $payload
+ * @return array The filtered payload
+ * @since 1.0.0
+ */
public function upserv_nonce_api_payload( $payload ) {
global $wp;
@@ -692,6 +955,12 @@ class License_API {
// Misc. -------------------------------------------------------
+ /**
+ * Is doing API request
+ *
+ * @return boolean True if doing API request, false otherwise
+ * @since 1.0.0
+ */
public static function is_doing_api_request() {
if ( null === self::$doing_api_request ) {
@@ -701,6 +970,12 @@ class License_API {
return self::$doing_api_request;
}
+ /**
+ * Get config
+ *
+ * @return array The config
+ * @since 1.0.0
+ */
public static function get_config() {
if ( ! self::$config ) {
@@ -712,9 +987,21 @@ class License_API {
self::$config = $config;
}
+ /**
+ * Filter the License API configuration.
+ *
+ * @param array $config The License API configuration.
+ * @since 1.0.0
+ */
return apply_filters( 'upserv_license_api_config', self::$config );
}
+ /**
+ * Get instance
+ *
+ * @return License_API The instance
+ * @since 1.0.0
+ */
public static function get_instance() {
if ( ! self::$instance ) {
@@ -724,6 +1011,13 @@ class License_API {
return self::$instance;
}
+ /**
+ * Is package require license
+ *
+ * @param int $package_id
+ * @return boolean True if package requires license, false otherwise
+ * @since 1.0.0
+ */
public static function is_package_require_license( $package_id ) {
$require_license = wp_cache_get( 'upserv_package_require_license_' . $package_id, 'updatepulse-server', false, $found );
@@ -745,6 +1039,13 @@ class License_API {
* Protected methods
*******************************************************************/
+ /**
+ * Sanitize license result
+ *
+ * @param object $result - by reference
+ * @return void
+ * @since 1.0.0
+ */
protected function sanitize_license_result( &$result ) {
$num_allowed_domains = (
isset( $result->allowed_domains ) &&
@@ -767,6 +1068,15 @@ class License_API {
}
}
+ /**
+ * Prepare error response
+ *
+ * @param string $code
+ * @param string $message
+ * @param array $data
+ * @return array The response
+ * @since 1.0.0
+ */
protected function prepare_error_response( $code, $message, $data = array() ) {
return array(
'code' => $code,
@@ -775,6 +1085,13 @@ class License_API {
);
}
+ /**
+ * Normalize allowed domains
+ *
+ * @param array $license_data - by reference
+ * @return void
+ * @since 1.0.0
+ */
protected function normalize_allowed_domains( &$license_data ) {
if ( isset( $license_data['allowed_domains'] ) && ! is_array( $license_data['allowed_domains'] ) ) {
@@ -782,6 +1099,13 @@ class License_API {
}
}
+ /**
+ * Extract domain from license data
+ *
+ * @param array $license_data
+ * @return string|false The first domain found or false if not found
+ * @since 1.0.0
+ */
protected function extract_domain_from_license_data( $license_data ) {
if (
@@ -795,6 +1119,15 @@ class License_API {
return false;
}
+ /**
+ * Is valid license for state transition
+ *
+ * @param object $license
+ * @param string $request_slug
+ * @param string $domain
+ * @return boolean True if valid, false otherwise
+ * @since 1.0.0
+ */
protected function is_valid_license_for_state_transition( $license, $request_slug, $domain ) {
return (
is_object( $license ) &&
@@ -804,6 +1137,14 @@ class License_API {
);
}
+ /**
+ * Handle license activation
+ *
+ * @param object $license
+ * @param string $domain
+ * @return array|null The result or null if not found
+ * @since 1.0.0
+ */
protected function handle_license_activation( $license, $domain ) {
$domain_count = count( $license->allowed_domains ) + 1;
@@ -824,6 +1165,13 @@ class License_API {
return $this->process_license_activation( $license, $domain );
}
+ /**
+ * Prepare illegal status response
+ *
+ * @param object $license
+ * @return array The response
+ * @since 1.0.0
+ */
protected function prepare_illegal_status_response( $license ) {
$response = array(
'code' => 'illegal_license_status',
@@ -840,6 +1188,13 @@ class License_API {
return $response;
}
+ /**
+ * Prepare max domains response
+ *
+ * @param object $license
+ * @return array The response
+ * @since 1.0.0
+ */
protected function prepare_max_domains_response( $license ) {
return array(
'code' => 'max_domains_reached',
@@ -850,6 +1205,13 @@ class License_API {
);
}
+ /**
+ * Prepare already activated response
+ *
+ * @param string $domain
+ * @return array The response
+ * @since 1.0.0
+ */
protected function prepare_already_activated_response( $domain ) {
return array(
'code' => 'license_already_activated',
@@ -860,10 +1222,25 @@ class License_API {
);
}
+ /**
+ * Process license activation
+ *
+ * @param object $license
+ * @param string $domain
+ * @return array|null The result or null if not found
+ * @since 1.0.0
+ */
protected function process_license_activation( $license, $domain ) {
$data = isset( $license->data ) ? $license->data : array();
if ( ! isset( $data['next_deactivate'] ) || time() > $data['next_deactivate'] ) {
+ /**
+ * Filter the timestamp for the next allowed deactivation after activation.
+ *
+ * @param int $timestamp The timestamp for the next allowed deactivation.
+ * @param object $license The license object.
+ * @since 1.0.0
+ */
$data['next_deactivate'] = apply_filters( 'upserv_activate_license_next_deactivate', time(), $license );
}
@@ -876,6 +1253,12 @@ class License_API {
try {
$result = $this->license_server->edit_license(
+ /**
+ * Filter the payload for license activation.
+ *
+ * @param array $payload The license activation payload.
+ * @since 1.0.0
+ */
apply_filters( 'upserv_activate_license_payload', $payload )
);
} catch ( Exception $e ) {
@@ -898,6 +1281,14 @@ class License_API {
return null;
}
+ /**
+ * Handle license deactivation
+ *
+ * @param object $license
+ * @param string $domain
+ * @return array|null The result or null if not found
+ * @since 1.0.0
+ */
protected function handle_license_deactivation( $license, $domain ) {
if ( in_array( $license->status, array( 'expired', 'blocked', 'on-hold' ), true ) ) {
@@ -920,6 +1311,13 @@ class License_API {
return $this->process_license_deactivation( $license, $domain );
}
+ /**
+ * Prepare already deactivated response
+ *
+ * @param string $domain
+ * @return array The response
+ * @since 1.0.0
+ */
protected function prepare_already_deactivated_response( $domain ) {
return array(
'code' => 'license_already_deactivated',
@@ -930,6 +1328,13 @@ class License_API {
);
}
+ /**
+ * Prepare too early deactivation response
+ *
+ * @param object $license
+ * @return array The response
+ * @since 1.0.0
+ */
protected function prepare_too_early_deactivation_response( $license ) {
return array(
'code' => 'too_early_deactivation',
@@ -940,8 +1345,23 @@ class License_API {
);
}
+ /**
+ * Process license deactivation
+ *
+ * @param object $license
+ * @param string $domain
+ * @return array|null The result or null if not found
+ * @since 1.0.0
+ */
protected function process_license_deactivation( $license, $domain ) {
- $data = isset( $license->data ) ? $license->data : array();
+ $data = isset( $license->data ) ? $license->data : array();
+ /**
+ * Filter the timestamp for the next allowed deactivation.
+ *
+ * @param int $timestamp The timestamp for the next allowed deactivation.
+ * @param object $license The license object.
+ * @since 1.0.0
+ */
$data['next_deactivate'] = apply_filters(
'upserv_deactivate_license_next_deactivate',
(bool) ( constant( 'WP_DEBUG' ) ) ? time() + ( MINUTE_IN_SECONDS / 4 ) : time() + MONTH_IN_SECONDS,
@@ -958,6 +1378,12 @@ class License_API {
try {
$result = $this->license_server->edit_license(
+ /**
+ * Filter the payload for license deactivation.
+ *
+ * @param array $payload The license deactivation payload.
+ * @since 1.0.0
+ */
apply_filters( 'upserv_activate_license_payload', $payload )
);
} catch ( Exception $e ) {
@@ -980,6 +1406,14 @@ class License_API {
return null;
}
+ /**
+ * Handle invalid license
+ *
+ * @param array $license
+ * @param array $license_data
+ * @return array The response
+ * @since 1.0.0
+ */
protected function handle_invalid_license( $license, $license_data ) {
if ( is_array( $license ) && isset( $license['license_not_found'] ) ) {
@@ -1007,6 +1441,14 @@ class License_API {
);
}
+ /**
+ * Authorize private API access
+ *
+ * @param string $action
+ * @param array $payload
+ * @return boolean True if authorized, false otherwise
+ * @since 1.0.0
+ */
protected function authorize_private( $action, $payload ) {
$token = false;
$is_auth = false;
@@ -1067,7 +1509,20 @@ class License_API {
return $is_auth;
}
+ /**
+ * Is API public
+ *
+ * @param string $method
+ * @return boolean True if public, false otherwise
+ * @since 1.0.0
+ */
protected function is_api_public( $method ) {
+ /**
+ * Filter the list of public License API actions.
+ *
+ * @param array $public_api_actions List of public License API actions.
+ * @since 1.0.0
+ */
$public_api = apply_filters(
'upserv_license_public_api_actions',
array(
@@ -1081,6 +1536,11 @@ class License_API {
return $is_api_public;
}
+ /**
+ * Handle API request
+ *
+ * @since 1.0.0
+ */
protected function handle_api_request() {
global $wp;
@@ -1114,6 +1574,16 @@ class License_API {
}
if ( ! $malformed_request ) {
+ $this->init_server();
+
+ /**
+ * Filter whether the License API request is authorized.
+ *
+ * @param bool $authorized Whether the License API request is authorized.
+ * @param string $method The method of the request.
+ * @param array $payload The payload of the request.
+ * @since 1.0.0
+ */
$authorized = apply_filters(
'upserv_license_api_request_authorized',
(
@@ -1128,11 +1598,16 @@ class License_API {
);
if ( $authorized ) {
+ /**
+ * Fired before the License API request is processed.
+ *
+ * @param string $method The License API action.
+ * @param array $payload The payload of the request.
+ * @since 1.0.0
+ */
do_action( 'upserv_license_api_request', $method, $payload );
if ( method_exists( $this, $method ) ) {
- $this->init_server();
-
$response = $this->$method( $payload );
if ( is_object( $response ) && ! empty( get_object_vars( $response ) ) ) {
@@ -1164,6 +1639,12 @@ class License_API {
wp_send_json( $response, $this->http_response_code, Utils::JSON_OPTIONS );
}
+ /**
+ * Authorize IP
+ *
+ * @return boolean True if authorized, false otherwise
+ * @since 1.0.0
+ */
protected function authorize_ip() {
$result = false;
$config = self::get_config();
@@ -1185,7 +1666,18 @@ class License_API {
return $result;
}
+ /**
+ * Init server
+ *
+ * @since 1.0.0
+ */
protected function init_server() {
+ /**
+ * Filter the License Server instance.
+ *
+ * @param License_Server $license_server The license server instance.
+ * @since 1.0.0
+ */
$this->license_server = apply_filters( 'upserv_license_server', new License_Server() );
}
}
diff --git a/inc/api/class-package-api.php b/inc/api/class-package-api.php
index 2212b00..5d2ed3c 100644
--- a/inc/api/class-package-api.php
+++ b/inc/api/class-package-api.php
@@ -14,16 +14,63 @@ use Anyape\UpdatePulse\Server\Server\Update\Package;
use Anyape\UpdatePulse\Server\Server\Update\Invalid_Package_Exception;
use Anyape\Utils\Utils;
+/**
+ * Package API class
+ *
+ * @since 1.0.0
+ */
class Package_API {
- protected $http_response_code = 200;
- protected $api_key_id;
- protected $api_access;
-
+ /**
+ * Is doing API request
+ *
+ * @var bool|null
+ * @since 1.0.0
+ */
protected static $doing_api_request = null;
+ /**
+ * Instance
+ *
+ * @var Package_API|null
+ * @since 1.0.0
+ */
protected static $instance;
+ /**
+ * Config
+ *
+ * @var array|null
+ * @since 1.0.0
+ */
protected static $config;
+ /**
+ * HTTP response code
+ *
+ * @var int|null
+ * @since 1.0.0
+ */
+ protected $http_response_code = 200;
+ /**
+ * API key ID
+ *
+ * @var string|null
+ * @since 1.0.0
+ */
+ protected $api_key_id;
+ /**
+ * API access
+ *
+ * @var array|null
+ * @since 1.0.0
+ */
+ protected $api_access;
+
+ /**
+ * Constructor
+ *
+ * @param boolean $init_hooks
+ * @since 1.0.0
+ */
public function __construct( $init_hooks = false ) {
if ( $init_hooks ) {
@@ -48,14 +95,37 @@ class Package_API {
// API action --------------------------------------------------
+ /**
+ * Browse packages
+ *
+ * Get information about multiple packages.
+ *
+ * @param string|array $query The search query or parameters.
+ * @return object Response with package information.
+ * @since 1.0.0
+ */
public function browse( $query ) {
$result = false;
$query = empty( $query ) || ! is_string( $query ) ? array() : json_decode( wp_unslash( $query ), true );
$query['search'] = isset( $query['search'] ) ? trim( esc_html( $query['search'] ) ) : false;
$result = upserv_get_batch_package_info( $query['search'], false );
$result['count'] = is_array( $result ) ? count( $result ) : 0;
- $result = apply_filters( 'upserv_package_browse', $result, $query );
+ /**
+ * Filter the result of the `browse` operation of the Package API.
+ *
+ * @param array $result The result of the `browse` operation
+ * @param array $query The query - see browse()
+ * @return array The filtered result
+ * @since 1.0.0
+ */
+ $result = apply_filters( 'upserv_package_browse', $result, $query );
+ /**
+ * Fired after the `browse` Package API action.
+ *
+ * @param array $result the result of the action
+ * @since 1.0.0
+ */
do_action( 'upserv_did_browse_package', $result );
if ( empty( $result ) ) {
@@ -70,9 +140,19 @@ class Package_API {
);
}
- return $result;
+ return (object) $result;
}
+ /**
+ * Read package information
+ *
+ * Get information about a single package.
+ *
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return object Response with package information.
+ * @since 1.0.0
+ */
public function read( $package_id, $type ) {
$result = upserv_get_package_info( $package_id, false );
@@ -86,8 +166,23 @@ class Package_API {
unset( $result['file_path'] );
}
+ /**
+ * Filter the result of the `read` operation of the Package API.
+ *
+ * @param array $result The result of the `read` operation
+ * @param string $package_id The slug of the read package
+ * @param string $type The type of the read package
+ * @return array The filtered result
+ * @since 1.0.0
+ */
$result = apply_filters( 'upserv_package_read', $result, $package_id, $type );
+ /**
+ * Fired after the `read` Package API action.
+ *
+ * @param array $result the result of the action
+ * @since 1.0.0
+ */
do_action( 'upserv_did_read_package', $result );
if ( ! $result ) {
@@ -98,9 +193,19 @@ class Package_API {
);
}
- return $result;
+ return (object) $result;
}
+ /**
+ * Edit a package
+ *
+ * If a package exists, update it by uploading a valid package file, or by downloading it if using a VCS.
+ *
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return object Response with package information or error.
+ * @since 1.0.0
+ */
public function edit( $package_id, $type ) {
$result = false;
$config = self::get_config();
@@ -118,6 +223,15 @@ class Package_API {
$result = $result && ! is_wp_error( $result ) ? upserv_get_package_info( $package_id, false ) : $result;
}
+ /**
+ * Filter the result of the `edit` operation of the Package API.
+ *
+ * @param array $result The result of the `edit` operation
+ * @param string $package_id The slug of the edited package
+ * @param string $type The type of the edited package
+ * @return array The filtered result
+ * @since 1.0.0
+ */
$result = apply_filters( 'upserv_package_edit', $result, $package_id, $type );
if ( empty( $exists ) ) {
@@ -139,12 +253,28 @@ class Package_API {
'message' => __( 'Package could not be edited - invalid parameters.', 'updatepulse-server' ),
);
} else {
+ /**
+ * Fired after the `edit` Package API action.
+ *
+ * @param array $result the result of the action
+ * @since 1.0.0
+ */
do_action( 'upserv_did_edit_package', $result );
}
- return $result;
+ return (object) $result;
}
+ /**
+ * Add a package
+ *
+ * If a package does not exist, upload it by providing a valid package file, or download it if using a VCS.
+ *
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return object Response with package information or error.
+ * @since 1.0.0
+ */
public function add( $package_id, $type ) {
$result = false;
$config = self::get_config();
@@ -162,6 +292,15 @@ class Package_API {
$result = $result && ! is_wp_error( $result ) ? upserv_get_package_info( $package_id, false ) : $result;
}
+ /**
+ * Filter the result of the `add` operation of the Package API.
+ *
+ * @param array $result The result of the `add` operation
+ * @param string $package_id The slug of the added package
+ * @param string $type The type of the added package
+ * @return array The filtered result
+ * @since 1.0.0
+ */
$result = apply_filters( 'upserv_package_add', $result, $package_id, $type );
if ( ! empty( $exists ) ) {
@@ -183,19 +322,59 @@ class Package_API {
'message' => __( 'Package could not be added - invalid parameters.', 'updatepulse-server' ),
);
} else {
+ /**
+ * Fired after the `add` Package API action.
+ *
+ * @param array $result the result of the action
+ * @since 1.0.0
+ */
do_action( 'upserv_did_add_package', $result );
}
- return $result;
+ return (object) $result;
}
+ /**
+ * Delete a package
+ *
+ * Remove a package from the system.
+ *
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return object Response with deletion status or error.
+ * @since 1.0.0
+ */
public function delete( $package_id, $type ) {
+ /**
+ * Fired before the `delete` Package API action.
+ *
+ * @param string $package_slug the slug of the package to be deleted
+ * @param string $type the type of the package to be deleted
+ * @since 1.0.0
+ */
do_action( 'upserv_pre_delete_package', $package_id, $type );
$result = upserv_delete_package( $package_id );
+ /**
+ * Filter the result of the `delete` operation of the Package API.
+ *
+ * @param bool $result The result of the `delete` operation
+ * @param string $package_id The slug of the deleted package
+ * @param string $type The type of the deleted package
+ * @return bool The filtered result
+ * @since 1.0.0
+ */
$result = apply_filters( 'upserv_package_delete', $result, $package_id, $type );
if ( $result ) {
+ /**
+ * Fired after the `delete` Package API action.
+ *
+ * @param bool $result the result of the `delete` operation
+ * @param string $package_slug the slug of the deleted package
+ * @param string $type the type of the deleted package
+ * @since 1.0.0
+ */
do_action( 'upserv_did_delete_package', $result, $package_id, $type );
} else {
$this->http_response_code = 404;
@@ -205,9 +384,19 @@ class Package_API {
);
}
- return $result;
+ return (object) $result;
}
+ /**
+ * Download a package
+ *
+ * Initiate download of a package file.
+ *
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return array Error information if package not found.
+ * @since 1.0.0
+ */
public function download( $package_id, $type ) {
$path = upserv_get_local_package_path( $package_id );
@@ -222,15 +411,40 @@ class Package_API {
}
upserv_download_local_package( $package_id, $path, false );
+ /**
+ * Fired after the `download` Package API action.
+ *
+ * @param string $package_slug the slug of the downloaded package
+ * @since 1.0.0
+ */
do_action( 'upserv_did_download_package', $package_id );
exit;
}
+ /**
+ * Generate signed URL for package download
+ *
+ * Create a secure URL for downloading packages.
+ *
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return object Response with signed URL information.
+ * @since 1.0.0
+ */
public function signed_url( $package_id, $type ) {
$package_id = filter_var( $package_id, FILTER_SANITIZE_URL );
$type = filter_var( $type, FILTER_SANITIZE_URL );
- $token = apply_filters( 'upserv_package_signed_url_token', false, $package_id, $type );
+ /**
+ * Filter the token used to sign the URL.
+ *
+ * @param mixed $token The token used to sign the URL
+ * @param string $package_id The slug of the package for which the URL needs to be signed
+ * @param string $type The type of the package for which the URL needs to be signed
+ * @return mixed The filtered token
+ * @since 1.0.0
+ */
+ $token = apply_filters( 'upserv_package_signed_url_token', false, $package_id, $type );
if ( ! $token ) {
$token = upserv_create_nonce(
@@ -244,6 +458,15 @@ class Package_API {
);
}
+ /**
+ * Filter the result of the `signed_url` operation of the Package API.
+ *
+ * @param array $result The result of the `signed_url` operation
+ * @param string $package_id The slug of the package for which the URL was signed
+ * @param string $type The type of the package for which the URL was signed
+ * @return array The filtered result
+ * @since 1.0.0
+ */
$result = apply_filters(
'upserv_package_signed_url',
array(
@@ -262,6 +485,12 @@ class Package_API {
);
if ( $result ) {
+ /**
+ * Fired after the `signed_url` Package API action.
+ *
+ * @param array $result the result of the action
+ * @since 1.0.0
+ */
do_action( 'upserv_did_signed_url_package', $result );
} else {
$this->http_response_code = 404;
@@ -271,18 +500,24 @@ class Package_API {
);
}
- return $result;
+ return (object) $result;
}
// WordPress hooks ---------------------------------------------
+ /**
+ * Add API endpoints
+ *
+ * Register the rewrite rules for the Package API endpoints.
+ *
+ * @since 1.0.0
+ */
public function add_endpoints() {
add_rewrite_rule(
'^updatepulse-server-package-api/(plugin|theme|generic)/(.+)/*?$',
'index.php?type=$matches[1]&package_id=$matches[2]&$matches[3]&__upserv_package_api=1&',
'top'
);
-
add_rewrite_rule(
'^updatepulse-server-package-api/*?$',
'index.php?$matches[1]&__upserv_package_api=1&',
@@ -290,6 +525,13 @@ class Package_API {
);
}
+ /**
+ * Parse API requests
+ *
+ * Handle incoming API requests to the Package API endpoints.
+ *
+ * @since 1.0.0
+ */
public function parse_request() {
global $wp;
@@ -300,6 +542,15 @@ class Package_API {
}
}
+ /**
+ * Register query variables
+ *
+ * Add custom query variables used by the Package API.
+ *
+ * @param array $query_vars Existing query variables.
+ * @return array Modified query variables.
+ * @since 1.0.0
+ */
public function query_vars( $query_vars ) {
$query_vars = array_merge(
$query_vars,
@@ -318,6 +569,16 @@ class Package_API {
return $query_vars;
}
+ /**
+ * Handle package saved to local event
+ *
+ * Actions to perform when a remote package has been saved locally.
+ *
+ * @param bool $local_ready Whether the local package is ready.
+ * @param string $package_type The type of the package.
+ * @param string $package_slug The slug of the package.
+ * @since 1.0.0
+ */
public function upserv_saved_remote_package_to_local( $local_ready, $package_type, $package_slug ) {
if ( ! $local_ready ) {
@@ -336,6 +597,15 @@ class Package_API {
upserv_schedule_webhook( $payload, 'package' );
}
+ /**
+ * Handle pre-delete package event
+ *
+ * Actions to perform before a package is deleted.
+ *
+ * @param string $package_slug The slug of the package.
+ * @param string $package_type The type of the package.
+ * @since 1.0.0
+ */
public function upserv_pre_delete_package( $package_slug, $package_type ) {
wp_cache_set(
'upserv_package_deleted_info' . $package_slug . '_' . $package_type,
@@ -344,6 +614,16 @@ class Package_API {
);
}
+ /**
+ * Handle post-delete package event
+ *
+ * Actions to perform after a package is deleted.
+ *
+ * @param bool $result The result of the deletion.
+ * @param string $package_slug The slug of the package.
+ * @param string $package_type The type of the package.
+ * @since 1.0.0
+ */
public function upserv_did_delete_package( $result, $package_slug, $package_type ) {
$package_info = wp_cache_get(
'upserv_package_deleted_info' . $package_slug . '_' . $package_type,
@@ -362,6 +642,14 @@ class Package_API {
}
}
+ /**
+ * Handle package downloaded event
+ *
+ * Actions to perform after a package is downloaded.
+ *
+ * @param string $package_slug The slug of the downloaded package.
+ * @since 1.0.0
+ */
public function upserv_did_download_package( $package_slug ) {
$payload = array(
'event' => 'package_downloaded',
@@ -373,6 +661,15 @@ class Package_API {
upserv_schedule_webhook( $payload, 'package' );
}
+ /**
+ * Register package API actions
+ *
+ * Add descriptions for all available Package API actions.
+ *
+ * @param array $actions Existing API actions.
+ * @return array Modified API actions with descriptions.
+ * @since 1.0.0
+ */
public function upserv_api_package_actions( $actions ) {
$actions['browse'] = __( 'Get information about multiple packages', 'updatepulse-server' );
$actions['read'] = __( 'Get information about a single package', 'updatepulse-server' );
@@ -384,6 +681,15 @@ class Package_API {
return $actions;
}
+ /**
+ * Register webhook events
+ *
+ * Add supported webhook events for the Package API.
+ *
+ * @param array $webhook_events Existing webhook events.
+ * @return array Modified webhook events.
+ * @since 1.0.0
+ */
public function upserv_api_webhook_events( $webhook_events ) {
if ( isset( $webhook_events['package'], $webhook_events['package']['events'] ) ) {
@@ -395,6 +701,18 @@ class Package_API {
return $webhook_events;
}
+ /**
+ * Fetch nonce for public API
+ *
+ * Validate nonce for public API requests.
+ *
+ * @param mixed $nonce The nonce to validate.
+ * @param mixed $true_nonce The true nonce value.
+ * @param int $expiry The nonce expiry time.
+ * @param array $data Additional data associated with the nonce.
+ * @return mixed Validated nonce or null if invalid.
+ * @since 1.0.0
+ */
public function upserv_fetch_nonce_public( $nonce, $true_nonce, $expiry, $data ) {
global $wp;
@@ -423,6 +741,18 @@ class Package_API {
return $nonce;
}
+ /**
+ * Fetch nonce for private API
+ *
+ * Validate nonce for private API requests.
+ *
+ * @param mixed $nonce The nonce to validate.
+ * @param mixed $true_nonce The true nonce value.
+ * @param int $expiry The nonce expiry time.
+ * @param array $data Additional data associated with the nonce.
+ * @return mixed Validated nonce or null if invalid.
+ * @since 1.0.0
+ */
public function upserv_fetch_nonce_private( $nonce, $true_nonce, $expiry, $data ) {
$config = self::get_config();
$valid = false;
@@ -460,6 +790,15 @@ class Package_API {
return $nonce;
}
+ /**
+ * Modify nonce API payload
+ *
+ * Adjust the payload for API nonce creation.
+ *
+ * @param array $payload The original payload.
+ * @return array Modified payload.
+ * @since 1.0.0
+ */
public function upserv_nonce_api_payload( $payload ) {
global $wp;
@@ -503,12 +842,30 @@ class Package_API {
return $payload;
}
+ /**
+ * Filter package information inclusion
+ *
+ * Determine whether to include package information in responses.
+ *
+ * @param bool $_include Current inclusion status.
+ * @param array $info Package information.
+ * @return bool Whether to include the package information.
+ * @since 1.0.0
+ */
public function upserv_package_info_include( $_include, $info ) {
return ! upserv_get_option( 'use_vcs' ) || upserv_is_package_whitelisted( $info['slug'] );
}
// Misc. -------------------------------------------------------
+ /**
+ * Check if currently processing an API request
+ *
+ * Determine whether the current request is a Package API request.
+ *
+ * @return bool Whether the current request is a Package API request.
+ * @since 1.0.0
+ */
public static function is_doing_api_request() {
if ( null === self::$doing_api_request ) {
@@ -518,6 +875,14 @@ class Package_API {
return self::$doing_api_request;
}
+ /**
+ * Get Package API configuration
+ *
+ * Retrieve and filter the Package API configuration settings.
+ *
+ * @return array Package API configuration.
+ * @since 1.0.0
+ */
public static function get_config() {
if ( ! self::$config ) {
@@ -530,9 +895,24 @@ class Package_API {
self::$config = $config;
}
+ /**
+ * Filter the configuration of the Package API.
+ *
+ * @param array $config The configuration of the Package API
+ * @return array The filtered configuration
+ * @since 1.0.0
+ */
return apply_filters( 'upserv_package_api_config', self::$config );
}
+ /**
+ * Get Package API instance
+ *
+ * Retrieve or create the Package API singleton instance.
+ *
+ * @return Package_API The Package API instance.
+ * @since 1.0.0
+ */
public static function get_instance() {
if ( ! self::$instance ) {
@@ -546,6 +926,14 @@ class Package_API {
* Protected methods
*******************************************************************/
+ /**
+ * Get uploaded file
+ *
+ * Retrieve the uploaded file from a request.
+ *
+ * @return array|false File information array or false if no valid file.
+ * @since 1.0.0
+ */
protected function get_file() {
$files = $_FILES; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$return = false;
@@ -562,6 +950,17 @@ class Package_API {
return $return;
}
+ /**
+ * Process uploaded package file
+ *
+ * Handle validation and processing of an uploaded package file.
+ *
+ * @param array $file The file information array.
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return bool|WP_Error True on success, WP_Error on failure.
+ * @since 1.0.0
+ */
protected function process_file( $file, $package_id, $type ) {
list(
$local_filename,
@@ -656,11 +1055,30 @@ class Package_API {
upserv_set_package_metadata( $package_id, $meta );
}
+ /**
+ * Fired after an attempt to save a downloaded package on the file system has been performed.
+ * Fired during client update API request.
+ *
+ * @param bool $result `true` in case of success, `false` otherwise
+ * @param string $type type of the saved package - `"Plugin"`, `"Theme"`, or `"Generic"`
+ * @param string $package_slug slug of the saved package
+ * @since 1.0.0
+ */
do_action( 'upserv_saved_remote_package_to_local', true, $type, $package_id );
return $result;
}
+ /**
+ * Download a package file from VCS
+ *
+ * Fetch a package from its version control system source.
+ *
+ * @param string $package_id The package ID/slug.
+ * @param string $type The package type.
+ * @return bool|WP_Error True on success, WP_Error on failure.
+ * @since 1.0.0
+ */
protected function download_file( $package_id, $type ) {
$vcs_url = filter_input( INPUT_POST, 'vcs_url', FILTER_SANITIZE_URL );
$branch = sanitize_text_field( wp_unslash( filter_input( INPUT_POST, 'branch' ) ) );
@@ -676,6 +1094,14 @@ class Package_API {
return $result;
}
+ /**
+ * Authorize public API request
+ *
+ * Validate authorization for public API endpoints.
+ *
+ * @return bool Whether the request is authorized.
+ * @since 1.0.0
+ */
protected function authorize_public() {
$nonce = sanitize_text_field( wp_unslash( filter_input( INPUT_GET, 'token' ) ) );
@@ -692,6 +1118,15 @@ class Package_API {
return $result;
}
+ /**
+ * Authorize private API request
+ *
+ * Validate authorization for private API endpoints.
+ *
+ * @param string $action The requested API action.
+ * @return bool Whether the request is authorized.
+ * @since 1.0.0
+ */
protected function authorize_private( $action ) {
$token = false;
$is_auth = false;
@@ -726,7 +1161,24 @@ class Package_API {
return $is_auth;
}
+ /**
+ * Check if API action is public
+ *
+ * Determine if a specific API action is available publicly.
+ *
+ * @param string $method The API method to check.
+ * @return bool Whether the API action is public.
+ * @since 1.0.0
+ */
protected function is_api_public( $method ) {
+ /**
+ * Filter the public API actions; public actions can be accessed via the `GET` method and a token,
+ * all other actions are considered private and can only be accessed via the `POST` method.
+ *
+ * @param array $public_api_actions The public API actions
+ * @return array The filtered public API actions
+ * @since 1.0.0
+ */
$public_api = apply_filters(
'upserv_package_public_api_actions',
array( 'download' )
@@ -736,6 +1188,13 @@ class Package_API {
return $is_api_public;
}
+ /**
+ * Handle incoming API requests
+ *
+ * Process and respond to Package API requests.
+ *
+ * @since 1.0.0
+ */
protected function handle_api_request() {
global $wp;
@@ -765,6 +1224,15 @@ class Package_API {
}
if ( ! $malformed_request ) {
+ /**
+ * Filter whether the Package API request is authorized
+ *
+ * @param bool $authorized Whether the Package API request is authorized
+ * @param string $method The method of the request - `GET` or `POST`
+ * @param array $payload The payload of the request
+ * @return bool The filtered authorization status
+ * @since 1.0.0
+ */
$authorized = apply_filters(
'upserv_package_api_request_authorized',
(
@@ -782,6 +1250,13 @@ class Package_API {
);
if ( $authorized ) {
+ /**
+ * Fired before the Package API request is processed; useful to bypass the execution of currently implemented actions, or implement new actions.
+ *
+ * @param string $action the Package API action
+ * @param array $payload the payload of the request
+ * @since 1.0.0
+ */
do_action( 'upserv_package_api_request', $method, $payload );
if ( method_exists( $this, $method ) ) {
@@ -823,6 +1298,14 @@ class Package_API {
wp_send_json( $response, $this->http_response_code, Utils::JSON_OPTIONS );
}
+ /**
+ * Authorize request by IP address
+ *
+ * Validate if the request IP is allowed.
+ *
+ * @return bool Whether the request IP is authorized.
+ * @since 1.0.0
+ */
protected function authorize_ip() {
$result = false;
$config = self::get_config();
diff --git a/inc/api/class-update-api.php b/inc/api/class-update-api.php
index 0d5e598..ef3cc1a 100644
--- a/inc/api/class-update-api.php
+++ b/inc/api/class-update-api.php
@@ -10,13 +10,42 @@ use Anyape\UpdatePulse\Server\Manager\Data_Manager;
use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
use Anyape\Utils\Utils;
+/**
+ * Update API class
+ *
+ * @since 1.0.0
+ */
class Update_API {
+ /**
+ * Is doing API request
+ *
+ * @var bool|null
+ * @since 1.0.0
+ */
protected static $doing_api_request = null;
+ /**
+ * Instance
+ *
+ * @var Update_API|null
+ * @since 1.0.0
+ */
protected static $instance;
+ /**
+ * Update server object
+ *
+ * @var object|null
+ * @since 1.0.0
+ */
protected $update_server;
+ /**
+ * Constructor
+ *
+ * @param boolean $init_hooks Whether to initialize hooks.
+ * @since 1.0.0
+ */
public function __construct( $init_hooks = false ) {
if ( $init_hooks ) {
@@ -38,6 +67,13 @@ class Update_API {
// WordPress hooks ---------------------------------------------
+ /**
+ * Add API endpoints
+ *
+ * Register the rewrite rules for the Update API endpoints.
+ *
+ * @since 1.0.0
+ */
public function add_endpoints() {
add_rewrite_rule(
'^updatepulse-server-update-api/*$',
@@ -46,6 +82,13 @@ class Update_API {
);
}
+ /**
+ * Parse API requests
+ *
+ * Handle incoming API requests to the Update API endpoints.
+ *
+ * @since 1.0.0
+ */
public function parse_request() {
global $wp;
@@ -54,6 +97,15 @@ class Update_API {
}
}
+ /**
+ * Register query variables
+ *
+ * Add custom query variables used by the Update API.
+ *
+ * @param array $query_vars Existing query variables.
+ * @return array Modified query variables.
+ * @since 1.0.0
+ */
public function query_vars( $query_vars ) {
$query_vars = array_merge(
$query_vars,
@@ -69,10 +121,29 @@ class Update_API {
return $query_vars;
}
+ /**
+ * Handle checked remote package update event
+ *
+ * Actions to perform when a remote package update has been checked.
+ *
+ * @param bool $needs_update Whether the package needs an update.
+ * @param string $type The type of the package.
+ * @param string $slug The slug of the package.
+ * @since 1.0.0
+ */
public function upserv_checked_remote_package_update( $needs_update, $type, $slug ) {
$this->schedule_check_remote_event( $slug );
}
+ /**
+ * Handle package registered from VCS event
+ *
+ * Actions to perform when a package has been registered from VCS.
+ *
+ * @param bool $result The result of the registration.
+ * @param string $slug The slug of the package.
+ * @since 1.0.0
+ */
public function upserv_registered_package_from_vcs( $result, $slug ) {
if ( $result ) {
@@ -80,6 +151,16 @@ class Update_API {
}
}
+ /**
+ * Handle package removed event
+ *
+ * Actions to perform when a package has been removed.
+ *
+ * @param bool $result The result of the removal.
+ * @param string $type The type of the package.
+ * @param string $slug The slug of the package.
+ * @since 1.0.0
+ */
public function upserv_removed_package( $result, $type, $slug ) {
if ( $result ) {
@@ -87,6 +168,18 @@ class Update_API {
}
}
+ /**
+ * Pre-filter package information
+ *
+ * Filter package information before the update check.
+ *
+ * @param array $info Package information.
+ * @param object $api_obj The API object.
+ * @param mixed $ref Reference value.
+ * @param object $update_checker The update checker object.
+ * @return array Filtered package information.
+ * @since 1.0.0
+ */
public function puc_request_info_pre_filter( $info, $api_obj, $ref, $update_checker ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
$vcs_config = upserv_get_package_vcs_config( $info['slug'] );
@@ -94,6 +187,13 @@ class Update_API {
return $info;
}
+ /**
+ * Filter whether to filter the packages retrieved from the Version Control System.
+ *
+ * @param bool $filter_packages Whether to filter the packages retrieved from the Version Control System.
+ * @param array $info The information of the package from the VCS.
+ * @since 1.0.0
+ */
$filter_packages = apply_filters(
'upserv_vcs_filter_packages',
$vcs_config['filter_packages'],
@@ -109,6 +209,18 @@ class Update_API {
return $info;
}
+ /**
+ * Filter package information result
+ *
+ * Filter package information after the update check.
+ *
+ * @param array $info Package information.
+ * @param object $api_obj The API object.
+ * @param mixed $ref Reference value.
+ * @param object $checker The update checker object.
+ * @return array Filtered package information.
+ * @since 1.0.0
+ */
public function puc_request_info_result( $info, $api_obj, $ref, $checker ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
$vcs_config = upserv_get_package_vcs_config( $info['slug'] );
@@ -116,6 +228,13 @@ class Update_API {
return $info;
}
+ /**
+ * Filter whether to filter the packages retrieved from the Version Control System.
+ *
+ * @param bool $filter_packages Whether to filter the packages retrieved from the Version Control System.
+ * @param array $info The information of the package from the VCS.
+ * @since 1.0.0
+ */
$filter_packages = apply_filters(
'upserv_vcs_filter_packages',
$vcs_config['filter_packages'],
@@ -133,6 +252,14 @@ class Update_API {
// Misc. -------------------------------------------------------
+ /**
+ * Check if currently processing an API request
+ *
+ * Determine whether the current request is an Update API request.
+ *
+ * @return bool Whether the current request is an Update API request.
+ * @since 1.0.0
+ */
public static function is_doing_api_request() {
if ( null === self::$doing_api_request ) {
@@ -142,6 +269,14 @@ class Update_API {
return self::$doing_api_request;
}
+ /**
+ * Get Update API instance
+ *
+ * Retrieve or create the Update API singleton instance.
+ *
+ * @return Update_API The Update API instance.
+ * @since 1.0.0
+ */
public static function get_instance() {
if ( ! self::$instance ) {
@@ -151,6 +286,16 @@ class Update_API {
return self::$instance;
}
+ /**
+ * Check for remote package updates
+ *
+ * Verify if a remote package has updates available.
+ *
+ * @param string $slug The package slug.
+ * @param string $type The package type.
+ * @return bool|mixed Result of the remote update check.
+ * @since 1.0.0
+ */
public function check_remote_update( $slug, $type ) {
$this->init_server( $slug );
@@ -163,6 +308,17 @@ class Update_API {
return $this->update_server->check_remote_package_update( $slug );
}
+ /**
+ * Download a remote package
+ *
+ * Download and process a package from a remote source.
+ *
+ * @param string $slug The package slug.
+ * @param string|null $type The package type.
+ * @param bool $force Whether to force the download.
+ * @return bool Whether the download was successful.
+ * @since 1.0.0
+ */
public function download_remote_package( $slug, $type = null, $force = false ) {
$result = false;
@@ -215,6 +371,14 @@ class Update_API {
* Protected methods
*******************************************************************/
+ /**
+ * Schedule remote check event
+ *
+ * Set up a scheduled event to check for remote package updates.
+ *
+ * @param string $slug The package slug.
+ * @since 1.0.0
+ */
protected function schedule_check_remote_event( $slug ) {
$vcs_config = upserv_get_package_vcs_config( $slug );
@@ -238,6 +402,14 @@ class Update_API {
return;
}
+ /**
+ * Filter the package update remote check frequency set in the configuration.
+ * Fired during client update API request.
+ *
+ * @param string $frequency The frequency set in the configuration.
+ * @param string $package_slug The slug of the package to check for updates.
+ * @since 1.0.0
+ */
$frequency = apply_filters(
'upserv_check_remote_frequency',
$vcs_config['check_frequency'],
@@ -252,6 +424,18 @@ class Update_API {
$params
);
+ /**
+ * Fired after a remote check event has been scheduled for a package.
+ * Fired during client update API request.
+ *
+ * @param bool $result Whether the event was scheduled.
+ * @param string $package_slug Slug of the package for which the event was scheduled.
+ * @param int $timestamp Timestamp for when to run the event the first time after it's been scheduled.
+ * @param string $frequency Frequency at which the event would be ran.
+ * @param string $hook Event hook to fire when the event is ran.
+ * @param array $params Parameters passed to the actions registered to $hook when the event is ran.
+ * @since 1.0.0
+ */
do_action(
'upserv_scheduled_check_remote_event',
$result,
@@ -263,6 +447,13 @@ class Update_API {
);
}
+ /**
+ * Handle API requests
+ *
+ * Process and respond to Update API requests.
+ *
+ * @since 1.0.0
+ */
protected function handle_api_request() {
global $wp;
@@ -288,6 +479,13 @@ class Update_API {
ARRAY_FILTER_USE_KEY
)
);
+ /**
+ * Filter the parameters used to handle the request made by a client plugin, theme, or generic package to the plugin's API.
+ * Fired during client update API request.
+ *
+ * @param array $params The parameters of the request to the API.
+ * @since 1.0.0
+ */
$params = apply_filters( 'upserv_handle_update_request_params', array_merge( $query, $params ) );
$this->init_server( $params['slug'] );
@@ -303,10 +501,25 @@ class Update_API {
);
}
+ /**
+ * Fired before handling the request made by a client plugin, theme, or generic package to the plugin's API.
+ * Fired during client update API request.
+ *
+ * @param array $request_params The parameters or the request to the API.
+ * @since 1.0.0
+ */
do_action( 'upserv_before_handle_update_request', $params );
$this->update_server->handle_request( $params );
}
+ /**
+ * Initialize update server
+ *
+ * Set up the update server for a specific package.
+ *
+ * @param string $slug The package slug.
+ * @since 1.0.0
+ */
protected function init_server( $slug ) {
$check_manual = false;
@@ -341,13 +554,31 @@ class Update_API {
'directory' => Data_Manager::get_data_dir(),
'vcs_config' => isset( $vcs_config ) ? $vcs_config : null,
);
+ /**
+ * Filter the class name to use to instantiate a `Anyape\UpdatePulse\Server\Server\Update\Update_Server` object.
+ * Fired during client update API request.
+ *
+ * @param string $class_name The class name to use to instantiate a `Anyape\UpdatePulse\Server\Server\Update\Update_Server` object.
+ * @param string $package_slug The slug of the package to serve.
+ * @param array $config The configuration to use to serve the package.
+ * @since 1.0.0
+ */
$_class_name = apply_filters(
'upserv_server_class_name',
str_replace( 'API', 'Server\\Update', __NAMESPACE__ ) . '\\Update_Server',
$slug,
$filter_args
);
- $args = apply_filters(
+ /**
+ * Filter the arguments to pass to the constructor of the `Anyape\UpdatePulse\Server\Server\Update\Update_Server` object.
+ * Fired during client update API request.
+ *
+ * @param array $args The arguments to pass to the constructor of the `Anyape\UpdatePulse\Server\Server\Update\Update_Server` object.
+ * @param string $package_slug The slug of the package to serve.
+ * @param array $config The configuration to use to serve the package.
+ * @since 1.0.0
+ */
+ $args = apply_filters(
'upserv_server_constructor_args',
array(
home_url( '/updatepulse-server-update-api/' ),
diff --git a/inc/api/class-webhook-api.php b/inc/api/class-webhook-api.php
index d65b532..4a4522a 100644
--- a/inc/api/class-webhook-api.php
+++ b/inc/api/class-webhook-api.php
@@ -13,14 +13,48 @@ use Anyape\UpdatePulse\Server\Manager\Data_Manager;
use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
use Anyape\Utils\Utils;
+/**
+ * Webhook API class
+ *
+ * @since 1.0.0
+ */
class Webhook_API {
+ /**
+ * Is doing API request
+ *
+ * @var bool|null
+ */
protected static $doing_api_request = null;
+
+ /**
+ * Instance
+ *
+ * @var Webhook_API|null
+ */
protected static $instance;
+ /**
+ * Webhooks configuration
+ *
+ * @var array
+ */
protected $webhooks;
+
+ /**
+ * HTTP response code
+ *
+ * @var int
+ */
protected $http_response_code = 200;
+ /**
+ * Constructor
+ *
+ * @since 1.0.0
+ *
+ * @param boolean $init_hooks Whether to initialize hooks
+ */
public function __construct( $init_hooks = false ) {
$this->webhooks = upserv_get_option( 'api/webhooks', array() );
$vcs_configs = upserv_get_option( 'vcs', array() );
@@ -56,6 +90,11 @@ class Webhook_API {
// WordPress hooks ---------------------------------------------
+ /**
+ * Add API endpoints
+ *
+ * Register the rewrite rules for the Webhook API endpoints.
+ */
public function add_endpoints() {
add_rewrite_rule( '^updatepulse-server-webhook$', 'index.php?__upserv_webhook=1&', 'top' );
add_rewrite_rule(
@@ -65,6 +104,11 @@ class Webhook_API {
);
}
+ /**
+ * Parse API requests
+ *
+ * Handle incoming API requests to the Webhook API endpoints.
+ */
public function parse_request() {
global $wp;
@@ -75,6 +119,14 @@ class Webhook_API {
}
}
+ /**
+ * Register query variables
+ *
+ * Add custom query variables used by the Webhook API.
+ *
+ * @param array $query_vars Existing query variables.
+ * @return array Modified query variables.
+ */
public function query_vars( $query_vars ) {
$query_vars = array_merge(
$query_vars,
@@ -88,6 +140,11 @@ class Webhook_API {
return $query_vars;
}
+ /**
+ * Handle invalid webhook requests
+ *
+ * Display error page for unauthorized webhook requests.
+ */
public function upserv_webhook_invalid_request() {
$protocol = empty( $_SERVER['SERVER_PROTOCOL'] ) ? 'HTTP/1.1' : sanitize_text_field( wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) );
@@ -105,12 +162,36 @@ class Webhook_API {
exit( -1 );
}
+ /**
+ * Process webhook requests
+ *
+ * Determine whether to process webhook requests based on branch matching.
+ * If no branch is specified, the request will be processed to account for events
+ * registered to the webhook that do not have a branch associated with them.
+ *
+ * @param bool $process Current process status.
+ * @param array $payload Request payload.
+ * @param string $slug Package slug.
+ * @param string $type Package type.
+ * @param bool $package_exists Whether package already exists.
+ * @param array $vcs_config Version control system configuration.
+ * @return bool Whether to process the webhook request.
+ */
public function upserv_webhook_process_request( $process, $payload, $slug, $type, $package_exists, $vcs_config ) {
- return $this->get_payload_vcs_branch( $payload ) === $vcs_config['branch'];
+ $branch = $this->get_payload_vcs_branch( $payload );
+
+ return $process && ( $branch === $vcs_config['branch'] || ! $branch );
}
// Misc. -------------------------------------------------------
+ /**
+ * Check if currently processing an API request
+ *
+ * Determine whether the current request is a Webhook API request.
+ *
+ * @return bool Whether the current request is a Webhook API request.
+ */
public static function is_doing_api_request() {
if ( null === self::$doing_api_request ) {
@@ -120,6 +201,13 @@ class Webhook_API {
return self::$doing_api_request;
}
+ /**
+ * Get Webhook API instance
+ *
+ * Retrieve or create the Webhook API singleton instance.
+ *
+ * @return Webhook_API The Webhook API instance.
+ */
public static function get_instance() {
if ( ! self::$instance ) {
@@ -129,6 +217,16 @@ class Webhook_API {
return self::$instance;
}
+ /**
+ * Schedule webhook
+ *
+ * Schedule a webhook to be fired based on an event.
+ *
+ * @param array $payload Webhook payload data.
+ * @param string $event_type Event type identifier.
+ * @param bool $instant Whether to fire webhook immediately.
+ * @return void|WP_Error WP_Error on failure.
+ */
public function schedule_webhook( $payload, $event_type, $instant = false ) {
if ( empty( $this->webhooks ) ) {
@@ -169,12 +267,29 @@ class Webhook_API {
}
}
+ /**
+ * Filter whether to fire the webhook event.
+ *
+ * @param bool $fire Whether to fire the event.
+ * @param array $payload The payload of the event.
+ * @param string $url The target url of the event.
+ * @param array $webhook_setting The settings of the webhook.
+ * @return bool
+ */
if ( apply_filters( 'upserv_webhook_fire', $fire, $payload, $info['url'], $info ) ) {
$body = wp_json_encode( $payload, Utils::JSON_OPTIONS );
$hook = 'upserv_webhook';
$params = array( $info['url'], $info['secret'], $body, current_action() );
if ( ! Scheduler::get_instance()->has_scheduled_action( $hook, $params ) ) {
+ /**
+ * Filter whether to send the webhook notification immediately.
+ *
+ * @param bool $instant Whether to send the notification immediately.
+ * @param array $payload The payload of the event.
+ * @param string $event_type The type of event.
+ * @return bool
+ */
$instant = apply_filters(
'upserv_schedule_webhook_is_instant',
$instant,
@@ -194,6 +309,17 @@ class Webhook_API {
}
}
+ /**
+ * Fire webhook
+ *
+ * Send an HTTP request to the webhook endpoint.
+ *
+ * @param string $url Webhook endpoint URL.
+ * @param string $secret Secret key for signature.
+ * @param string $body Request body.
+ * @param string $action Current action.
+ * @return array|WP_Error HTTP response or WP_Error on failure.
+ */
public function fire_webhook( $url, $secret, $body, $action ) {
return wp_remote_post(
$url,
@@ -213,6 +339,11 @@ class Webhook_API {
* Protected methods
*******************************************************************/
+ /**
+ * Handle remote test
+ *
+ * Process and respond to webhook test requests.
+ */
protected function handle_remote_test() {
if ( empty( $_SERVER['HTTP_X_UPDATEPULSE_SIGNATURE_256'] ) ) {
@@ -266,6 +397,11 @@ class Webhook_API {
wp_send_json( $valid, $valid ? 200 : 403, Utils::JSON_OPTIONS );
}
+ /**
+ * Handle API request
+ *
+ * Process webhook API requests and return appropriate responses.
+ */
protected function handle_api_request() {
global $wp;
@@ -273,14 +409,34 @@ class Webhook_API {
$this->handle_remote_test();
}
- $response = array();
- $payload = $this->get_payload();
- $url = $this->get_payload_vcs_url( $payload );
- $branch = $this->get_payload_vcs_branch( $payload );
- $vcs_configs = upserv_get_option( 'vcs', array() );
- $vcs_key = hash( 'sha256', trailingslashit( $url ) . '|' . $branch );
- $vcs_config = isset( $vcs_configs[ $vcs_key ] ) ? $vcs_configs[ $vcs_key ] : false;
+ $response = array();
+ $payload = $this->get_payload();
+ $url = $this->get_payload_vcs_url( $payload );
+ $branch = $this->get_payload_vcs_branch( $payload );
+ $vcs_configs = upserv_get_option( 'vcs', array() );
+ $vcs_key = hash( 'sha256', trailingslashit( $url ) . '|' . $branch );
+ $vcs_config = isset( $vcs_configs[ $vcs_key ] ) ? $vcs_configs[ $vcs_key ] : false;
+ $vcs_candidates = $vcs_config ? array( $vcs_key => $vcs_config ) : array();
+ if ( empty( $vcs_candidates ) ) {
+
+ foreach ( $vcs_configs as $config ) {
+
+ if ( 0 === strpos( $config['url'], trailingslashit( $url ) ) ) {
+ $vcs_candidates[] = $config;
+ }
+ }
+ }
+
+ if ( 1 === count( $vcs_candidates ) ) {
+ $vcs_config = reset( $vcs_candidates );
+ }
+
+ /**
+ * Fired before handling a webhook request; fired whether it will be processed or not.
+ *
+ * @param array $config The configuration used to handle webhook requests.
+ */
do_action( 'upserv_webhook_before_handling_request', $vcs_config );
if ( $vcs_config && $this->validate_request( $vcs_config ) ) {
@@ -290,10 +446,19 @@ class Webhook_API {
$type = isset( $wp->query_vars['type'] ) ?
trim( rawurldecode( $wp->query_vars['type'] ) ) :
null;
- $delay = $vcs_config['check_delay'];
+ $delay = $vcs_config ? $vcs_config['check_delay'] : 0;
$dir = Data_Manager::get_data_dir( 'packages' );
$package_exists = null;
- $payload = $payload ? wp_json_encode( $payload ) : false;
+ /**
+ * Filter whether the package exists on the file system before processing the Webhook.
+ *
+ * @param bool|null $package_exists Whether the package exists on the file system; return `null` to leave the decision to the default behavior.
+ * @param array $payload The payload of the request.
+ * @param string $slug The slug of the package.
+ * @param string $type The type of the package.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ * @return bool|null
+ */
$package_exists = apply_filters(
'upserv_webhook_package_exists',
$package_exists,
@@ -308,6 +473,17 @@ class Webhook_API {
$package_exists = file_exists( $package_path );
}
+ /**
+ * Filter whether to process the Webhook request.
+ *
+ * @param bool $process Whether to process the Webhook request.
+ * @param array $payload The payload of the request.
+ * @param string $slug The slug of the package.
+ * @param string $type The type of the package.
+ * @param bool $package_exists Whether the package exists on the file system.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ * @return bool
+ */
$process = apply_filters(
'upserv_webhook_process_request',
true,
@@ -319,6 +495,15 @@ class Webhook_API {
);
if ( $process ) {
+ /**
+ * Fired before processing a webhook request.
+ *
+ * @param array $payload The data sent by the Version Control System.
+ * @param string $slug The slug of the package triggering the webhook.
+ * @param string $type The type of the package triggering the webhook.
+ * @param bool $package_exists Whether the package exists on the file system.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ */
do_action(
'upserv_webhook_before_processing_request',
$payload,
@@ -340,15 +525,38 @@ class Webhook_API {
if ( ! $scheduled_action ) {
Scheduler::get_instance()->unschedule_all_actions( $hook );
+ /**
+ * Fired after a remote check schedule event has been unscheduled for a package.
+ *
+ * @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, $hook );
}
+ /**
+ * Filter the delay time for remote package checks.
+ *
+ * @param int $delay The delay time in minutes.
+ * @param string $slug The slug of the package.
+ * @return int
+ */
$delay = apply_filters( 'upserv_check_remote_delay', $delay, $slug );
$timestamp = ( $delay ) ?
time() + ( abs( intval( $delay ) ) * MINUTE_IN_SECONDS ) :
time();
$result = Scheduler::get_instance()->schedule_single_action( $timestamp, $hook, $params );
+ /**
+ * Fired after scheduling a remote check event.
+ *
+ * @param bool $result Whether the event was successfully scheduled.
+ * @param string $slug The slug of the package triggering the webhook.
+ * @param int $timestamp The timestamp when the event is scheduled to run.
+ * @param bool $is_cron Whether the event is a cron job.
+ * @param string $hook The hook name for the scheduled event.
+ * @param array $params The parameters passed to the scheduled event.
+ */
do_action(
'upserv_scheduled_check_remote_event',
$result,
@@ -382,6 +590,12 @@ class Webhook_API {
}
} else {
Scheduler::get_instance()->unschedule_all_actions( $hook );
+ /**
+ * Fired after a remote check schedule event has been unscheduled for a package.
+ *
+ * @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, $hook );
$result = upserv_download_remote_package( $slug, $type );
@@ -403,6 +617,15 @@ class Webhook_API {
}
}
+ /**
+ * Fired after processing a webhook request.
+ *
+ * @param array $payload The data sent by the Version Control System.
+ * @param string $slug The slug of the package triggering the webhook.
+ * @param string $type The type of the package triggering the webhook.
+ * @param bool $package_exists Whether the package exists on the file system.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ */
do_action(
'upserv_webhook_after_processing_request',
$payload,
@@ -412,33 +635,92 @@ class Webhook_API {
$vcs_config
);
}
- } elseif ( $vcs_config ) {
+ } elseif ( empty( $vcs_candidates ) ) {
$this->http_response_code = 403;
$response = array(
- 'code' => 'unauthorized',
- 'message' => __( 'Invalid request signature', 'updatepulse-server' ),
+ 'code' => 'invalid_request',
+ 'message' => __( 'Invalid request', 'updatepulse-server' ),
);
-
+ } elseif ( 1 < count( $vcs_candidates ) ) {
+ $this->http_response_code = 409;
+ $response = array(
+ 'code' => 'conflict',
+ 'message' => __( 'Multiple candidate VCS configurations found ; the event has not be processed. Please limit the events sent to the webhook to events specifying the branch in their payload (such as push), or update your UpdatePulse Server VCS configuration to avoid branch conflicts.', 'updatepulse-server' ),
+ 'details' => array(
+ 'vcs_candidates' => array_map(
+ function ( $config ) {
+ return array(
+ 'url' => $config['url'],
+ 'branch' => $config['branch'],
+ );
+ },
+ $vcs_candidates
+ ),
+ ),
+ );
+ } else {
+ /**
+ * Fired when a webhook request is invalid.
+ *
+ * @param array $config The configuration used to handle webhook requests.
+ */
do_action( 'upserv_webhook_invalid_request', $vcs_config );
}
- if ( 200 === $this->http_response_code ) {
- $response['time_elapsed'] = Utils::get_time_elapsed();
- }
+ $response['time_elapsed'] = Utils::get_time_elapsed();
+ /**
+ * Filter the response data to send to the Version Control System after handling the webhook request.
+ *
+ * @param array $response The response data to send to the Version Control System.
+ * @param int $http_response_code The HTTP response code.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ * @return array
+ */
$response = apply_filters( 'upserv_webhook_response', $response, $this->http_response_code, $vcs_config );
+ /**
+ * Fired after handling a webhook request; fired whether it was processed or not.
+ *
+ * @param array $config The configuration used to handle webhook requests.
+ * @param array $response The response data that will be sent to the Version Control System.
+ */
do_action( 'upserv_webhook_after_handling_request', $vcs_config, $response );
wp_send_json( $response, $this->http_response_code, Utils::JSON_OPTIONS );
}
+ /**
+ * Validate webhook request
+ *
+ * Verify webhook request signature against stored secrets.
+ *
+ * @param array $vcs_config Version control system configuration.
+ * @return bool Whether the request signature is valid.
+ */
protected function validate_request( $vcs_config ) {
$valid = false;
$sign = false;
- $secret = $vcs_config && isset( $vcs_config['webhook_secret'] ) ? $vcs_config['webhook_secret'] : false;
+ $secret = isset( $vcs_config['webhook_secret'] ) ? $vcs_config['webhook_secret'] : false;
+
+ /**
+ * Filter the webhook secret used for request validation.
+ *
+ * @param string|bool $secret The secret key for webhook validation.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ * @return string|bool
+ */
$secret = apply_filters( 'upserv_webhook_secret', $secret, $vcs_config );
- if ( ! $vcs_config || ! $secret ) {
+ if ( ! $secret ) {
+ /**
+ * Filter whether the webhook request is valid after validation.
+ *
+ * @param bool $valid Whether the request signature is valid.
+ * @param string|bool $sign The signature from the request.
+ * @param string $secret The secret key for webhook validation.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ * @return bool
+ */
return apply_filters( 'upserv_webhook_validate_request', $valid, $sign, '', $vcs_config );
}
@@ -452,6 +734,14 @@ class Webhook_API {
$sign = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_HUB_SIGNATURE'] ) );
}
+ /**
+ * Filter the signature from the webhook request.
+ *
+ * @param string|bool $sign The signature from the request.
+ * @param string $secret The secret key for webhook validation.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ * @return string|bool
+ */
$sign = apply_filters( 'upserv_webhook_signature', $sign, $secret, $vcs_config );
if ( $sign ) {
@@ -463,9 +753,25 @@ class Webhook_API {
}
}
+ /**
+ * Filter whether the webhook request is valid after validation.
+ *
+ * @param bool $valid Whether the request signature is valid.
+ * @param string|bool $sign The signature from the request.
+ * @param string $secret The secret key for webhook validation.
+ * @param array $vcs_config The configuration used to handle webhook requests.
+ * @return bool
+ */
return apply_filters( 'upserv_webhook_validate_request', $valid, $sign, $secret, $vcs_config );
}
+ /**
+ * Get webhook payload
+ *
+ * Extract and decode the payload from the webhook request.
+ *
+ * @return array Decoded webhook payload.
+ */
protected function get_payload() {
$payload = @file_get_contents( 'php://input' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.PHP.NoSilencedErrors.Discouraged
$decoded = json_decode( $payload, true );
@@ -480,9 +786,17 @@ class Webhook_API {
}
}
- return $decoded;
+ return ! is_array( $decoded ) ? array( 'decoded' => $decoded ) : $decoded;
}
+ /**
+ * Get VCS URL from payload
+ *
+ * Extract the version control system URL from webhook payload.
+ *
+ * @param array $payload Webhook payload.
+ * @return string|false VCS URL or false if not found.
+ */
protected function get_payload_vcs_url( $payload ) {
$url = false;
@@ -501,6 +815,13 @@ class Webhook_API {
$url = $payload['repository']['links']['html']['href'];
}
+ /**
+ * Filter the Version Control System URL extracted from the webhook payload.
+ *
+ * @param string|bool $url The URL of the Version Control System.
+ * @param array $payload The webhook payload data.
+ * @return string|bool
+ */
$url = apply_filters( 'upserv_webhook_vcs_url', $url, $payload );
$parsed_url = wp_parse_url( $url );
@@ -521,6 +842,14 @@ class Webhook_API {
return trailingslashit( $url );
}
+ /**
+ * Get VCS branch from payload
+ *
+ * Extract the branch information from webhook payload.
+ *
+ * @param array $payload Webhook payload.
+ * @return string|false Branch name or false if not found.
+ */
protected function get_payload_vcs_branch( $payload ) {
$branch = false;
@@ -538,8 +867,40 @@ class Webhook_API {
'',
$payload['push']['changes'][0]['new']['name']
);
+ } elseif ( isset( $payload['ref'] ) ) {
+ $branch = str_replace( 'refs/heads/', '', $payload['ref'] );
+ } else {
+ $branch = $this->find_branch_recursively( $payload );
}
return $branch;
}
+
+ /**
+ * Recursively search for branch references in payload
+ *
+ * Search through nested arrays to find values starting with 'refs/heads/'.
+ *
+ * @param mixed $data Part of the payload to search through.
+ * @return string|false Branch name or false if not found.
+ */
+ protected function find_branch_recursively( $data ) {
+
+ if ( is_string( $data ) && 0 === strpos( $data, 'refs/heads/' ) ) {
+ return str_replace( 'refs/heads/', '', $data );
+ }
+
+ if ( is_array( $data ) ) {
+
+ foreach ( $data as $value ) {
+ $result = $this->find_branch_recursively( $value );
+
+ if ( false === $result ) {
+ return $result;
+ }
+ }
+ }
+
+ return false;
+ }
}
diff --git a/inc/class-upserv.php b/inc/class-upserv.php
index 89317d1..2377181 100644
--- a/inc/class-upserv.php
+++ b/inc/class-upserv.php
@@ -11,12 +11,41 @@ use Anyape\UpdatePulse\Server\Manager\Data_Manager;
use Anyape\UpdatePulse\Server\Manager\Package_Manager;
use Anyape\Utils\Utils;
+/**
+ * Main server class for UpdatePulse
+ *
+ * @since 1.0.0
+ */
class UPServ {
+ /**
+ * Class instance
+ *
+ * @var UPServ|null
+ * @since 1.0.0
+ */
protected static $instance;
+ /**
+ * Default plugin options
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected static $default_options;
+ /**
+ * Current plugin options
+ *
+ * @var array
+ * @since 1.0.0
+ */
protected static $options;
+ /**
+ * Constructor
+ *
+ * @param boolean $init_hooks Whether to initialize hooks
+ * @since 1.0.0
+ */
public function __construct( $init_hooks = false ) {
self::$default_options = array(
'use_vcs' => 0,
@@ -79,6 +108,16 @@ class UPServ {
* Public methods
*******************************************************************/
+ /**
+ * Handle Action Scheduler failed execution
+ *
+ * Logs information about failed scheduled actions when debug mode is enabled.
+ *
+ * @param int $action_id The ID of the failed action
+ * @param Exception $exception The exception that was thrown
+ * @param string $context Additional context information
+ * @since 1.0.0
+ */
public function action_scheduler_failed_execution( $action_id, Exception $exception, $context = '' ) {
if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
@@ -96,10 +135,17 @@ class UPServ {
// WordPress hooks ---------------------------------------------
+ /**
+ * Activate plugin
+ *
+ * Runs on plugin activation to verify requirements and initialize settings.
+ *
+ * @since 1.0.0
+ */
public static function activate() {
- if ( ! version_compare( phpversion(), '7.4', '>=' ) ) {
- $error_message = __( 'PHP version 7.4 or higher is required. Current version: ', 'updatepulse-server' );
+ if ( ! version_compare( phpversion(), '8.0', '>=' ) ) {
+ $error_message = __( 'PHP version 8.0 or higher is required. Current version: ', 'updatepulse-server' );
$error_message .= phpversion();
die( $error_message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
@@ -127,25 +173,70 @@ class UPServ {
}
}
+ /**
+ * Deactivate plugin
+ *
+ * Runs on plugin deactivation.
+ *
+ * @since 1.0.0
+ */
public static function deactivate() {
flush_rewrite_rules();
}
+ /**
+ * Uninstall plugin
+ *
+ * Runs on plugin uninstallation.
+ *
+ * @since 1.0.0
+ */
public static function uninstall() {
require_once UPSERV_PLUGIN_PATH . 'uninstall.php';
}
+ /**
+ * Get all plugin options
+ *
+ * Retrieves the plugin's options from the database.
+ *
+ * @return array Plugin options
+ * @since 1.0.0
+ */
public function get_options() {
$options = get_option( 'upserv_options' );
$options = json_decode( $options, true );
$options = $options ? $options : array();
$options = array_merge( self::$default_options, $options );
+ /**
+ * Filter the plugin options.
+ *
+ * @param array $options The plugin options
+ * @return array The filtered options
+ * @since 1.0.0
+ */
return apply_filters( 'upserv_get_options', $options );
}
+ /**
+ * Update plugin options
+ *
+ * Updates the plugin's options in the database.
+ *
+ * @param array $options New options to update
+ * @return bool Whether the update was successful
+ * @since 1.0.0
+ */
public function update_options( $options ) {
$options = array_merge( self::$options, $options );
+ /**
+ * Filter the options before updating.
+ *
+ * @param array $options The options to update
+ * @return array The filtered options
+ * @since 1.0.0
+ */
$options = apply_filters( 'upserv_update_options', $options );
$options = wp_json_encode(
$options,
@@ -160,6 +251,16 @@ class UPServ {
return $result;
}
+ /**
+ * Get single option value
+ *
+ * Retrieves a specific option by its path.
+ *
+ * @param string|array $path Option path
+ * @param mixed $_default Default value if option not found
+ * @return mixed Option value
+ * @since 1.0.0
+ */
public function get_option( $path, $_default ) {
$options = $this->get_options();
$option = Utils::access_nested_array( $options, $path );
@@ -168,9 +269,27 @@ class UPServ {
$option = $_default;
}
+ /**
+ * Filter a specific option value.
+ *
+ * @param mixed $option The option value
+ * @param string|array $path The option path
+ * @return mixed The filtered option value
+ * @since 1.0.0
+ */
return apply_filters( 'upserv_get_option', $option, $path );
}
+ /**
+ * Set option in memory
+ *
+ * Sets an option value in memory without saving to database.
+ *
+ * @param string|array $path Option path
+ * @param mixed $value Option value
+ * @return array Updated options
+ * @since 1.0.0
+ */
public function set_option( $path, $value ) {
$options = self::$options;
@@ -181,6 +300,16 @@ class UPServ {
return self::$options;
}
+ /**
+ * Update single option
+ *
+ * Updates a specific option by its path and saves to database.
+ *
+ * @param string|array $path Option path
+ * @param mixed $value Option value
+ * @return bool Whether the update was successful
+ * @since 1.0.0
+ */
public function update_option( $path, $value ) {
$options = $this->get_options();
@@ -189,6 +318,13 @@ class UPServ {
return $this->update_options( $options );
}
+ /**
+ * Initialize plugin
+ *
+ * Runs during WordPress init hook to set up the plugin.
+ *
+ * @since 1.0.0
+ */
public function init() {
if ( get_transient( 'upserv_flush' ) ) {
@@ -207,10 +343,26 @@ class UPServ {
}
}
+ /**
+ * Load text domain
+ *
+ * Loads the plugin's translations.
+ *
+ * @since 1.0.0
+ */
public function load_textdomain() {
load_plugin_textdomain( 'updatepulse-server', false, '/languages' );
}
+ /**
+ * Register admin styles
+ *
+ * Adds stylesheets for the admin interface.
+ *
+ * @param array $styles Existing styles
+ * @return array Modified styles
+ * @since 1.0.0
+ */
public function upserv_admin_styles( $styles ) {
$styles['main'] = array(
'path' => UPSERV_PLUGIN_PATH . 'css/admin/main' . upserv_assets_suffix() . '.css',
@@ -232,6 +384,15 @@ class UPServ {
return $styles;
}
+ /**
+ * Register admin scripts
+ *
+ * Adds JavaScript files for the admin interface.
+ *
+ * @param array $scripts Existing scripts
+ * @return array Modified scripts
+ * @since 1.0.0
+ */
public function upserv_admin_scripts( $scripts ) {
$scripts['main'] = array(
'path' => UPSERV_PLUGIN_PATH . 'js/admin/main' . upserv_assets_suffix() . '.js',
@@ -246,6 +407,16 @@ class UPServ {
return $scripts;
}
+ /**
+ * Process script localization
+ *
+ * Formats localization strings for JavaScript files.
+ *
+ * @param array $l10n Localization data
+ * @param string $script Script name
+ * @return array Modified localization data
+ * @since 1.0.0
+ */
public function upserv_scripts_l10n( $l10n, $script ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
foreach ( $l10n as $key => $values ) {
@@ -260,6 +431,14 @@ class UPServ {
return $l10n;
}
+ /**
+ * Enqueue admin scripts and styles
+ *
+ * Loads the necessary assets for admin pages.
+ *
+ * @param string $hook Current admin page hook
+ * @since 1.0.0
+ */
public function admin_enqueue_scripts( $hook ) {
if ( false !== strpos( $hook, 'page_upserv' ) ) {
@@ -268,6 +447,13 @@ class UPServ {
}
}
+ /**
+ * Register main admin menu
+ *
+ * Adds the main UpdatePulse menu item to the admin menu.
+ *
+ * @since 1.0.0
+ */
public function admin_menu() {
$page_title = __( 'UpdatePulse', 'updatepulse-server' );
$menu_title = $page_title;
@@ -276,6 +462,13 @@ class UPServ {
add_menu_page( $page_title, $menu_title, 'manage_options', 'upserv-page', '', $icon );
}
+ /**
+ * Register help page in admin menu
+ *
+ * Adds the help submenu to the UpdatePulse menu.
+ *
+ * @since 1.0.0
+ */
public function admin_menu_help() {
$function = array( $this, 'help_page' );
$page_title = __( 'UpdatePulse Server - Help', 'updatepulse-server' );
@@ -285,6 +478,15 @@ class UPServ {
add_submenu_page( 'upserv-page', $page_title, $menu_title, 'manage_options', $menu_slug, $function );
}
+ /**
+ * Add tab links for admin interface
+ *
+ * Registers navigation tabs for the admin interface.
+ *
+ * @param array $links Existing tab links
+ * @return array Modified tab links
+ * @since 1.0.0
+ */
public function upserv_admin_tab_links( $links ) {
$links['help'] = array(
admin_url( 'admin.php?page=upserv-page-help' ),
@@ -294,12 +496,31 @@ class UPServ {
return $links;
}
+ /**
+ * Add tab states for admin interface
+ *
+ * Sets active states for navigation tabs.
+ *
+ * @param array $states Existing tab states
+ * @param string $page Current page
+ * @return array Modified tab states
+ * @since 1.0.0
+ */
public function upserv_admin_tab_states( $states, $page ) {
$states['help'] = 'upserv-page-help' === $page;
return $states;
}
+ /**
+ * Add plugin action links
+ *
+ * Adds custom links to the plugin's entry in the plugins list.
+ *
+ * @param array $links Existing plugin action links
+ * @return array Modified plugin action links
+ * @since 1.0.0
+ */
public function add_action_links( $links ) {
$link = array(
'' . __( 'Help', 'updatepulse-server' ) . '',
@@ -308,10 +529,28 @@ class UPServ {
return array_merge( $links, $link );
}
+ /**
+ * Set action scheduler retention period
+ *
+ * Controls how long scheduled actions are kept in the database.
+ *
+ * @return int Retention period in seconds
+ * @since 1.0.0
+ */
public function action_scheduler_retention_period() {
return DAY_IN_SECONDS;
}
+ /**
+ * Modify admin template arguments
+ *
+ * Adds or modifies arguments passed to admin templates.
+ *
+ * @param array $args Existing template arguments
+ * @param string $template_name Name of the template
+ * @return array Modified template arguments
+ * @since 1.0.0
+ */
public function upserv_get_admin_template_args( $args, $template_name ) {
if ( preg_match( '/^plugin-.*-page\.php$/', $template_name ) ) {
@@ -323,6 +562,14 @@ class UPServ {
// Misc. -------------------------------------------------------
+ /**
+ * Get class instance
+ *
+ * Retrieves or creates the singleton instance of this class.
+ *
+ * @return UPServ The class instance
+ * @since 1.0.0
+ */
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
@@ -332,20 +579,47 @@ class UPServ {
return self::$instance;
}
+ /**
+ * Locate template file
+ *
+ * Finds a template file in the theme or plugin directories.
+ *
+ * @param string $template_name Template name
+ * @param bool $load Whether to load the template
+ * @param bool $required_once Whether to use require_once or require
+ * @return string Template path
+ * @since 1.0.0
+ */
public static function locate_template( $template_name, $load = false, $required_once = true ) {
- $name = str_replace( 'templates/', '', $template_name );
- $paths = array(
+ $name = str_replace( 'templates/', '', $template_name );
+ $paths = array(
'plugins/updatepulse-server/templates/' . $name,
'plugins/updatepulse-server/' . $name,
'updatepulse-server/templates/' . $name,
'updatepulse-server/' . $name,
);
+ /**
+ * Filter the paths where templates can be located.
+ *
+ * @param array $paths Array of template paths
+ * @return array The filtered paths
+ * @since 1.0.0
+ */
$template = locate_template( apply_filters( 'upserv_locate_template_paths', $paths ) );
if ( empty( $template ) ) {
$template = UPSERV_PLUGIN_PATH . 'inc/templates/' . $template_name;
}
+ /**
+ * Filter the located template.
+ *
+ * @param string $template The path to the template
+ * @param string $template_name The template name
+ * @param string $template_path The template path
+ * @return string The filtered template path
+ * @since 1.0.0
+ */
$template = apply_filters(
'upserv_locate_template',
$template,
@@ -360,7 +634,27 @@ class UPServ {
return $template;
}
+ /**
+ * Locate admin template file
+ *
+ * Finds an admin template file in the plugin directory.
+ *
+ * @param string $template_name Template name
+ * @param bool $load Whether to load the template
+ * @param bool $required_once Whether to use require_once or require
+ * @return string Template path
+ * @since 1.0.0
+ */
public static function locate_admin_template( $template_name, $load = false, $required_once = true ) {
+ /**
+ * Filter the admin template location.
+ *
+ * @param string $template The path to the template
+ * @param string $template_name The template name
+ * @param string $template_path The template path
+ * @return string The filtered template path
+ * @since 1.0.0
+ */
$template = apply_filters(
'upserv_locate_admin_template',
UPSERV_PLUGIN_PATH . 'inc/templates/admin/' . $template_name,
@@ -375,6 +669,13 @@ class UPServ {
return $template;
}
+ /**
+ * Display MU plugin setup failure notice
+ *
+ * Shows admin notice when MU plugin couldn't be installed.
+ *
+ * @since 1.0.0
+ */
public function setup_mu_plugin_failure_notice() {
$class = 'notice notice-error';
$message = sprintf(
@@ -387,6 +688,13 @@ class UPServ {
printf( ' %2$s %2$s
@@ -100,7 +100,7 @@
-
+
@@ -165,7 +165,7 @@
-
+
-
+
-
+ PUC_FORCE_BRANCH, %3$s is
-
+ PUC_FORCE_BRANCH, %3$s is ' . $this->failed_update_info . ' ' . sprintf( esc_html__( 'The %1$s may not have any effect until the issues are resolved.', 'wp-update-migrate' ), $this->package_type ) . ' ' . $this->success_update_info . 'Question<\/h4>
Question<\/h4>
' . esc_html__( 'UpdatePulse Server', 'updatepulse-server' ) . '
';
@@ -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 60e0a4a..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
- const JSON_OPTIONS = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK;
+ /**
+ * 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() {
@@ -128,14 +152,16 @@ class Utils {
return null;
}
- return sprintf( '%.3f', microtime( true ) - $req_time_float );
+ return (string) sprintf( '%.3fs', microtime( true ) - $req_time_float );
}
/**
- * @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() {
@@ -151,4 +177,32 @@ 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':
+ return __( 'Pending', 'updatepulse-server' );
+ case 'activated':
+ return __( 'Activated', 'updatepulse-server' );
+ case 'deactivated':
+ return __( 'Deactivated', 'updatepulse-server' );
+ case 'on-hold':
+ return __( 'On Hold', 'updatepulse-server' );
+ case 'blocked':
+ return __( 'Blocked', 'updatepulse-server' );
+ case 'expired':
+ return __( 'Expired', 'updatepulse-server' );
+ default:
+ return __( 'N/A', 'updatepulse-server' );
+ }
+ }
}
diff --git a/inc/cli/class-cli.php b/inc/cli/class-cli.php
index 2e18a88..b88b12b 100644
--- a/inc/cli/class-cli.php
+++ b/inc/cli/class-cli.php
@@ -11,11 +11,34 @@ use WP_CLI;
use WP_Error;
use Anyape\UpdatePulse\Server\Nonce\Nonce;
+/**
+ * CLI commands for UpdatePulse Server.
+ *
+ * @since 1.0.0
+ */
class CLI extends WP_CLI_Command {
+ /**
+ * Error code for when a resource is not found.
+ *
+ * @var int
+ * @since 1.0.0
+ */
protected const RESOURCE_NOT_FOUND = 3;
- protected const DEFAULT_ERROR = 1;
- protected const LOG_METHODS = array(
+ /**
+ * Default error code for general errors.
+ *
+ * @var int
+ * @since 1.0.0
+ */
+ protected const DEFAULT_ERROR = 1;
+ /**
+ * Available log methods for WP_CLI.
+ *
+ * @var array
+ * @since 1.0.0
+ */
+ protected const LOG_METHODS = array(
'line',
'log',
'success',
@@ -25,7 +48,13 @@ class CLI extends WP_CLI_Command {
'halt',
'error_multi_line',
);
- protected const PACKAGE_TYPES = array(
+ /**
+ * Available package types supported by the plugin.
+ *
+ * @var array
+ * @since 1.0.0
+ */
+ protected const PACKAGE_TYPES = array(
'plugin',
'theme',
'generic',
@@ -41,6 +70,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse cleanup_cache
+ *
+ * @since 1.0.0
*/
public function cleanup_cache() {
$this->cleanup( 'cache' );
@@ -52,6 +83,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse cleanup_logs
+ *
+ * @since 1.0.0
*/
public function cleanup_logs() {
$this->cleanup( 'logs' );
@@ -63,6 +96,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse cleanup_tmp
+ *
+ * @since 1.0.0
*/
public function cleanup_tmp() {
$this->cleanup( 'tmp' );
@@ -74,6 +109,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse cleanup-all
+ *
+ * @since 1.0.0
*/
public function cleanup_all() {
$this->cleanup( 'cache' );
@@ -95,6 +132,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse check_remote_package_update my-package plugin
+ *
+ * @since 1.0.0
*/
public function check_remote_package_update( $args, $assoc_args ) {
$slug = $args[0];
@@ -134,6 +173,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse download_remote_package my-package plugin --vcs_url='https://vcs-url.tld/identifier/' --branch='main'
+ *
+ * @since 1.0.0
*/
public function download_remote_package( $args, $assoc_args ) {
@@ -175,6 +216,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse delete_package my-package
+ *
+ * @since 1.0.0
*/
public function delete_package( $args, $assoc_args ) {
$slug = $args[0];
@@ -196,6 +239,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse get_package_info my-package
+ *
+ * @since 1.0.0
*/
public function get_package_info( $args, $assoc_args ) {
$slug = $args[0];
@@ -228,6 +273,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse create_nonce --true_nonce=true --expiry_length=30 --data='{}' --return=nonce_only --store=true
+ *
+ * @since 1.0.0
*/
public function create_nonce( $args, $assoc_args ) {
$assoc_args = wp_parse_args(
@@ -298,6 +345,7 @@ class CLI extends WP_CLI_Command {
*
* wp updatepulse build_nonce_api_signature --api_key_id='UPDATEPULSE_L_api_key_name' --timestamp=1704067200 --api_key=da9d20647163a1f3c04844387f91e2c3 --payload='{"key": "value"}'
*
+ * @since 1.0.0
*/
public function build_nonce_api_signature( $args, $assoc_args ) {
$assoc_args = wp_parse_args(
@@ -333,6 +381,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse clear_nonces
+ *
+ * @since 1.0.0
*/
public function clear_nonces() {
$result = upserv_clear_nonces();
@@ -353,6 +403,8 @@ class CLI extends WP_CLI_Command {
* ## EXAMPLES
*
* wp updatepulse get_nonce_expiry
+
-
+
diff --git a/inc/templates/admin/packages-table-row.php b/inc/templates/admin/packages-table-row.php
index 3b43bf9..290bc3b 100644
--- a/inc/templates/admin/packages-table-row.php
+++ b/inc/templates/admin/packages-table-row.php
@@ -21,7 +21,7 @@
$actions['download'] = vsprintf( '%s', $args );
$args[1] = 'delete';
$args[ count( $args ) - 1 ] = __( 'Delete' );
- $actions['delete'] = vsprintf( '%s', $args );
+ $actions['delete'] = vsprintf( '%s', $args );
$actions = $table->row_actions(
apply_filters( 'upserv_packages_table_row_actions', $actions, $args, $query_string, $record_key )
);
diff --git a/inc/templates/admin/plugin-api-page.php b/inc/templates/admin/plugin-api-page.php
index 94f321f..9b13497 100644
--- a/inc/templates/admin/plugin-api-page.php
+++ b/inc/templates/admin/plugin-api-page.php
@@ -32,7 +32,7 @@
-
+
diff --git a/inc/templates/admin/plugin-help-page.php b/inc/templates/admin/plugin-help-page.php
index c782712..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\' );'
);
?>
@@ -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',
);
?>
@@ -78,11 +77,11 @@
-
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 @@
+
@@ -295,7 +304,16 @@
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/js/admin/api.js b/js/admin/api.js
index 7dc13e1..7682f39 100644
--- a/js/admin/api.js
+++ b/js/admin/api.js
@@ -17,8 +17,10 @@ jQuery(document).ready(function ($) {
var info;
var modal = $(this);
- if (handler.closest('tr').find('input[name="api_data[]"]').val()) {
- info = JSON.parse(handler.closest('tr').find('input[name="api_data[]"]').val());
+ if (handler.closest('table').find('.api-data').val()) {
+ info = JSON.parse(handler.closest('table').find('.api-data').val());
+ } else if (handler.closest('h3').next('table').find('.api-data').val()) {
+ info = JSON.parse(handler.closest('h3').next('table').find('.api-data').val());
}
if (typeof info !== 'object') {
diff --git a/js/admin/api.min.js b/js/admin/api.min.js
index 1bf8129..0af8f7c 100644
--- a/js/admin/api.min.js
+++ b/js/admin/api.min.js
@@ -1 +1 @@
-jQuery(document).ready(function(e){function n(e){var n=new Uint8Array(e),t="";window.crypto.getRandomValues(n);for(var i=0;itrue, %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'
+ );
+ ?>
"+l.package_slug+" - "+l.owner_name+" ("+l.email+")"),t.find("pre").text(JSON.stringify(l,null,2))}),e("#add_license_trigger").on("click",function(){t(e("#upserv_license_panel"),function(){n(),e("#upserv_license_action").val("create"),e(".upserv-edit-license-label").hide(),e(".upserv-license-show-if-edit").hide(),e(".upserv-add-license-label").show(),e(".open-panel").attr("disabled","disabled"),e(".upserv-licenses-table .open-panel").hide(),e("html, body").animate({scrollTop:e("#upserv_license_panel").offset().top-e("#wpadminbar").height()-20},500)})}),e(".upserv-licenses-table .open-panel .edit a").on("click",function(a){var l;a.preventDefault(),l=e(this).closest("tr").find('input[name="license_data[]"]').val()?JSON.parse(e(this).closest("tr").find('input[name="license_data[]"]').val()):{},"object"!=typeof l&&(l={}),t(e("#upserv_license_panel"),function(){n(l),e("#upserv_license_action").val("update"),e(".upserv-edit-license-label").show(),e(".upserv-license-show-if-edit").show(),e(".upserv-add-license-label").hide(),e(".open-panel").attr("disabled","disabled"),e(".upserv-licenses-table .open-panel").hide(),e("html, body").animate({scrollTop:e("#upserv_license_panel").offset().top-e("#wpadminbar").height()-20},500)})}),e("#upserv_license_cancel, .close-panel.reset").on("click",function(){e("html, body").animate({scrollTop:e(".upserv-wrap").offset().top-e("#wpadminbar").height()-20},150),s(e("#upserv_license_panel"),function(){l()})}),document.getElementById("upserv_license").addEventListener("submit",a),e("#upserv_license_registered_domains").on("click",".upserv-remove-domain",function(a){a.preventDefault(),e(this).parent().remove(),1>=e(".upserv-remove-domain").length&&e(".upserv-no-domain").show()})});
\ No newline at end of file
+jQuery(document).ready(function(e){function a(e){function a(){e.target.querySelectorAll(".upserv-license-error").forEach(e=>e.classList.remove("upserv-license-error"))}function n(e){e.classList.add("upserv-license-error")}e.preventDefault();const l={required:e=>""!==e.trim(),email:e=>/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(e.trim()),slug:e=>/^[a-z0-9-]+$/.test(e.trim()),licenseDate:e=>/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/.test(e.trim())||""===e.trim()};let t=!0;const i={licenseKey:{el:document.getElementById("upserv_license_key"),validate:"required"},licensePackageSlug:{el:document.getElementById("upserv_license_package_slug"),validate:"slug"},registeredEmail:{el:document.getElementById("upserv_license_registered_email"),validate:"email"},dateCreated:{el:document.getElementById("upserv_license_date_created"),validate:["required","licenseDate"]},dateExpiry:{el:document.getElementById("upserv_license_date_expiry"),validate:"licenseDate"},dateRenewed:{el:document.getElementById("upserv_license_date_renewed"),validate:"licenseDate"},maxAllowedDomains:{el:document.getElementById("upserv_license_max_allowed_domains"),validate:"required"}};a();for(const[e,a]of Object.entries(i)){const e=a.el.value,i=Array.isArray(a.validate)?a.validate:[a.validate];for(const s of i)if(!l[s](e)){t=!1,n(a.el);break}}if(t){const a={id:document.getElementById("upserv_license_id").innerHTML,license_key:i.licenseKey.el.value,max_allowed_domains:i.maxAllowedDomains.el.value,allowed_domains:Array.from(document.querySelectorAll(".upserv-domains-list li:not(.upserv-domain-template) .upserv-domain-value")).map(e=>e.textContent),status:document.getElementById("upserv_license_status").value,owner_name:document.getElementById("upserv_license_owner_name").value,email:i.registeredEmail.el.value,company_name:document.getElementById("upserv_license_owner_company").value,txn_id:document.getElementById("upserv_license_transaction_id").value,data:s.codemirror.getValue(),date_created:i.dateCreated.el.value,date_renewed:i.dateRenewed.el.value,date_expiry:i.dateExpiry.el.value,package_slug:i.licensePackageSlug.el.value,package_type:document.getElementById("upserv_license_package_type").value};document.getElementById("upserv_license_values").value=JSON.stringify(a),document.querySelectorAll(".no-submit").forEach(e=>e.removeAttribute("name")),e.target.submit()}}function n(a){if(r&&(s=s.initialize(e("#upserv_license_data"),UPServAdminLicense.cm_settings),r=!1),a&&a.constructor===Object){if(e("#upserv_license_id").html(a.id),e("#upserv_license_key").val(a.license_key),e("#upserv_license_date_created").val(a.date_created),e("#upserv_license_max_allowed_domains").val(a.max_allowed_domains),e("#upserv_license_owner_name").val(a.owner_name),e("#upserv_license_registered_email").val(a.email),e("#upserv_license_owner_company").val(a.company_name),e("#upserv_license_transaction_id").val(a.txn_id),e("#upserv_license_package_slug").val(a.package_slug),e("#upserv_license_status").val(a.status),e("#upserv_license_data").val("string"==typeof a.data?JSON.stringify(JSON.parse(a.data),null,"\t"):"{}"),e("#upserv_license_package_type").val(a.package_type),s.codemirror.setValue(e("#upserv_license_data").val()),"0000-00-00"!==a.date_expiry&&e("#upserv_license_date_expiry").val(a.date_expiry),"0000-00-00"!==a.date_renewed&&e("#upserv_license_date_renewed").val(a.date_renewed),a.allowed_domains.length>0){var n=e(".upserv-domains-list"),l=n.find("li").clone();l.removeClass("upserv-domain-template"),e.each(a.allowed_domains,function(e,a){var t=l.clone();t.find(".upserv-domain-value").html(a),n.append(t)}),e(".upserv-no-domain").hide(),n.show()}}else e("#upserv_license_key").val(e("#upserv_license_key").data("random_key")),e("#upserv_license_date_created").val((new Date).toISOString().slice(0,10)),e("#upserv_license_max_allowed_domains").val(1),s.codemirror.setValue("{}")}function l(){e("#upserv_license").trigger("reset"),e("upserv_license_values").val(""),e("upserv_license_action").val(""),e(".open-panel").removeAttr("disabled"),e(".upserv-licenses-table .open-panel").show(),e("#upserv_license_id").html(""),e(".upserv-domains-list li:not(.upserv-domain-template)").remove(),e(".upserv-no-domain").show(),e("label.upserv-license-error").hide(),e(".upserv-license-error").removeClass("upserv-license-error"),s.codemirror.setValue("{}")}function t(e,a){e.is(":visible")||e.slideDown(100,function(){a(e),e.find(".inside").animate({opacity:"1"},150)})}function i(e,a){e.is(":visible")&&e.slideUp(100,function(){e.find(".inside").css({opacity:"0"}),a(e)})}var s=wp.codeEditor,r=!0;e(".upserv-wrap .wp-list-table .delete a").on("click",function(a){window.confirm(UPServAdminLicense_l10n.deleteLicenseConfirm)||a.preventDefault(),e(this).attr("href",e(this).data("href"))}),e(".upserv-delete-all-licenses").on("click",function(e){window.confirm(UPServAdminLicense_l10n.deleteLicensesConfirm)||e.preventDefault()}),e("#upserv_modal_license_details").on("open",function(a,n){var l,t=e(this);n.closest("tr").find('input[name="license_data[]"]').val()&&(l=JSON.parse(n.closest("tr").find('input[name="license_data[]"]').val())),"object"!=typeof l&&(l={}),l.data&&(l.data=JSON.parse(l.data)),"object"!=typeof l.data&&(l.data={}),t.find("h2").html(l.license_key+"
"+l.package_slug+" - "+l.owner_name+" ("+l.email+")"),t.find("pre").text(JSON.stringify(l,null,2))}),e("#add_license_trigger").on("click",function(){t(e("#upserv_license_panel"),function(){n(),e("#upserv_license_action").val("create"),e(".upserv-edit-license-label").hide(),e(".upserv-license-show-if-edit").hide(),e(".upserv-add-license-label").show(),e(".open-panel").attr("disabled","disabled"),e(".upserv-licenses-table .open-panel").hide(),e("html, body").animate({scrollTop:e("#upserv_license_panel").offset().top-e("#wpadminbar").height()-20},500)})}),e(".upserv-licenses-table .open-panel .edit a").on("click",function(a){var l;a.preventDefault(),l=e(this).closest("tr").find('input[name="license_data[]"]').val()?JSON.parse(e(this).closest("tr").find('input[name="license_data[]"]').val()):{},"object"!=typeof l&&(l={}),t(e("#upserv_license_panel"),function(){n(l),e("#upserv_license_action").val("update"),e(".upserv-edit-license-label").show(),e(".upserv-license-show-if-edit").show(),e(".upserv-add-license-label").hide(),e(".open-panel").attr("disabled","disabled"),e(".upserv-licenses-table .open-panel").hide(),e("html, body").animate({scrollTop:e("#upserv_license_panel").offset().top-e("#wpadminbar").height()-20},500)})}),e("#upserv_license_cancel, .close-panel.reset").on("click",function(){e("html, body").animate({scrollTop:e(".upserv-wrap").offset().top-e("#wpadminbar").height()-20},150),i(e("#upserv_license_panel"),function(){l()})}),document.getElementById("upserv_license").addEventListener("submit",a),e("#upserv_license_registered_domains").on("click",".upserv-remove-domain",function(a){a.preventDefault(),e(this).parent().remove(),1>=e(".upserv-remove-domain").length&&e(".upserv-no-domain").show()})});
\ No newline at end of file
diff --git a/js/admin/package.js b/js/admin/package.js
index 78e3176..a95afe9 100644
--- a/js/admin/package.js
+++ b/js/admin/package.js
@@ -3,20 +3,22 @@ jQuery(document).ready(function ($) {
var registrationLocked = false;
$('.upserv-wrap .wp-list-table .delete a').on('click', function(e) {
- var r = window.confirm(UPServAdminPackage_l10n.deletePackageConfirm);
- if (!r) {
+ if (!window.confirm(UPServAdminPackage_l10n.deletePackageConfirm)) {
e.preventDefault();
}
+
+ $(this).attr('href', $(this).data('href'));
});
$('.upserv-delete-all-packages').on('click', function(e) {
- var r = window.confirm(UPServAdminPackage_l10n.deletePackagesConfirm);
- if (!r) {
+ if (!window.confirm(UPServAdminPackage_l10n.deletePackagesConfirm)) {
e.preventDefault();
}
+
+ $(this).attr('type', 'submit');
});
$('#upserv_modal_package_details').on('open', function (e, handler) {
diff --git a/js/admin/package.min.js b/js/admin/package.min.js
index 99a2157..07892e5 100644
--- a/js/admin/package.min.js
+++ b/js/admin/package.min.js
@@ -1 +1 @@
-jQuery(document).ready(function(e){var a=!1;e(".upserv-wrap .wp-list-table .delete a").on("click",function(e){var a=window.confirm(UPServAdminPackage_l10n.deletePackageConfirm);a||e.preventDefault()}),e(".upserv-delete-all-packages").on("click",function(e){var a=window.confirm(UPServAdminPackage_l10n.deletePackagesConfirm);a||e.preventDefault()}),e("#upserv_modal_package_details").on("open",function(a,i){var r=i.data("info"),n=e(this);n.find("h2").text(r.name+" v"+r.version),n.find("pre").text(JSON.stringify(r,null,2))}),e("#upserv_register_package_slug").on("input",function(){0%1$s is not a valid UpdatePulse Server data directory."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:143
+#: inc/templates/admin/plugin-licenses-page.php:142
msgid "Domains currently allowed to use this license."
msgstr ""
-#: inc/table/class-packages-table.php:260
+#: inc/table/class-packages-table.php:273
#: inc/templates/admin/packages-table-row.php:20
msgid "Download"
msgstr ""
-#: inc/table/class-packages-table.php:237
+#: inc/table/class-packages-table.php:251
msgid ""
"Download: Archive max size exceeded - try to adjust it in the settings below."
msgstr ""
@@ -581,8 +585,8 @@ msgstr ""
msgid "Edit License"
msgstr ""
-#: inc/manager/class-license-manager.php:454
-#: inc/templates/admin/plugin-licenses-page.php:183
+#: inc/manager/class-license-manager.php:702
+#: inc/templates/admin/plugin-licenses-page.php:182
msgid "Enable Package Licenses"
msgstr ""
@@ -636,12 +640,12 @@ msgid ""
"the page and try again."
msgstr ""
-#: inc/manager/class-package-manager.php:378
-#: inc/manager/class-package-manager.php:398
-#: inc/manager/class-package-manager.php:447
-#: inc/manager/class-package-manager.php:458
-#: inc/manager/class-package-manager.php:473
-#: inc/manager/class-package-manager.php:501
+#: inc/manager/class-package-manager.php:408
+#: inc/manager/class-package-manager.php:428
+#: inc/manager/class-package-manager.php:477
+#: inc/manager/class-package-manager.php:488
+#: inc/manager/class-package-manager.php:503
+#: inc/manager/class-package-manager.php:531
msgid "Error - could not upload the package. "
msgstr ""
@@ -661,14 +665,14 @@ msgstr ""
msgid "Error - Please check the provided Version Control System URL."
msgstr ""
-#: inc/manager/class-cloud-storage-manager.php:431
-#: inc/manager/class-cloud-storage-manager.php:443
+#: inc/manager/class-cloud-storage-manager.php:612
+#: inc/manager/class-cloud-storage-manager.php:624
#: inc/manager/class-remote-sources-manager.php:241
#: inc/manager/class-remote-sources-manager.php:253
msgid "Error - Received invalid data; please reload the page and try again."
msgstr ""
-#: inc/manager/class-cloud-storage-manager.php:469
+#: inc/manager/class-cloud-storage-manager.php:650
msgid "Error - Storage Unit not found"
msgstr ""
@@ -692,55 +696,59 @@ msgstr ""
msgid "Example of valid value: package-slug"
msgstr ""
-#: inc/table/class-licenses-table.php:258
+#: inc/table/class-licenses-table.php:46
+#: inc/templates/admin/plugin-licenses-page.php:80
+msgid "Expiration Date"
+msgstr ""
+
+#: inc/templates/admin/plugin-licenses-page.php:84
+msgid "Expiration date of the license. Leave empty for no expiry."
+msgstr ""
+
+#: inc/table/class-licenses-table.php:264
msgid "Expire"
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:58
+#: inc/class-utils.php:168
msgid "Expired"
msgstr ""
-#: inc/table/class-licenses-table.php:45
-#: inc/templates/admin/plugin-licenses-page.php:81
-msgid "Expiry Date"
-msgstr ""
-
-#: inc/templates/admin/plugin-licenses-page.php:85
-msgid "Expiry date of the license. Leave empty for no expiry."
-msgstr ""
-
-#: inc/templates/admin/plugin-licenses-page.php:160
+#: inc/templates/admin/plugin-licenses-page.php:159
msgid "Extra Data"
msgstr ""
-#: inc/manager/class-license-manager.php:59 inc/nonce/class-nonce.php:38
+#: inc/manager/class-license-manager.php:102 inc/nonce/class-nonce.php:38
msgid "Failed to create the necessary database table(s)."
msgstr ""
#. %s: package ID
-#: inc/api/class-webhook-api.php:400
+#: inc/api/class-webhook-api.php:615
#, php-format
msgid "Failed to download package %s."
msgstr ""
-#: inc/manager/class-license-manager.php:565
+#: inc/manager/class-license-manager.php:847
msgid "Failed to insert the license record in the database."
msgstr ""
#. %s: package ID
-#: inc/api/class-webhook-api.php:379
+#: inc/api/class-webhook-api.php:588
#, php-format
msgid "Failed to sechedule download for package %s."
msgstr ""
-#: inc/manager/class-license-manager.php:551
+#: inc/manager/class-license-manager.php:825
msgid "Failed to update the license record in the database."
msgstr ""
-#: inc/manager/class-package-manager.php:429
+#: inc/manager/class-package-manager.php:459
msgid "Failed to write file to disk."
msgstr ""
+#: inc/table/class-packages-table.php:49
+msgid "File Modified "
+msgstr ""
+
#: inc/table/class-packages-table.php:47
msgid "File Name"
msgstr ""
@@ -788,11 +796,11 @@ msgid ""
"%s directory by entering the package slug."
msgstr ""
-#: inc/api/class-package-api.php:376
+#: inc/api/class-package-api.php:675
msgid "Get information about a single package"
msgstr ""
-#: inc/api/class-package-api.php:375
+#: inc/api/class-package-api.php:674
msgid "Get information about multiple packages"
msgstr ""
@@ -800,11 +808,11 @@ msgstr ""
msgid "Get remote package"
msgstr ""
-#: inc/api/class-license-api.php:453
+#: inc/api/class-license-api.php:669
msgid "Get single license records"
msgstr ""
-#: functions.php:25
+#: functions.php:46
msgid "GitHub"
msgstr ""
@@ -812,7 +820,7 @@ msgstr ""
msgid "Github"
msgstr ""
-#: functions.php:27
+#: functions.php:48
msgid "GitLab"
msgstr ""
@@ -858,17 +866,17 @@ msgstr ""
msgid "https://github.com/anyape/updatepulse-server/"
msgstr ""
-#: inc/table/class-licenses-table.php:46
+#: inc/table/class-licenses-table.php:47
msgid "ID"
msgstr ""
-#: inc/api/class-package-api.php:377
+#: inc/api/class-package-api.php:676
msgid ""
"If a package does exist, update it by uploading a valid package file, or by "
"downloading it if using a VCS"
msgstr ""
-#: inc/api/class-package-api.php:378
+#: inc/api/class-package-api.php:677
msgid ""
"If a package does not exist, upload it by providing a valid package file, or "
"download it if using a VCS"
@@ -886,7 +894,7 @@ msgid ""
"when it is triggered."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:152
+#: inc/templates/admin/plugin-licenses-page.php:151
msgid ""
"If applicable, the transaction identifier associated to the purchase of the "
"license."
@@ -913,17 +921,17 @@ msgid ""
"check the provided token has the permissions to access members information."
msgstr ""
-#: inc/manager/class-api-manager.php:56
+#: inc/manager/class-api-manager.php:85
msgid ""
"If you proceed, the Payload URL will not receive the configured events "
"anymore."
msgstr ""
-#: inc/manager/class-api-manager.php:62
+#: inc/manager/class-api-manager.php:91
msgid "If you proceed, the Payload URL will receive events for ALL licenses."
msgstr ""
-#: inc/manager/class-api-manager.php:50
+#: inc/manager/class-api-manager.php:79
msgid ""
"If you proceed, the remote systems using it will not be able to access the "
"API anymore."
@@ -969,7 +977,7 @@ msgid "Internal Error - nonce insert error"
msgstr ""
#. %s is a comma-separated list of valid operators.
-#: inc/server/license/class-license-server.php:601
+#: inc/server/license/class-license-server.php:604
#, php-format
msgid ""
"Invalid criteria. The following keys are required: operator, value, field. "
@@ -977,36 +985,36 @@ msgid ""
msgstr ""
#. %s is a comma-separated list of valid fields.
-#: inc/server/license/class-license-server.php:623
+#: inc/server/license/class-license-server.php:626
#, php-format
msgid "Invalid field. The following values are valid: %s"
msgstr ""
-#: inc/manager/class-package-manager.php:380
+#: inc/manager/class-package-manager.php:410
msgid "Invalid file data."
msgstr ""
#. %s is a comma-separated list of valid keys.
-#: inc/server/license/class-license-server.php:527
+#: inc/server/license/class-license-server.php:530
#, php-format
msgid "Invalid keys. The following values are valid: %s"
msgstr ""
-#: inc/api/class-license-api.php:177 inc/api/class-license-api.php:215
-#: inc/api/class-license-api.php:228 inc/api/class-license-api.php:235
-#: inc/api/class-license-api.php:266 inc/api/class-license-api.php:273
-#: inc/api/class-license-api.php:280 inc/api/class-license-api.php:302
+#: inc/api/class-license-api.php:245 inc/api/class-license-api.php:290
+#: inc/api/class-license-api.php:303 inc/api/class-license-api.php:310
+#: inc/api/class-license-api.php:348 inc/api/class-license-api.php:355
+#: inc/api/class-license-api.php:362 inc/api/class-license-api.php:391
msgid "Invalid license data."
msgstr ""
#. %s is a comma-separated list of valid operators.
-#: inc/server/license/class-license-server.php:612
+#: inc/server/license/class-license-server.php:615
#, php-format
msgid "Invalid operator. The following values are valid: %s"
msgstr ""
#. %s is a comma-separated list of valid fields.
-#: inc/server/license/class-license-server.php:572
+#: inc/server/license/class-license-server.php:575
#, php-format
msgid "Invalid order_by field. The following values are valid: %s"
msgstr ""
@@ -1015,15 +1023,15 @@ msgstr ""
msgid "Invalid package."
msgstr ""
-#: inc/server/license/class-license-server.php:544
+#: inc/server/license/class-license-server.php:547
msgid "Invalid relationship operator. Only \"AND\" and \"OR\" are allowed."
msgstr ""
-#: inc/api/class-webhook-api.php:419
-msgid "Invalid request signature"
+#: inc/api/class-webhook-api.php:643
+msgid "Invalid request"
msgstr ""
-#: inc/api/class-webhook-api.php:101
+#: inc/api/class-webhook-api.php:158
msgid "Invalid signature"
msgstr ""
@@ -1041,7 +1049,7 @@ msgstr ""
msgid "IP Whitelist"
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:190
+#: inc/templates/admin/plugin-licenses-page.php:189
msgid ""
"It affects all the packages with a \"Requires License\" license status "
"delivered by this installation of UpdatePulse Server."
@@ -1069,10 +1077,6 @@ msgid ""
"core from executing beyond what is strictly necessary."
msgstr ""
-#: inc/table/class-packages-table.php:49
-msgid "Last Modified "
-msgstr ""
-
#: inc/templates/admin/plugin-remote-sources-page.php:198
#: inc/templates/admin/remote-webhook-options.php:39
msgid ""
@@ -1085,15 +1089,15 @@ msgstr ""
msgid "Leave blank to allow any IP address (not recommended)."
msgstr ""
-#: inc/api/class-license-api.php:464
+#: inc/api/class-license-api.php:687
msgid "License activated"
msgstr ""
-#: inc/api/class-license-api.php:466
+#: inc/api/class-license-api.php:689
msgid "License added"
msgstr ""
-#: inc/manager/class-license-manager.php:562
+#: inc/manager/class-license-manager.php:844
msgid "License added successfully."
msgstr ""
@@ -1101,39 +1105,39 @@ msgstr ""
msgid "License API"
msgstr ""
-#: inc/api/class-license-api.php:1145
+#: inc/api/class-license-api.php:1620
msgid "License API action not found."
msgstr ""
-#: inc/server/license/class-license-server.php:262
+#: inc/server/license/class-license-server.php:265
msgid "License creation failed - database insertion error."
msgstr ""
-#: inc/manager/class-license-manager.php:566
+#: inc/manager/class-license-manager.php:848
msgid "License creation failed."
msgstr ""
-#: inc/api/class-license-api.php:465
+#: inc/api/class-license-api.php:688
msgid "License deactivated"
msgstr ""
-#: inc/api/class-license-api.php:468
+#: inc/api/class-license-api.php:691
msgid "License deleted"
msgstr ""
-#: inc/api/class-license-api.php:467
+#: inc/api/class-license-api.php:690
msgid "License edited"
msgstr ""
-#: inc/manager/class-license-manager.php:548
+#: inc/manager/class-license-manager.php:822
msgid "License edited successfully."
msgstr ""
-#: inc/manager/class-api-manager.php:179
+#: inc/manager/class-api-manager.php:262
msgid "License events"
msgstr ""
-#: inc/table/class-licenses-table.php:39
+#: inc/table/class-licenses-table.php:40
#: inc/templates/admin/plugin-licenses-page.php:20
msgid "License Key"
msgstr ""
@@ -1146,9 +1150,9 @@ msgstr ""
msgid "License Key ID"
msgstr ""
-#: inc/api/class-license-api.php:171 inc/api/class-license-api.php:184
-#: inc/api/class-license-api.php:221 inc/api/class-license-api.php:242
-#: inc/api/class-license-api.php:296 inc/api/class-license-api.php:309
+#: inc/api/class-license-api.php:239 inc/api/class-license-api.php:252
+#: inc/api/class-license-api.php:296 inc/api/class-license-api.php:317
+#: inc/api/class-license-api.php:385 inc/api/class-license-api.php:398
msgid "License not found."
msgstr ""
@@ -1156,7 +1160,7 @@ msgstr ""
msgid "License Private API Keys"
msgstr ""
-#: inc/server/license/class-license-server.php:301
+#: inc/server/license/class-license-server.php:304
msgid "License removal failed - database deletion error."
msgstr ""
@@ -1164,30 +1168,30 @@ msgstr ""
msgid "License Status"
msgstr ""
-#: inc/manager/class-license-manager.php:246
-#: inc/manager/class-license-manager.php:252
+#: inc/manager/class-license-manager.php:384
+#: inc/manager/class-license-manager.php:399
msgid "License status"
msgstr ""
-#: inc/server/license/class-license-server.php:217
+#: inc/server/license/class-license-server.php:220
msgid "License update failed - database update error."
msgstr ""
-#: inc/manager/class-license-manager.php:552
+#: inc/manager/class-license-manager.php:826
msgid "License update failed."
msgstr ""
-#: inc/manager/class-license-manager.php:268
-#: inc/manager/class-license-manager.php:279
+#: inc/manager/class-license-manager.php:432
+#: inc/manager/class-license-manager.php:452
#: inc/templates/admin/plugin-licenses-page.php:8
msgid "Licenses"
msgstr ""
-#: inc/api/class-license-api.php:138
+#: inc/api/class-license-api.php:199
msgid "Licenses not found."
msgstr ""
-#: inc/manager/class-license-manager.php:233
+#: inc/manager/class-license-manager.php:351
msgid "Licenses per page"
msgstr ""
@@ -1198,16 +1202,16 @@ msgid ""
"Private API (one IP address or CIDR per line)."
msgstr ""
-#: inc/manager/class-package-manager.php:1218
+#: inc/manager/class-package-manager.php:1254
#: inc/templates/admin/plugin-packages-page.php:158
msgid "Logs max size (in MB)"
msgstr ""
-#: inc/manager/class-package-manager.php:400
+#: inc/manager/class-package-manager.php:430
msgid "Make sure the uploaded file is a zip archive."
msgstr ""
-#: inc/manager/class-package-manager.php:460
+#: inc/manager/class-package-manager.php:490
msgid "Make sure the uploaded file is not empty."
msgstr ""
@@ -1215,7 +1219,7 @@ msgstr ""
msgid "Malformed request"
msgstr ""
-#: inc/api/class-package-api.php:817 inc/api/class-license-api.php:1159
+#: inc/api/class-package-api.php:1293 inc/api/class-license-api.php:1634
msgid "Malformed request."
msgstr ""
@@ -1223,7 +1227,7 @@ msgstr ""
msgid "Manual"
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:121
+#: inc/templates/admin/plugin-licenses-page.php:120
msgid "Max. Allowed Domains"
msgstr ""
@@ -1246,7 +1250,7 @@ msgstr ""
msgid "Miscellaneous"
msgstr ""
-#: inc/manager/class-package-manager.php:425
+#: inc/manager/class-package-manager.php:455
msgid "Missing a temporary folder."
msgstr ""
@@ -1254,6 +1258,14 @@ msgstr ""
msgid "More help..."
msgstr ""
+#: inc/api/class-webhook-api.php:649
+msgid ""
+"Multiple candidate VCS configurations found ; the event has not be processed."
+" Please limit the events sent to the webhook to events specifying the branch "
+"in their payload (such as push), or update your UpdatePulse Server VCS "
+"configuration to avoid branch conflicts."
+msgstr ""
+
#. %1$s is https://version-control-system.tld/identifier/, %2$s is identifier
#: inc/templates/admin/plugin-remote-sources-page.php:74
#: inc/templates/admin/plugin-remote-sources-page.php:274
@@ -1265,47 +1277,47 @@ msgid ""
"Gitlab (no support for Gitlab subgroups)."
msgstr ""
-#: inc/templates/admin/licenses-table-row.php:66
+#: inc/class-utils.php:170 inc/templates/admin/licenses-table-row.php:70
msgid "N/A"
msgstr ""
-#: inc/manager/class-package-manager.php:421
+#: inc/manager/class-package-manager.php:451
msgid "No file was uploaded."
msgstr ""
-#: inc/api/class-package-api.php:69
+#: inc/api/class-package-api.php:139
msgid "No packages found."
msgstr ""
-#: inc/api/class-update-api.php:299
+#: inc/api/class-update-api.php:497
msgid "No server found for this package."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:140
+#: inc/templates/admin/plugin-licenses-page.php:139
msgid "None"
msgstr ""
-#: inc/manager/class-package-manager.php:1212
-#: inc/manager/class-package-manager.php:1219
-#: inc/manager/class-package-manager.php:1226
+#: inc/manager/class-package-manager.php:1248
+#: inc/manager/class-package-manager.php:1255
+#: inc/manager/class-package-manager.php:1262
msgid "Not a valid number"
msgstr ""
#: inc/manager/class-remote-sources-manager.php:658
-#: inc/manager/class-api-manager.php:410 inc/manager/class-api-manager.php:422
-#: inc/manager/class-api-manager.php:434
+#: inc/manager/class-api-manager.php:542 inc/manager/class-api-manager.php:554
+#: inc/manager/class-api-manager.php:566
msgid "Not a valid payload"
msgstr ""
-#: inc/manager/class-cloud-storage-manager.php:200
-#: inc/manager/class-cloud-storage-manager.php:211
-#: inc/manager/class-cloud-storage-manager.php:222
-#: inc/manager/class-cloud-storage-manager.php:233
-#: inc/manager/class-cloud-storage-manager.php:244
+#: inc/manager/class-cloud-storage-manager.php:326
+#: inc/manager/class-cloud-storage-manager.php:337
+#: inc/manager/class-cloud-storage-manager.php:348
+#: inc/manager/class-cloud-storage-manager.php:359
+#: inc/manager/class-cloud-storage-manager.php:370
msgid "Not a valid string"
msgstr ""
-#: inc/manager/class-license-manager.php:261
+#: inc/manager/class-license-manager.php:418
msgid "Not Required"
msgstr ""
@@ -1330,7 +1342,7 @@ msgid ""
"available."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:56
+#: inc/class-utils.php:164
msgid "On Hold"
msgstr ""
@@ -1338,25 +1350,25 @@ msgstr ""
msgid "open an issue on Github"
msgstr ""
-#: inc/manager/class-license-manager.php:512
+#: inc/manager/class-license-manager.php:769
msgid ""
"Operation failed: all the selected licenses have passed their expiry date, "
"or already have the selected status - IDs: "
msgstr ""
-#: inc/manager/class-license-manager.php:465
+#: inc/manager/class-license-manager.php:722
msgid "Operation failed: an unexpected error occured (invalid license data)."
msgstr ""
-#: inc/manager/class-license-manager.php:502
+#: inc/manager/class-license-manager.php:759
msgid "Operation failed: an unexpected error occured (invalid license status)."
msgstr ""
#. %1$s is the option display name, %2$s is the condition for update
#: inc/manager/class-remote-sources-manager.php:477
-#: inc/manager/class-package-manager.php:1178
-#: inc/manager/class-license-manager.php:423
-#: inc/manager/class-api-manager.php:377
+#: inc/manager/class-package-manager.php:1214
+#: inc/manager/class-license-manager.php:658
+#: inc/manager/class-api-manager.php:488
#, php-format
msgid "Option %1$s was not updated. Reason: %2$s"
msgstr ""
@@ -1377,27 +1389,27 @@ msgstr ""
msgid "Other packages provided by the VCS will need to be registered again."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:103
+#: inc/templates/admin/plugin-licenses-page.php:102
msgid "Owner Name"
msgstr ""
#. %1$s: package ID, %2$s: scheduled date and time
-#: inc/api/class-webhook-api.php:370
+#: inc/api/class-webhook-api.php:579
#, php-format
msgid "Package %1$s has been scheduled for download: %2$s."
msgstr ""
#. %s: package ID
-#: inc/api/class-webhook-api.php:392
+#: inc/api/class-webhook-api.php:607
#, php-format
msgid "Package %s downloaded."
msgstr ""
-#: inc/api/class-package-api.php:388
+#: inc/api/class-package-api.php:696
msgid "Package added or updated"
msgstr ""
-#: inc/api/class-package-api.php:171
+#: inc/api/class-package-api.php:310
msgid "Package already exists."
msgstr ""
@@ -1405,31 +1417,31 @@ msgstr ""
msgid "Package API"
msgstr ""
-#: inc/api/class-package-api.php:803
+#: inc/api/class-package-api.php:1279
msgid "Package API action not found."
msgstr ""
-#: inc/manager/class-api-manager.php:409
+#: inc/manager/class-api-manager.php:541
msgid "Package API Authentication Keys"
msgstr ""
-#: inc/api/class-package-api.php:183
+#: inc/api/class-package-api.php:322
msgid "Package could not be added - invalid parameters."
msgstr ""
-#: inc/api/class-package-api.php:139
+#: inc/api/class-package-api.php:253
msgid "Package could not be edited - invalid parameters."
msgstr ""
-#: inc/api/class-package-api.php:389
+#: inc/api/class-package-api.php:697
msgid "Package deleted"
msgstr ""
-#: inc/api/class-package-api.php:390
+#: inc/api/class-package-api.php:698
msgid "Package downloaded via a signed URL"
msgstr ""
-#: inc/manager/class-api-manager.php:175
+#: inc/manager/class-api-manager.php:258
msgid "Package events"
msgstr ""
@@ -1441,13 +1453,13 @@ msgstr ""
msgid "Package Name"
msgstr ""
-#: inc/manager/class-cloud-storage-manager.php:797
+#: inc/manager/class-cloud-storage-manager.php:1050
msgid "Package not found"
msgstr ""
-#: inc/api/class-package-api.php:97 inc/api/class-package-api.php:127
-#: inc/api/class-package-api.php:204 inc/api/class-package-api.php:219
-#: inc/api/class-package-api.php:270
+#: inc/api/class-package-api.php:192 inc/api/class-package-api.php:241
+#: inc/api/class-package-api.php:383 inc/api/class-package-api.php:408
+#: inc/api/class-package-api.php:499
msgid "Package not found."
msgstr ""
@@ -1455,12 +1467,12 @@ msgstr ""
msgid "Package Private API Keys"
msgstr ""
-#: inc/table/class-licenses-table.php:43
+#: inc/table/class-licenses-table.php:44
#: inc/templates/admin/plugin-licenses-page.php:39
msgid "Package Slug"
msgstr ""
-#: inc/table/class-licenses-table.php:42
+#: inc/table/class-licenses-table.php:43
#: inc/templates/admin/plugin-licenses-page.php:29
msgid "Package Type"
msgstr ""
@@ -1484,7 +1496,7 @@ msgstr ""
msgid "Packages per page"
msgstr ""
-#: inc/manager/class-license-manager.php:211
+#: inc/manager/class-license-manager.php:315
msgid ""
"Packages requiring these licenses will not be able to get a successful "
"response from this server."
@@ -1494,12 +1506,12 @@ msgstr ""
msgid "Payload URL"
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:53
+#: inc/class-utils.php:158
msgid "Pending"
msgstr ""
#. %1$s is the path to the plugin's data directory
-#: inc/manager/class-data-manager.php:51
+#: inc/manager/class-data-manager.php:85
#, php-format
msgid ""
"Permission errors creating %1$s - could not setup the data directory. Please "
@@ -1541,7 +1553,7 @@ msgid ""
"Version Control System when calling the Webhook."
msgstr ""
-#: inc/manager/class-api-manager.php:421
+#: inc/manager/class-api-manager.php:553
msgid "Private API Authentication Key"
msgstr ""
@@ -1564,12 +1576,12 @@ msgstr ""
msgid "register packages"
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:130
+#: inc/templates/admin/plugin-licenses-page.php:129
msgid "Registered Domains"
msgstr ""
-#: inc/table/class-licenses-table.php:40
-#: inc/templates/admin/plugin-licenses-page.php:94
+#: inc/table/class-licenses-table.php:41
+#: inc/templates/admin/plugin-licenses-page.php:93
msgid "Registered Email"
msgstr ""
@@ -1602,11 +1614,11 @@ msgstr ""
msgid "Requests optimisation"
msgstr ""
-#: inc/manager/class-license-manager.php:261
+#: inc/manager/class-license-manager.php:418
msgid "Required"
msgstr ""
-#: inc/api/class-package-api.php:380
+#: inc/api/class-package-api.php:679
msgid "Retrieve secure URLs for downloading packages"
msgstr ""
@@ -1619,8 +1631,8 @@ msgstr ""
#: inc/templates/admin/plugin-api-page.php:69
#: inc/templates/admin/plugin-api-page.php:137
#: inc/templates/admin/plugin-api-page.php:212
-#: inc/templates/admin/plugin-licenses-page.php:171
-#: inc/templates/admin/plugin-licenses-page.php:197
+#: inc/templates/admin/plugin-licenses-page.php:170
+#: inc/templates/admin/plugin-licenses-page.php:196
msgid "Save"
msgstr ""
@@ -1652,11 +1664,11 @@ msgstr ""
msgid "Select a VCS"
msgstr ""
-#: inc/manager/class-license-manager.php:537
+#: inc/manager/class-license-manager.php:803
msgid "Selected licenses deleted - IDs: "
msgstr ""
-#: inc/manager/class-package-manager.php:559
+#: inc/manager/class-package-manager.php:589
#: inc/templates/admin/plugin-remote-sources-page.php:51
msgid "Self-hosted"
msgstr ""
@@ -1673,7 +1685,7 @@ msgstr ""
msgid "Send a test request to the Version Control System."
msgstr ""
-#: inc/table/class-licenses-table.php:254
+#: inc/table/class-licenses-table.php:260
msgid "Set to Pending"
msgstr ""
@@ -1687,15 +1699,15 @@ msgstr ""
msgid "Size"
msgstr ""
-#: inc/manager/class-cloud-storage-manager.php:189
+#: inc/manager/class-cloud-storage-manager.php:315
msgid "Something went wrong"
msgstr ""
-#: inc/table/class-licenses-table.php:41
+#: inc/table/class-licenses-table.php:42
msgid "Status"
msgstr ""
-#: inc/manager/class-license-manager.php:510
+#: inc/manager/class-license-manager.php:767
msgid ""
"Status of the selected licenses updated successfully where applicable - IDs "
"of updated licenses: "
@@ -1748,7 +1760,7 @@ msgstr ""
msgid "The Access Key provided by the Cloud Storage service provider."
msgstr ""
-#: inc/server/license/class-license-server.php:921
+#: inc/server/license/class-license-server.php:924
msgid "The allowed domains must be an array."
msgstr ""
@@ -1769,15 +1781,15 @@ msgid ""
"System."
msgstr ""
-#: inc/server/license/class-license-server.php:907
+#: inc/server/license/class-license-server.php:910
msgid "The company name must be a string."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:116
+#: inc/templates/admin/plugin-licenses-page.php:115
msgid "The company of the owner of this license."
msgstr ""
-#: inc/server/license/class-license-server.php:852
+#: inc/server/license/class-license-server.php:855
msgid ""
"The creation date is required and must follow the following format: YYYY-MM-"
"DD"
@@ -1795,11 +1807,11 @@ msgstr ""
msgid "The download URL token has expired."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:98
+#: inc/templates/admin/plugin-licenses-page.php:97
msgid "The email registered with this license."
msgstr ""
-#: inc/server/license/class-license-server.php:872
+#: inc/server/license/class-license-server.php:875
msgid "The expiry date must follow the following format: YYYY-MM-DD"
msgstr ""
@@ -1812,66 +1824,66 @@ msgid ""
"generic packages."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:107
+#: inc/templates/admin/plugin-licenses-page.php:106
msgid "The full name of the owner of the license."
msgstr ""
-#: inc/server/license/class-license-server.php:779
+#: inc/server/license/class-license-server.php:782
msgid "The ID must be an integer."
msgstr ""
#. %s is the license key
-#: inc/api/class-license-api.php:523
+#: inc/api/class-license-api.php:759
#, php-format
msgid "The license `%s` has been activated on UpdatePulse Server"
msgstr ""
#. %s is the license key
-#: inc/api/class-license-api.php:513
+#: inc/api/class-license-api.php:749
#, php-format
msgid "The license `%s` has been added on UpdatePulse Server"
msgstr ""
#. %s is the license key
-#: inc/api/class-license-api.php:528
+#: inc/api/class-license-api.php:764
#, php-format
msgid "The license `%s` has been deactivated on UpdatePulse Server"
msgstr ""
#. %s is the license key
-#: inc/api/class-license-api.php:518
+#: inc/api/class-license-api.php:754
#, php-format
msgid "The license `%s` has been deleted on UpdatePulse Server"
msgstr ""
#. %s is the license key
-#: inc/api/class-license-api.php:508
+#: inc/api/class-license-api.php:744
#, php-format
msgid "The license `%s` has been edited on UpdatePulse Server"
msgstr ""
-#: inc/api/class-license-api.php:830
+#: inc/api/class-license-api.php:1178
msgid "The license cannot be activated due to its current status."
msgstr ""
-#: inc/api/class-license-api.php:936
+#: inc/api/class-license-api.php:1341
msgid "The license cannot be deactivated before the specified date."
msgstr ""
-#: inc/server/license/class-license-server.php:776
-#: inc/server/license/class-license-server.php:792
+#: inc/server/license/class-license-server.php:779
+#: inc/server/license/class-license-server.php:795
msgid "The license cannot be found."
msgstr ""
-#: inc/api/class-license-api.php:846
+#: inc/api/class-license-api.php:1201
msgid "The license has reached the maximum allowed activations for domains."
msgstr ""
-#: inc/api/class-license-api.php:856
+#: inc/api/class-license-api.php:1218
msgid "The license is already activated for the specified domain."
msgstr ""
-#: inc/api/class-license-api.php:926
+#: inc/api/class-license-api.php:1324
msgid "The license is already deactivated for the specified domain."
msgstr ""
@@ -1881,31 +1893,31 @@ msgstr ""
msgid "The License Key ID must contain only numbers, letters, %1$s and %2$s."
msgstr ""
-#: inc/server/license/class-license-server.php:804
+#: inc/server/license/class-license-server.php:807
msgid "The license key is required and must be a string."
msgstr ""
-#: inc/server/license/class-license-server.php:899
+#: inc/server/license/class-license-server.php:902
msgid "The license owner name must be a string."
msgstr ""
-#: inc/server/license/class-license-server.php:835
+#: inc/server/license/class-license-server.php:838
msgid "The license status is invalid."
msgstr ""
-#: inc/server/license/class-license-server.php:548
+#: inc/server/license/class-license-server.php:551
msgid "The limit must be an integer."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:125
+#: inc/templates/admin/plugin-licenses-page.php:124
msgid "The maximum number of domains on which this license can be used."
msgstr ""
-#: inc/server/license/class-license-server.php:828
+#: inc/server/license/class-license-server.php:831
msgid "The number of allowed domains is required and must be greater than 1."
msgstr ""
-#: inc/server/license/class-license-server.php:560
+#: inc/server/license/class-license-server.php:563
msgid "The offset must be a positive integer."
msgstr ""
@@ -1916,14 +1928,14 @@ msgid "The Package Key ID must contain only numbers, letters, %1$s and %2$s."
msgstr ""
#. %s is the package slug
-#: inc/api/class-package-api.php:367
+#: inc/api/class-package-api.php:657
#, php-format
msgid ""
"The package of `%s` has been securely downloaded from UpdatePulse Server"
msgstr ""
#. %1$s is the package type, %2$s is the package slug
-#: inc/api/class-package-api.php:355 inc/manager/class-package-manager.php:522
+#: inc/api/class-package-api.php:637 inc/manager/class-package-manager.php:552
#, php-format
msgid ""
"The package of type `%1$s` and slug `%2$s` has been deleted on UpdatePulse "
@@ -1931,20 +1943,20 @@ msgid ""
msgstr ""
#. %1$s is the package type, %2$s is the pakage slug
-#: inc/api/class-package-api.php:330
+#: inc/api/class-package-api.php:593
#, php-format
msgid ""
"The package of type `%1$s` and slug `%2$s` has been updated on UpdatePulse "
"Server"
msgstr ""
-#: inc/server/license/class-license-server.php:882
+#: inc/server/license/class-license-server.php:885
msgid ""
"The package slug is required and must contain only alphanumeric characters "
"or dashes."
msgstr ""
-#: inc/server/license/class-license-server.php:891
+#: inc/server/license/class-license-server.php:894
msgid ""
"The package type is required and must be \"generic\", \"plugin\" or "
"\"theme\"."
@@ -1962,23 +1974,23 @@ msgid ""
"dashes are allowed."
msgstr ""
-#: inc/api/class-package-api.php:575
+#: inc/api/class-package-api.php:976
msgid "The provided file does not match the provided hash."
msgstr ""
-#: inc/api/class-package-api.php:624
+#: inc/api/class-package-api.php:1025
msgid "The provided file is not a valid package."
msgstr ""
-#: inc/api/class-package-api.php:594
+#: inc/api/class-package-api.php:995
msgid "The provided file is not a valid ZIP file."
msgstr ""
-#: inc/api/class-license-api.php:991
+#: inc/api/class-license-api.php:1425
msgid "The provided license key is invalid."
msgstr ""
-#: functions.php:277 inc/api/class-package-api.php:648
+#: functions.php:560 inc/api/class-package-api.php:1049
msgid "The provided VCS information is not valid"
msgstr ""
@@ -1992,11 +2004,11 @@ msgid ""
"service provider."
msgstr ""
-#: inc/server/license/class-license-server.php:842
+#: inc/server/license/class-license-server.php:845
msgid "The registered email is required and must be a valid email address."
msgstr ""
-#: inc/server/license/class-license-server.php:862
+#: inc/server/license/class-license-server.php:865
msgid "The renewal date must follow the following format: YYYY-MM-DD"
msgstr ""
@@ -2024,7 +2036,7 @@ msgstr ""
msgid "The Secret Key provided by the Cloud Storage service provider."
msgstr ""
-#: inc/server/license/class-license-server.php:915
+#: inc/server/license/class-license-server.php:918
msgid "The transaction ID must be a string."
msgstr ""
@@ -2034,25 +2046,25 @@ msgid ""
"it is unique in the database."
msgstr ""
-#: inc/manager/class-package-manager.php:413
+#: inc/manager/class-package-manager.php:443
msgid ""
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in "
"the HTML form."
msgstr ""
-#: inc/manager/class-package-manager.php:409
+#: inc/manager/class-package-manager.php:439
msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini."
msgstr ""
-#: inc/manager/class-package-manager.php:417
+#: inc/manager/class-package-manager.php:447
msgid "The uploaded file was only partially uploaded."
msgstr ""
-#: inc/manager/class-package-manager.php:503
+#: inc/manager/class-package-manager.php:533
msgid "The uploaded package could not be moved to the packages directory."
msgstr ""
-#: inc/manager/class-package-manager.php:475
+#: inc/manager/class-package-manager.php:505
msgid "The uploaded package is not a valid Generic, Theme or Plugin package."
msgstr ""
@@ -2065,25 +2077,25 @@ msgstr ""
msgid "The URL of the Version Control System where packages are hosted."
msgstr ""
-#: inc/server/license/class-license-server.php:634
+#: inc/server/license/class-license-server.php:637
msgid "The value for the BETWEEN operator must be an array with two elements."
msgstr ""
-#: inc/server/license/class-license-server.php:641
+#: inc/server/license/class-license-server.php:644
msgid "The value for the IN and NOT IN operators must be an array."
msgstr ""
-#: inc/server/license/class-license-server.php:648
+#: inc/server/license/class-license-server.php:651
msgid "The value for the IN and NOT IN operators must not be empty."
msgstr ""
-#: inc/server/license/class-license-server.php:658
+#: inc/server/license/class-license-server.php:661
msgid ""
"The value must be a scalar for all operators except BETWEEN, NOT BETWEEN, IN,"
" and NOT IN operators."
msgstr ""
-#: functions.php:608 inc/api/class-webhook-api.php:141
+#: functions.php:1138 inc/api/class-webhook-api.php:239
msgid "The webhook payload must contain an event string and a content."
msgstr ""
@@ -2098,9 +2110,9 @@ msgid "Theme"
msgstr ""
#: inc/manager/class-remote-sources-manager.php:431
-#: inc/manager/class-package-manager.php:1147
-#: inc/manager/class-license-manager.php:397
-#: inc/manager/class-api-manager.php:199
+#: inc/manager/class-package-manager.php:1183
+#: inc/manager/class-license-manager.php:632
+#: inc/manager/class-api-manager.php:290
msgid ""
"There was an error validating the form. It may be outdated. Please reload "
"the page."
@@ -2113,7 +2125,7 @@ msgid ""
"UpdatePulse Server is uninstalled."
msgstr ""
-#: inc/api/class-license-api.php:1005
+#: inc/api/class-license-api.php:1439
msgid "This is an unexpected error. Please contact support."
msgstr ""
@@ -2168,7 +2180,7 @@ msgid ""
"upon activating UpdatePulse Server."
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:148
+#: inc/templates/admin/plugin-licenses-page.php:147
msgid "Transaction ID"
msgstr ""
@@ -2176,18 +2188,18 @@ msgstr ""
msgid "Type"
msgstr ""
-#: inc/templates/admin/plugin-licenses-page.php:163
+#: inc/templates/admin/plugin-licenses-page.php:162
msgid ""
"Typically used by plugins & API integrations; proceed with caution when "
"editing."
msgstr ""
-#: inc/api/class-package-api.php:810 inc/api/class-license-api.php:1152
+#: inc/api/class-package-api.php:1286 inc/api/class-license-api.php:1627
#: inc/nonce/class-nonce.php:96
msgid "Unauthorized access."
msgstr ""
-#: inc/api/class-package-api.php:750 inc/api/class-license-api.php:1096
+#: inc/api/class-package-api.php:1210 inc/api/class-license-api.php:1556
msgid "Unauthorized GET method."
msgstr ""
@@ -2195,7 +2207,7 @@ msgstr ""
msgid "Unavailable file system."
msgstr ""
-#: functions.php:31
+#: functions.php:52
msgid "Undefined"
msgstr ""
@@ -2209,9 +2221,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
-#: inc/api/class-license-api.php:214 inc/api/class-license-api.php:234
-#: inc/api/class-license-api.php:265 inc/api/class-license-api.php:279
-#: inc/api/class-license-api.php:998
+#: inc/api/class-license-api.php:289 inc/api/class-license-api.php:309
+#: inc/api/class-license-api.php:347 inc/api/class-license-api.php:361
+#: inc/api/class-license-api.php:1432
msgid "Unknown error."
msgstr ""
@@ -2227,7 +2239,7 @@ msgid ""
"file in %2$s would be %3$s."
msgstr ""
-#: inc/api/class-license-api.php:454
+#: inc/api/class-license-api.php:670
msgid "Update license records"
msgstr ""
@@ -2240,7 +2252,7 @@ msgstr ""
msgid "UpdatePulse Server"
msgstr ""
-#: inc/manager/class-api-manager.php:98
+#: inc/manager/class-api-manager.php:134
msgid "UpdatePulse Server - API & Webhooks"
msgstr ""
@@ -2248,7 +2260,7 @@ msgstr ""
msgid "UpdatePulse Server - Help"
msgstr ""
-#: inc/manager/class-license-manager.php:267
+#: inc/manager/class-license-manager.php:431
msgid "UpdatePulse Server - Licenses"
msgstr ""
@@ -2262,28 +2274,28 @@ msgstr ""
msgid "UpdatePulse Server Integration"
msgstr ""
-#: inc/manager/class-license-manager.php:404
+#: inc/manager/class-license-manager.php:639
msgid "UpdatePulse Server license options successfully updated."
msgstr ""
#: inc/manager/class-remote-sources-manager.php:438
-#: inc/manager/class-package-manager.php:1154
-#: inc/manager/class-api-manager.php:206
+#: inc/manager/class-package-manager.php:1190
+#: inc/manager/class-api-manager.php:297
msgid "UpdatePulse Server options successfully updated"
msgstr ""
#. the separator between summaries; example: All package events, 3 license events
-#: inc/manager/class-api-manager.php:84
+#: inc/manager/class-api-manager.php:113
msgctxt "UpdatePulse Server separator between API summaries"
msgid ", "
msgstr ""
-#: inc/manager/class-api-manager.php:82
+#: inc/manager/class-api-manager.php:111
msgctxt "UpdatePulse Server webhook event type"
msgid "license"
msgstr ""
-#: inc/manager/class-api-manager.php:81
+#: inc/manager/class-api-manager.php:110
msgctxt "UpdatePulse Server webhook event type"
msgid "package"
msgstr ""
@@ -2296,7 +2308,7 @@ msgstr ""
msgid "Upload package"
msgstr ""
-#: inc/manager/class-cloud-storage-manager.php:188
+#: inc/manager/class-cloud-storage-manager.php:314
#: inc/templates/admin/cloud-storage-options.php:8
msgid "Use Cloud Storage"
msgstr ""
@@ -2367,13 +2379,13 @@ msgid "Version Control Systems "
msgstr ""
#. %s is the virtual folder
-#: inc/manager/class-cloud-storage-manager.php:492
+#: inc/manager/class-cloud-storage-manager.php:673
#, php-format
msgid "Virtual folder \"%s\" found."
msgstr ""
#. %s is the virtual folder
-#: inc/manager/class-cloud-storage-manager.php:481
+#: inc/manager/class-cloud-storage-manager.php:662
#, php-format
msgid "Virtual folder \"%s\" was created successfully."
msgstr ""
@@ -2403,8 +2415,8 @@ msgid ""
msgstr ""
#. %s is the virtual folder
-#: inc/manager/class-cloud-storage-manager.php:486
-#: inc/manager/class-cloud-storage-manager.php:534
+#: inc/manager/class-cloud-storage-manager.php:667
+#: inc/manager/class-cloud-storage-manager.php:722
#, php-format
msgid ""
"WARNING: Unable to create Virtual folder \"%s\". The Cloud Storage feature "
@@ -2428,7 +2440,7 @@ msgid ""
"and %6$s is the slug of the package to register."
msgstr ""
-#: inc/manager/class-api-manager.php:433
+#: inc/manager/class-api-manager.php:565
#: inc/templates/admin/plugin-api-page.php:140
#: inc/templates/admin/plugin-api-page.php:140
msgid "Webhooks"
@@ -2471,15 +2483,15 @@ msgid ""
"also loaded, with its own plugins and themes."
msgstr ""
-#: inc/manager/class-api-manager.php:61
+#: inc/manager/class-api-manager.php:90
msgid "You are about to add a Webhook without License API Key ID."
msgstr ""
-#: inc/manager/class-api-manager.php:55
+#: inc/manager/class-api-manager.php:84
msgid "You are about to delete a Webhook."
msgstr ""
-#: inc/manager/class-license-manager.php:209
+#: inc/manager/class-license-manager.php:313
msgid "You are about to delete all the licenses from this server."
msgstr ""
@@ -2488,7 +2500,7 @@ msgstr ""
msgid "You are about to delete all the packages from this server."
msgstr ""
-#: inc/manager/class-api-manager.php:49
+#: inc/manager/class-api-manager.php:78
msgid "You are about to delete an API key."
msgstr ""
diff --git a/lib/anyape-crypto/crypto.php b/lib/anyape-crypto/crypto.php
index dab2ff0..a713e9e 100644
--- a/lib/anyape-crypto/crypto.php
+++ b/lib/anyape-crypto/crypto.php
@@ -4,11 +4,33 @@ namespace Anyape\Crypto;
use Exception;
+/**
+ * Cryptography utility class for encryption, decryption, and HMAC operations.
+ *
+ * This class provides methods for secure data encryption and authentication
+ * using AES-256-CBC encryption and HMAC-SHA256 signatures.
+ */
class Crypto {
- const METHOD = 'aes-256-cbc';
+ /**
+ * The encryption cipher method used for all encryption operations.
+ */
+ const METHOD = 'aes-256-cbc';
+
+ /**
+ * Character used to replace forward slashes in base64url encoding.
+ */
const SLASH_REPLACE = '_';
+ /**
+ * Encrypts a message using AES-256-CBC with HMAC authentication.
+ *
+ * @param string $message The plaintext message to encrypt.
+ * @param string $crypt_key The encryption key.
+ * @param string $sign_key The signing key for HMAC authentication.
+ * @return string|false The encrypted, authenticated, and encoded string, or false on failure.
+ * @throws Exception When the key length is invalid.
+ */
public static function encrypt( $message, $crypt_key, $sign_key ) {
$cipher = false;
$crypt_key = hex2bin( hash( 'sha256', $crypt_key ) );
@@ -29,6 +51,15 @@ class Crypto {
return $cipher;
}
+ /**
+ * Decrypts an encrypted message after verifying its HMAC signature.
+ *
+ * @param string $cipher The encrypted, authenticated, and encoded string.
+ * @param string $crypt_key The encryption key.
+ * @param string $sign_key The signing key for HMAC verification.
+ * @return string|false The decrypted message, or false if verification fails.
+ * @throws Exception When the key length is invalid.
+ */
public static function decrypt( $cipher, $crypt_key, $sign_key ) {
$message = false;
$crypt_key = hex2bin( hash( 'sha256', $crypt_key ) );
@@ -51,12 +82,26 @@ class Crypto {
return $message;
}
+ /**
+ * Signs a message using HMAC-SHA256.
+ *
+ * @param string $message The message to sign.
+ * @param string $sign_key The signing key.
+ * @return string The HMAC signature.
+ */
public static function hmac_sign( $message, $sign_key ) {
$signature = hash_hmac( 'sha256', $message, $sign_key, true );
return $signature;
}
+ /**
+ * Verifies an HMAC signature.
+ *
+ * @param string $original_val The original HMAC signature.
+ * @param string $new_val The new HMAC signature to compare.
+ * @return bool True if the signatures match, false otherwise.
+ */
public static function hmac_verify( $original_val, $new_val ) {
if ( function_exists( 'hash_equals' ) ) {
@@ -82,10 +127,22 @@ class Crypto {
return 0 === $result;
}
+ /**
+ * Encodes data using base64url encoding.
+ *
+ * @param string $s The data to encode.
+ * @return string The base64url encoded string.
+ */
public static function base64url_encode( $s ) {
return str_replace( '/', self::SLASH_REPLACE, base64_encode( $s ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}
+ /**
+ * Decodes a base64url encoded string.
+ *
+ * @param string $s The base64url encoded string.
+ * @return string The decoded data.
+ */
public static function base64url_decode( $s ) {
return base64_decode( str_replace( self::SLASH_REPLACE, '/', $s ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
}
diff --git a/lib/anyape-package-parser/package-parser.php b/lib/anyape-package-parser/package-parser.php
index f03abf4..587265e 100644
--- a/lib/anyape-package-parser/package-parser.php
+++ b/lib/anyape-package-parser/package-parser.php
@@ -7,25 +7,30 @@ use ZipArchive as SystemZipArchive;
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
+/**
+ * Package parser for WordPress plugins and themes.
+ *
+ * This class provides functionality to extract and analyze information from WordPress plugin and theme packages, or generic packages, in ZIP format.
+ */
class Parser {
/**
- * Extract headers and readme.txt data from a ZIP archive that contains a package.
+ * Extracts and parses metadata from a WordPress plugin or theme ZIP package.
*
- * Returns an associative array with these keys:
- * 'type' - Detected package type. This can be either "plugin" or "theme".
- * 'header' - An array of package headers. See get_plugin_data() or WP_Theme for details.
- * 'readme' - An array of metadata extracted from readme.txt. @see self::parseReadme()
- * 'pluginFile' - The name of the PHP file where the plugin headers were found relative to the root directory of the ZIP archive.
- * 'stylesheet' - The relative path to the style.css file that contains theme headers, if any.
+ * Analyzes the contents of a ZIP archive to determine if it contains a valid WordPress plugin, theme, or generic package, then extracts relevant metadata from header files and readme.txt (if present).
*
- * The 'readme' key will only be present if the input archive contains a readme.txt file
- * formatted according to WordPress.org readme standards. Similarly, 'pluginFile' and
- * 'stylesheet' will only be present if the archive contains a plugin or a theme, respectively.
+ * The function returns an array with the following structure:
+ * 'type' - Package type: "plugin", "theme", or "generic"
+ * 'header' - Package header information (varies by type)
+ * 'readme' - Metadata extracted from readme.txt (if available)
+ * 'pluginFile' - Path to the main plugin file (for plugins only)
+ * 'stylesheet' - Path to style.css file (for themes only)
+ * 'generic_file' - Path to updatepulse.json (for generic packages only)
+ * 'extra' - Additional metadata like icons and banners (if available)
*
* @param string $package_filename The path to the ZIP package.
* @param bool $apply_markdown Whether to transform markup used in readme.txt to HTML. Defaults to false.
- * @return array|bool Either an associative array or FALSE if the input file is not a valid ZIP archive or doesn't contain a package.
+ * @return array|bool Package information array or FALSE if invalid/unreadable.
*/
public static function parse_package( $package_filename, $apply_markdown = false ) {
@@ -39,7 +44,7 @@ class Parser {
return false;
}
- //Find and parse the package file and ( optionally ) readme.txt.
+ // Find and parse the package file and (optionally) readme.txt
$header = null;
$readme = null;
$plugin_file = null;
@@ -57,27 +62,36 @@ class Parser {
$file_index++
) {
$info = $entries[ $file_index ];
- //Normalize filename: convert backslashes to slashes, remove leading slashes.
- $file_name = trim( str_replace( '\\', '/', $info['name'] ), '/' );
- $file_name = ltrim( $file_name, '/' );
+ // Normalize filename: convert backslashes to slashes, remove leading slashes
+ $file_name = trim( str_replace( '\\', '/', $info['name'] ), '/' );
+ $file_name = ltrim( $file_name, '/' );
+
+ // Add path traversal protection
+ if ( false !== strpos( $file_name, '../' ) || false !== strpos( $file_name, '..\\' ) ) {
+ // Log attempt and skip this file
+ error_log( __METHOD__ . ' Potential path traversal attempt blocked for file: ' . $file_name ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+
+ continue;
+ }
+
$file_name_parts = explode( '.', $file_name );
$extension = strtolower( end( $file_name_parts ) );
$depth = substr_count( $file_name, '/' );
- // Skip empty files, directories and everything that's more than 1 sub-directory deep.
+ // Skip files that are either empty, directories, or nested deeper than one level
if ( ( $depth > 1 ) || $info['isFolder'] ) {
continue;
}
- // readme.txt ( for plugins )?
+ // Check for and parse readme.txt file for plugins
if ( empty( $readme ) && ( strtolower( basename( $file_name ) ) === 'readme.txt' ) ) {
- //Try to parse the readme.
+ // Attempt to parse the readme content
$readme = self::parse_readme( $zip->get_file_contents( $info ), $apply_markdown );
}
$file_contents = null;
- // Theme stylesheet?
+ // Check if the provided file is for a theme
if ( empty( $header ) && ( strtolower( basename( $file_name ) ) === 'style.css' ) ) {
$file_contents = substr( $zip->get_file_contents( $info ), 0, 8 * 1024 );
$header = self::get_theme_headers( $file_contents );
@@ -89,7 +103,7 @@ class Parser {
}
}
- // Main plugin file?
+ // Check if the provided file is for a plugin
if ( empty( $header ) && ( 'php' === $extension ) ) {
$file_contents = substr( $zip->get_file_contents( $info ), 0, 8 * 1024 );
$plugin_file = $file_name;
@@ -98,7 +112,7 @@ class Parser {
$type = 'plugin';
}
- // Generic info file?
+ // Check if the provided file is a generic package
if ( empty( $header ) && ( 'json' === $extension ) && ( basename( $file_name ) === 'updatepulse.json' ) ) {
$file_contents = substr( $zip->get_file_contents( $info ), 0, 8 * 1024 );
$header = self::get_generic_headers( $file_contents );
@@ -121,29 +135,25 @@ class Parser {
}
/**
- * Parse a plugin's readme.txt to extract various plugin metadata.
+ * Extracts metadata from a WordPress plugin/theme readme.txt file.
*
- * Returns an array with the following fields:
- * 'name' - Name of the plugin.
- * 'contributors' - An array of wordpress.org usernames.
- * 'donate' - The plugin's donation link.
- * 'tags' - An array of the plugin's tags.
- * 'requires' - The minimum version of WordPress that the plugin will run on.
- * 'tested' - The latest version of WordPress that the plugin has been tested on.
- * 'stable' - The SVN tag of the latest stable release, or 'trunk'.
- * 'short_description' - The plugin's "short description".
- * 'sections' - An associative array of sections present in the readme.txt.
- * Case and formatting of section headers will be preserved.
+ * Parses the standardized WordPress readme.txt format to extract key information about the plugin or theme, including version requirements, descriptions, and documentation sections.
*
- * Be warned that this function does *not* perfectly emulate the way that WordPress.org
- * parses plugin readme's. In particular, it may mangle certain HTML markup that wp.org
- * handles correctly.
+ * The returned array includes:
+ * 'name' - Plugin/theme name
+ * 'contributors' - List of WordPress.org contributor usernames
+ * 'donate' - Donation URL
+ * 'tags' - Plugin tags/categories
+ * 'requires' - Minimum WordPress version
+ * 'requires_php' - Minimum PHP version
+ * 'tested' - WordPress version tested up to
+ * 'stable' - Stable release tag
+ * 'short_description' - Brief plugin description
+ * 'sections' - Content sections (FAQ, installation, etc.)
*
- * @see http://wordpress.org/extend/plugins/about/readme.txt
- *
- * @param string $readme_txt_contents The contents of a plugin's readme.txt file.
- * @param bool $apply_markdown Whether to transform Markdown used in readme.txt sections to HTML. Defaults to false.
- * @return array|null Associative array, or NULL if the input isn't a valid readme.txt file.
+ * @param string $readme_txt_contents The contents of a readme.txt file.
+ * @param bool $apply_markdown Whether to convert Markdown to HTML. Defaults to false.
+ * @return array|null Parsed readme data or NULL if invalid format.
*/
public static function parse_readme( $readme_txt_contents, $apply_markdown = false ) {
$readme_txt_contents = trim( $readme_txt_contents, " \t\n\r" );
@@ -160,17 +170,17 @@ class Parser {
'sections' => array(),
);
- //The readme.txt header has a fairly fixed structure, so we can parse it line-by-line
+ // Do a line-by-line parse of the readme.txt file
$lines = explode( "\n", $readme_txt_contents );
- //Plugin name is at the very top, e.g. === My Plugin ===
+ // Get the name of the plugin
if ( preg_match( '@===\s*( .+? )\s*===@', array_shift( $lines ), $matches ) ) {
$readme['name'] = $matches[1];
} else {
return null;
}
- //Then there's a bunch of meta fields formatted as "Field: value"
+ // Set up a map of header fields to their corresponding keys in the readme array
$headers = array();
$header_map = array(
'Contributors' => 'contributors',
@@ -182,7 +192,7 @@ class Parser {
'Stable tag' => 'stable',
);
- do { //Parse each readme.txt header
+ do {
$pieces = explode( ':', array_shift( $lines ), 2 );
if ( array_key_exists( $pieces[0], $header_map ) ) {
@@ -193,53 +203,53 @@ class Parser {
$headers[ $header_map[ $pieces[0] ] ] = '';
}
}
- } while ( trim( $pieces[0] ) !== '' ); //Until an empty line is encountered
+ } while ( trim( $pieces[0] ) !== '' );
- //"Contributors" is a comma-separated list. Convert it to an array.
+ // Convert comma-separated contributors list into an array
if ( ! empty( $headers['contributors'] ) ) {
$headers['contributors'] = array_map( 'trim', explode( ',', $headers['contributors'] ) );
}
- //Likewise for "Tags"
+ // Convert comma-separated tags list into an array
if ( ! empty( $headers['tags'] ) ) {
$headers['tags'] = array_map( 'trim', explode( ',', $headers['tags'] ) );
}
$readme = array_merge( $readme, $headers );
- //After the headers comes the short description
+ // Extract the short description from the next line
$readme['short_description'] = array_shift( $lines );
- //Finally, a valid readme.txt also contains one or more "sections" identified by "== Section Name =="
+ // Parse remaining content into sections (e.g., "== Description ==", "== Installation ==", etc.)
$sections = array();
$content_buffer = array();
$current_section = '';
foreach ( $lines as $line ) {
- //Is this a section header?
+ // Check if there is a section header
if ( preg_match( '@^\s*==\s+(.+?)\s+==\s*$@m', $line, $matches ) ) {
- //Flush the content buffer for the previous section, if any
+ // Flush the content buffer for the previous section, if any
if ( ! empty( $current_section ) ) {
$section_content = trim( implode( "\n", $content_buffer ) );
$sections[ $current_section ] = $section_content;
}
- //Start reading a new section
+ // Read a new section
$current_section = $matches[1];
$content_buffer = array();
} else {
- //Buffer all section content
+ // Buffer all section content
$content_buffer[] = $line;
}
}
- //Flush the buffer for the last section
+ // Flush the buffer for the last section
if ( ! empty( $current_section ) ) {
$sections[ $current_section ] = trim( implode( "\n", $content_buffer ) );
}
- //Apply Markdown to sections
+ // Apply Markdown to sections
if ( $apply_markdown ) {
$sections = array_map( __CLASS__ . '::apply_markdown', $sections );
}
@@ -250,41 +260,31 @@ class Parser {
}
/**
- * Transform Markdown markup to HTML.
+ * Converts Markdown syntax to HTML format.
*
- * Tries ( in vain ) to emulate the transformation that WordPress.org applies to readme.txt files.
+ * This method processes text with Markdown formatting and returns HTML content.
+ * It handles WordPress-specific readme.txt formatting conventions, including custom header syntax like "= H4 headers =".
*
- * @param string $text
- * @return string
+ * @param string $text Text content with Markdown formatting
+ * @return string HTML-formatted content
*/
private static function apply_markdown( $text ) {
- //The WP standard for readme files uses some custom markup, like "= H4 headers ="
+ // The WP standard for readme files uses some custom markup, like "= H4 headers ="
$text = preg_replace( '@^\s*=\s*( .+? )\s*=\s*$@m', "\n####$1####\n", $text );
return Parsedown::instance()->text( $text );
}
/**
- * Parse the plugin contents to retrieve plugin's metadata headers.
+ * Extracts plugin header metadata from PHP file content.
*
- * Adapted from the get_plugin_data() function used by WordPress.
- * Returns an array that contains the following:
- * 'Name' - Name of the plugin.
- * 'Title' - Title of the plugin and the link to the plugin's web site.
- * 'Description' - Description of what the plugin does and/or notes from the author.
- * 'Author' - The author's name.
- * 'AuthorURI' - The author's web site address.
- * 'Version' - The plugin version number.
- * 'PluginURI' - Plugin web site address.
- * 'TextDomain' - Plugin's text domain for localization.
- * 'DomainPath' - Plugin's relative directory path to .mo files.
- * 'Network' - Boolean. Whether the plugin can only be activated network wide.
+ * Parses a plugin file to extract standard WordPress plugin headers like name, version, author information, and other metadata. This mimics WordPress's get_plugin_data() function to handle plugin header extraction.
*
- * If the input string doesn't appear to contain a valid plugin header, the function
- * will return NULL.
+ * The returned array includes:
+ * 'Name', 'Title', 'Description', 'Author', 'AuthorURI', 'Version', 'PluginURI', 'TextDomain', 'DomainPath', 'Network', 'Depends', 'Provides', 'RequiresPHP', and others.
*
* @param string $file_contents Contents of the plugin file
- * @return array|null See above for description.
+ * @return array|null Plugin metadata or NULL if no valid plugin header found
*/
public static function get_plugin_headers( $file_contents ) {
//[Internal name => Name used in the plugin file]
@@ -308,7 +308,6 @@ class Parser {
)
);
- //Site Wide Only is the old header for Network.
if ( empty( $headers['Network'] ) && ! empty( $headers['_sitewide'] ) ) {
$headers['Network'] = $headers['_sitewide'];
}
@@ -317,20 +316,20 @@ class Parser {
$headers['Network'] = ( strtolower( $headers['Network'] ) === 'true' );
- //For backward compatibility by default Title is the same as Name.
+ // For backward compatibility, by default, Title is the same as Name.
$headers['Title'] = $headers['Name'];
- //"Depends" is a comma-separated list. Convert it to an array.
+ // Comma-separated list. Convert it to an array.
if ( ! empty( $headers['Depends'] ) ) {
$headers['Depends'] = array_map( 'trim', explode( ',', $headers['Depends'] ) );
}
- //Same for "Provides"
+ // Comma-separated list. Convert it to an array.
if ( ! empty( $headers['Provides'] ) ) {
$headers['Provides'] = array_map( 'trim', explode( ',', $headers['Provides'] ) );
}
- //If it doesn't have a name, it's probably not a plugin.
+ // If no name is found, return null - not a plugin.
if ( empty( $headers['Name'] ) ) {
return null;
}
@@ -339,27 +338,14 @@ class Parser {
}
/**
- * Parse the theme stylesheet to retrieve its metadata headers.
+ * Extracts theme metadata from style.css file content.
*
- * Adapted from the get_theme_data() function and the WP_Theme class in WordPress.
- * Returns an array that contains the following:
- * 'Name' - Name of the theme.
- * 'Description' - Theme description.
- * 'Author' - The author's name
- * 'AuthorURI' - The authors web site address.
- * 'Version' - The theme version number.
- * 'ThemeURI' - Theme web site address.
- * 'Template' - The slug of the parent theme. Only applies to child themes.
- * 'Status' - Unknown. Included for completeness.
- * 'Tags' - An array of tags.
- * 'TextDomain' - Theme's text domain for localization.
- * 'DomainPath' - Theme's relative directory path to .mo files.
+ * Analyzes a WordPress theme's style.css file to extract standardized theme headers that provide information about the theme, including name, version, author details, and theme dependencies.
*
- * If the input string doesn't appear to contain a valid theme header, the function
- * will return NULL.
+ * The returned array includes: 'Name', 'Description', 'Author', 'AuthorURI', 'Version', 'ThemeURI', 'Template' (parent theme), 'Tags', 'TextDomain', 'DomainPath', and more.
*
- * @param string $file_contents Contents of the theme stylesheet.
- * @return array|null See above for description.
+ * @param string $file_contents Contents of the theme stylesheet
+ * @return array|null Theme metadata or NULL if no valid theme header found
*/
public static function get_theme_headers( $file_contents ) {
//[Internal name => Name used in the theme file]
@@ -383,7 +369,7 @@ class Parser {
$headers['Tags'] = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( $headers['Tags'] ) ) ) );
- //If it doesn't have a name, it's probably not a valid theme.
+ // If no name is found, return null - not a theme.
if ( empty( $headers['Name'] ) ) {
return null;
}
@@ -392,16 +378,15 @@ class Parser {
}
/**
- * Parse the generic package's headers from updatepulse.json file.
- * Returns an array that may contain the following:
- * 'Name'
- * 'Version'
- * 'Homepage'
- * 'Author'
- * 'AuthorURI'
- * 'Description'
- * @param string $file_contents Contents of the package file
- * @return array See above for description.
+ * Extracts generic package metadata from updatepulse.json file.
+ *
+ * Parses a JSON file containing package metadata for non-standard WordPress packages. This allows UpdatePulse to manage generic software packages alongside plugins and themes.
+ *
+ * The function extracts standard package information fields:
+ * 'Name', 'Version', 'Homepage', 'Author', 'AuthorURI', 'Description'
+ *
+ * @param string $file_contents Contents of the updatepulse.json file
+ * @return array Extracted package metadata
*/
public static function get_generic_headers( $file_contents ) {
$decoded_contents = json_decode( $file_contents, true );
@@ -430,16 +415,18 @@ class Parser {
}
/**
- * Parse the generic package's extra headers from updatepulse.json file.
- * Returns an array that may contain the following:
- * 'Icon1x'
- * 'Icon2x'
- * 'BannerHigh'
- * 'BannerLow'
- * 'RequireLicense'
- * 'LicensedWith'
- * @param string $file_contents Contents of the package file
- * @return array See above for description.
+ * Extracts additional metadata from a generic package's JSON file.
+ *
+ * Parses the updatepulse.json file to retrieve supplementary informationlike icons, banners, and licensing requirements for generic packages.
+ *
+ * The returned array may contain:
+ * 'icons' - Package icons in different resolutions
+ * 'banners' - Banner images in high/low resolutions
+ * 'require_license' - Whether the package requires license validation
+ * 'licensed_with' - Associated licensing system or provider
+ *
+ * @param string $file_contents Contents of the updatepulse.json file
+ * @return array Additional package metadata
*/
public static function get_generic_extra_headers( $file_contents ) {
$decoded_contents = json_decode( $file_contents, true );
@@ -489,23 +476,18 @@ class Parser {
}
/**
- * Parse the package contents to retrieve icons and banners information.
+ * Extracts visual assets and licensing information from package files.
*
- * Returns an array that may contain the following:
- * 'icons':
- * 'Icon1x'
- * 'Icon2x'
- * 'banners':
- * 'BannerHigh'
- * 'BannerLow'
- * 'Require License'
- * 'Licensed With'
+ * Searches plugin and theme files for special headers that define supplementary assets like icons and banners, as well as licensing requirements.
*
- * If the data is not found, the function
- * will return NULL.
+ * The returned array may include:
+ * 'icons' - Package icon URLs in different resolutions
+ * 'banners' - Banner image URLs in high/low resolutions
+ * 'require_license' - Whether the package requires license validation
+ * 'licensed_with' - Associated licensing system or provider
*
- * @param string $fileContents Contents of the package file
- * @return array|null See above for description.
+ * @param string $file_contents Contents of a plugin or theme file
+ * @return array|null Supplementary metadata or NULL if none found
*/
public static function get_extra_headers( $file_contents ) {
//[Internal name => Name used in the package file]
@@ -521,7 +503,13 @@ class Parser {
$extra_headers = array();
if ( ! empty( $headers['RequireLicense'] ) ) {
- $extra_headers['require_license'] = $headers['RequireLicense'];
+ $extra_headers['require_license'] = ! in_array(
+ $headers['RequireLicense'],
+ array( 'false', 'no', '0', 'off', 0 ),
+ true
+ );
+ } else {
+ $extra_headers['require_license'] = false;
}
if ( ! empty( $headers['LicensedWith'] ) ) {
@@ -560,19 +548,20 @@ class Parser {
}
/**
- * Parse the file contents to retrieve its metadata.
+ * Extracts metadata headers from file contents.
*
- * Searches for metadata for a file, such as a package. Each piece of
- * metadata must be on its own line. For a field spanning multiple lines, it
- * must not have any newlines or only parts of it will be displayed.
+ * A low-level utility function that searches for formatted header comments in file content. It supports the standard WordPress header format used for plugins, themes, and other metadata files.
*
- * @param string $file_contents File contents. Can be safely truncated to 8kiB as that's all WP itself scans.
- * @param array $header_map The list of headers to search for in the file.
- * @return array
+ * Each header must appear on its own line in the format:
+ * "Header Name: Header Value"
+ *
+ * @param string $file_contents File content to search for headers
+ * @param array $header_map Map of internal header names to their file representation
+ * @return array Extracted header values indexed by internal names
*/
public static function get_file_headers( $file_contents, $header_map ) {
$headers = array();
- //Support systems that use CR as a line ending.
+ // Support systems that use CR as a line ending.
$file_contents = str_replace( "\r", "\n", $file_contents );
foreach ( $header_map as $field => $pretty_name ) {
@@ -585,7 +574,7 @@ class Parser {
);
if ( ( $found > 0 ) && ! empty( $matches[1] ) ) {
- //Strip comment markers and closing PHP tags.
+ // Strip comment markers and closing PHP tags.
$value = trim( preg_replace( '/\s*(?:\*\/|\?>).*/', '', $matches[1] ) );
$headers[ $field ] = $value;
} else {
@@ -597,6 +586,12 @@ class Parser {
}
}
+/**
+ * Wrapper class for PHP's built-in ZipArchive.
+ *
+ * Provides a simplified interface for working with ZIP archives,
+ * specifically tailored for parsing WordPress package files.
+ */
class ZipArchive {
/**
* @var SystemZipArchive
@@ -608,10 +603,13 @@ class ZipArchive {
}
/**
- * Open a Zip archive.
+ * Opens a ZIP archive file for reading.
*
- * @param string $zip_file_name
- * @return bool|ZipArchive
+ * Creates and initializes a ZipArchive instance from a file path.
+ * The method handles the low-level details of opening the archive.
+ *
+ * @param string $zip_file_name Path to the ZIP archive file
+ * @return bool|ZipArchive ZipArchive instance or FALSE on failure
*/
public static function open( $zip_file_name ) {
$zip = new SystemZipArchive();
@@ -623,6 +621,13 @@ class ZipArchive {
return new self( $zip );
}
+ /**
+ * Lists all entries in the ZIP archive.
+ *
+ * Provides information about each file and folder in the archive, including name, size, and whether it's a folder.
+ *
+ * @return array List of entry information arrays
+ */
public function list_entries() {
$list = array();
$zip = $this->archive;
@@ -643,6 +648,14 @@ class ZipArchive {
return $list;
}
+ /**
+ * Retrieves the contents of a file within the ZIP archive.
+ *
+ * Extracts and returns the contents of a specific file identified by its information array (typically from list_entries).
+ *
+ * @param array $file_info File information containing 'index' key
+ * @return string File contents
+ */
public function get_file_contents( $file_info ) {
return $this->archive->getFromIndex( $file_info['index'] );
}
diff --git a/lib/package-update-checker/Autoloader.php b/lib/package-update-checker/Autoloader.php
index d69f31f..d707b44 100644
--- a/lib/package-update-checker/Autoloader.php
+++ b/lib/package-update-checker/Autoloader.php
@@ -4,13 +4,27 @@ namespace Anyape\PackageUpdateChecker;
if ( ! class_exists( Autoloader::class, false ) ) :
+ /**
+ * Handles class autoloading for the Package Update Checker library.
+ *
+ * Automatically loads class files based on PSR-4 naming conventions when they are referenced in the code.
+ */
class Autoloader {
+ /** @var string The default namespace prefix for the library */
const DEFAULT_NS_PREFIX = 'Anyape\\PackageUpdateChecker\\';
+ /** @var string The namespace prefix to handle */
private $prefix;
+
+ /** @var string The root directory containing class files */
private $root_dir;
+ /**
+ * Initializes the autoloader and registers it with SPL.
+ *
+ * Sets up the root directory and namespace prefix, then registers the autoload method with PHP's SPL autoloader.
+ */
public function __construct() {
$this->root_dir = __DIR__ . '/';
$namespace_with_slash = __NAMESPACE__ . '\\';
@@ -19,6 +33,14 @@ if ( ! class_exists( Autoloader::class, false ) ) :
spl_autoload_register( array( $this, 'autoload' ) );
}
+ /**
+ * Attempts to load a class file based on its fully qualified name.
+ *
+ * Converts the namespace and class name into a file path and includes the file if it exists.
+ *
+ * @param string $class_name The fully qualified class name to load
+ * @return void
+ */
public function autoload( $class_name ) {
if ( 0 === strpos( $class_name, $this->prefix ) ) {
diff --git a/lib/package-update-checker/GenericUpdateChecker.php b/lib/package-update-checker/GenericUpdateChecker.php
index 40be183..11d6444 100644
--- a/lib/package-update-checker/GenericUpdateChecker.php
+++ b/lib/package-update-checker/GenericUpdateChecker.php
@@ -4,8 +4,21 @@ namespace Anyape\PackageUpdateChecker;
if ( ! class_exists( GenericUpdateChecker::class, false ) ) :
+ /**
+ * Generic update checker for non-WordPress packages.
+ *
+ * This class extends the base UpdateChecker to provide update checking functionality for generic packages that use JSON files for metadata storage.
+ */
class GenericUpdateChecker extends UpdateChecker {
+ /**
+ * Initializes a new instance of the generic update checker.
+ *
+ * @param object $api The Version Control System API instance
+ * @param string $slug The package's slug/directory name
+ * @param string $container The parent directory containing the package
+ * @param string $file_name The main package file name without extension
+ */
public function __construct( $api, $slug, $container, $file_name ) {
$this->api = $api;
$this->package_absolute_path = trailingslashit( $container ) . $slug;
@@ -17,6 +30,15 @@ if ( ! class_exists( GenericUpdateChecker::class, false ) ) :
$this->api->set_slug( $this->slug );
}
+ /**
+ * Extracts version information from the package's JSON file.
+ *
+ * Parses the JSON file content and looks for version information in the
+ * packageData section of the JSON structure.
+ *
+ * @param string $file Content of the package's JSON file
+ * @return string The version number found in the file or from update source
+ */
protected function get_version_from_package_file( $file ) {
$file_contents = json_decode( $file, true );
@@ -31,6 +53,15 @@ if ( ! class_exists( GenericUpdateChecker::class, false ) ) :
return $this->update_source->version;
}
+ /**
+ * Returns an empty array since generic packages don't use header names.
+ *
+ * This method is implemented to satisfy the abstract parent class requirement
+ * but returns an empty array as generic packages use JSON format instead of
+ * header fields.
+ *
+ * @return array Empty array as generic packages don't use header fields
+ */
protected function get_header_names() {
return array();
}
diff --git a/lib/package-update-checker/PluginUpdateChecker.php b/lib/package-update-checker/PluginUpdateChecker.php
index ff561ce..41aca6b 100644
--- a/lib/package-update-checker/PluginUpdateChecker.php
+++ b/lib/package-update-checker/PluginUpdateChecker.php
@@ -4,10 +4,24 @@ namespace Anyape\PackageUpdateChecker;
if ( ! class_exists( PluginUpdateChecker::class, false ) ) :
+ /**
+ * Handles WordPress plugin update checking functionality.
+ *
+ * This class extends the base UpdateChecker to provide plugin-specific update checking, focusing on plugin header file parsing and metadata retrieval.
+ */
class PluginUpdateChecker extends UpdateChecker {
+ /** @var string Path to the main plugin file */
public $package_file = '';
+ /**
+ * Initializes a new instance of the plugin update checker.
+ *
+ * @param object $api The Version Control System API instance
+ * @param string $slug The plugin's slug/directory name
+ * @param string $container The parent directory containing the plugin
+ * @param string $file_name The main plugin file name without extension
+ */
public function __construct( $api, $slug, $container, $file_name ) {
$this->api = $api;
$this->package_absolute_path = trailingslashit( $container ) . $slug;
@@ -19,12 +33,24 @@ if ( ! class_exists( PluginUpdateChecker::class, false ) ) :
$this->api->set_slug( $this->slug );
}
+ /**
+ * Requests update information for the plugin.
+ *
+ * @param string $type The type of package (defaults to 'Plugin')
+ * @return array|WP_Error Update information array or WP_Error on failure
+ */
public function request_info( $type = 'Plugin' ) {
$info = parent::request_info( $type );
return $info;
}
+ /**
+ * Extracts version information from the plugin's main file.
+ *
+ * @param string $file Content of the plugin's main PHP file
+ * @return string The version number found in the file or from update source
+ */
protected function get_version_from_package_file( $file ) {
$remote_header = $this->get_file_header( $file );
@@ -33,6 +59,14 @@ if ( ! class_exists( PluginUpdateChecker::class, false ) ) :
$remote_header['Version'];
}
+ /**
+ * Defines the standard WordPress plugin header field names.
+ *
+ * These fields correspond to the metadata in the plugin's main PHP file
+ * that WordPress uses to identify and categorize plugins.
+ *
+ * @return array Associative array of plugin header fields and their corresponding names
+ */
protected function get_header_names() {
return array(
'Name' => 'Plugin Name',
diff --git a/lib/package-update-checker/ThemeUpdateChecker.php b/lib/package-update-checker/ThemeUpdateChecker.php
index 9232a5a..967921e 100644
--- a/lib/package-update-checker/ThemeUpdateChecker.php
+++ b/lib/package-update-checker/ThemeUpdateChecker.php
@@ -4,10 +4,23 @@ namespace Anyape\PackageUpdateChecker;
if ( ! class_exists( ThemeUpdateChecker::class, false ) ) :
+ /**
+ * Handles WordPress theme update checking functionality.
+ *
+ * This class extends the base UpdateChecker to provide theme-specific update checking, focusing on style.css parsing and theme header information retrieval.
+ */
class ThemeUpdateChecker extends UpdateChecker {
+ /** @var string The main theme file to check for updates */
public $package_file = 'style.css';
+ /**
+ * Initializes a new instance of the theme update checker.
+ *
+ * @param object $api The Version Control System API instance
+ * @param string $slug The theme's slug/directory name
+ * @param string $container The parent directory containing the theme
+ */
public function __construct( $api, $slug, $container ) {
$this->api = $api;
$this->package_absolute_path = trailingslashit( $container ) . $slug;
@@ -18,12 +31,24 @@ if ( ! class_exists( ThemeUpdateChecker::class, false ) ) :
$this->api->set_slug( $this->slug );
}
+ /**
+ * Requests update information for the theme.
+ *
+ * @param string $type The type of package (defaults to 'Theme')
+ * @return array|WP_Error Update information array or WP_Error on failure
+ */
public function request_info( $type = 'Theme' ) {
$info = parent::request_info( $type );
return $info;
}
+ /**
+ * Extracts version information from the theme's style.css file.
+ *
+ * @param string $file Content of the theme's style.css file
+ * @return string The version number found in the file or from update source
+ */
protected function get_version_from_package_file( $file ) {
$remote_header = $this->get_file_header( $file );
@@ -32,6 +57,11 @@ if ( ! class_exists( ThemeUpdateChecker::class, false ) ) :
$remote_header['Version'];
}
+ /**
+ * Defines the standard WordPress theme header field names.
+ *
+ * @return array Associative array of theme header fields and their corresponding names
+ */
protected function get_header_names() {
return array(
'Name' => 'Theme Name',
diff --git a/lib/package-update-checker/UpdateChecker.php b/lib/package-update-checker/UpdateChecker.php
index 2cb482e..1debd82 100644
--- a/lib/package-update-checker/UpdateChecker.php
+++ b/lib/package-update-checker/UpdateChecker.php
@@ -4,19 +4,47 @@ namespace Anyape\PackageUpdateChecker;
if ( ! class_exists( UpdateChecker::class, false ) ) :
+ /**
+ * Abstract base class for package update checking functionality.
+ *
+ * Provides core functionality for checking updates from various version control systems.
+ */
abstract class UpdateChecker {
+ /** @var bool|null Debug mode status */
public $debug_mode = null;
+
+ /** @var string Directory name of the package */
public $directory_name;
+
+ /** @var string Unique identifier for the package */
public $slug;
+
+ /** @var string Absolute path to the package directory */
public $package_absolute_path = '';
+ /** @var string Branch name to check for updates, defaults to 'main' */
protected $branch = 'main';
+
+ /** @var object Version Control System API instance */
protected $api;
+
+ /** @var string Current reference (tag/branch) being checked */
protected $ref;
+
+ /** @var object Source of the update */
protected $update_source;
+
+ /** @var string Path to the main package file */
protected $package_file;
+ /**
+ * Triggers an error message when in debug mode.
+ *
+ * @param string $message The error message to display
+ * @param int $error_type The type of error to trigger
+ * @return void
+ */
public function trigger_error( $message, $error_type ) {
if ( $this->is_debug_mode_enabled() ) {
@@ -25,14 +53,16 @@ if ( ! class_exists( UpdateChecker::class, false ) ) :
}
}
+ /**
+ * Extracts header information from a file's content.
+ *
+ * @param string $content The content to parse for headers
+ * @return array Associative array of header fields and their values
+ */
public function get_file_header( $content ) {
$content = (string) $content;
-
- //WordPress only looks at the first 8 KiB of the file, so we do the same.
- $content = substr( $content, 0, 8192 );
- //Normalize line endings.
- $content = str_replace( "\r", "\n", $content );
-
+ $content = substr( $content, 0, 8192 ); // Limit to 8KB
+ $content = str_replace( "\r", "\n", $content ); // Normalize line endings
$headers = $this->get_header_names();
$results = array();
@@ -55,22 +85,45 @@ if ( ! class_exists( UpdateChecker::class, false ) ) :
return $results;
}
+ /**
+ * Sets the branch to check for updates.
+ *
+ * @param string $branch The branch name
+ * @return self
+ */
public function set_branch( $branch ) {
$this->branch = $branch;
return $this;
}
+ /**
+ * Sets authentication credentials for the API.
+ *
+ * @param mixed $credentials The authentication credentials
+ * @return self
+ */
public function set_authentication( $credentials ) {
$this->api->set_authentication( $credentials );
return $this;
}
+ /**
+ * Gets the VCS API instance.
+ *
+ * @return object The VCS API instance
+ */
public function get_vcs_api() {
return $this->api;
}
+ /**
+ * Requests update information from the repository.
+ *
+ * @param string $type The type of package (default: 'Generic')
+ * @return array|WP_Error Update information or error on failure
+ */
public function request_info( $type = 'Generic' ) {
if ( function_exists( 'set_time_limit' ) ) {
@@ -84,7 +137,6 @@ if ( ! class_exists( UpdateChecker::class, false ) ) :
if ( $update_source ) {
$ref = $update_source->name;
} else {
- //There's probably a network problem or an authentication error.
return new \WP_Error(
'puc-no-update-source',
'Could not retrieve version information from the repository for '
@@ -141,15 +193,25 @@ if ( ! class_exists( UpdateChecker::class, false ) ) :
return $info;
}
+ /**
+ * Extracts version information from the package file.
+ *
+ * @param string $file Content of the package file
+ * @return string Version number
+ */
abstract protected function get_version_from_package_file( $file );
/**
- * @return array Format: ['HeaderKey' => 'Header Name']
+ * Gets the header field names to look for in the package file.
+ *
+ * @return array Associative array of header keys and their names
*/
abstract protected function get_header_names();
/**
- * @return bool
+ * Checks if debug mode is enabled.
+ *
+ * @return bool True if debug mode is enabled, false otherwise
*/
protected function is_debug_mode_enabled() {
diff --git a/lib/package-update-checker/Vcs/Api.php b/lib/package-update-checker/Vcs/Api.php
index 80b61e4..ccb1e3d 100644
--- a/lib/package-update-checker/Vcs/Api.php
+++ b/lib/package-update-checker/Vcs/Api.php
@@ -4,6 +4,9 @@ namespace Anyape\PackageUpdateChecker\Vcs;
if ( ! class_exists( Api::class, false ) ) :
+ /**
+ * Abstract class representing a Version Control System (VCS) API.
+ */
abstract class Api {
const STRATEGY_LATEST_RELEASE = 'latest_release';
@@ -82,7 +85,7 @@ if ( ! class_exists( Api::class, false ) ) :
}
/**
- * Figure out which reference ( i.e. tag or branch ) contains the latest version.
+ * Determine which reference (i.e., tag or branch) contains the latest version.
*
* @param string $config_branch Start looking in this branch.
* @return null|Reference
@@ -119,7 +122,7 @@ if ( ! class_exists( Api::class, false ) ) :
* "README.TXT", or even "Readme.txt". Most VCS are case-sensitive so we need to know the correct
* capitalization.
*
- * Defaults to "readme.txt" ( all lowercase ).
+ * Defaults to "readme.txt" (all lowercase).
*
* @return string
*/
@@ -169,7 +172,7 @@ if ( ! class_exists( Api::class, false ) ) :
/**
* Get the tag that looks like the highest version number.
- * ( Implementations should skip pre-release versions if possible. )
+ * (Implementations should skip pre-release versions if possible.)
*
* @return Reference|null
*/
@@ -182,15 +185,15 @@ if ( ! class_exists( Api::class, false ) ) :
* @return bool
*/
protected function looks_like_version( $name ) {
- //Tag names may be prefixed with "v", e.g. "v1.2.3".
+ // Tag names may be prefixed with "v", e.g., "v1.2.3".
$name = ltrim( $name, 'v' );
- //The version string must start with a number.
+ // The version string must start with a number.
if ( ! is_numeric( substr( $name, 0, 1 ) ) ) {
return false;
}
- //The goal is to accept any SemVer-compatible or "PHP-standardized" version number.
+ // The goal is to accept any SemVer-compatible or "PHP-standardized" version number.
return ( preg_match( '@^(\d{1,5}?)(\.\d{1,10}?){0,4}?($|[abrdp+_\-]|\s)@i', $name ) === 1 );
}
@@ -208,23 +211,23 @@ if ( ! class_exists( Api::class, false ) ) :
/**
* Sort a list of tags as if they were version numbers.
- * Tags that don't look like version number will be removed.
+ * Tags that don't look like version numbers will be removed.
*
* @param \stdClass[] $tags Array of tag objects.
* @return \stdClass[] Filtered array of tags sorted in descending order.
*/
protected function sort_tags_by_version( $tags ) {
- //Keep only those tags that look like version numbers.
+ // Keep only those tags that look like version numbers.
$version_tags = array_filter( $tags, array( $this, 'is_version_tag' ) );
- //Sort them in descending order.
+ // Sort them in descending order.
usort( $version_tags, array( $this, 'compare_tag_names' ) );
return $version_tags;
}
/**
- * Compare two tags as if they were version number.
+ * Compare two tags as if they were version numbers.
*
* @param \stdClass $tag1 Tag object.
* @param \stdClass $tag2 Another tag object.
@@ -256,7 +259,7 @@ if ( ! class_exists( Api::class, false ) ) :
/**
* Get the timestamp of the latest commit that changed the specified branch or tag.
*
- * @param string $ref Reference name ( e.g. branch or tag ).
+ * @param string $ref Reference name (e.g., branch or tag).
* @return string|null
*/
abstract public function get_latest_commit_time( $ref );
diff --git a/lib/package-update-checker/Vcs/BitbucketApi.php b/lib/package-update-checker/Vcs/BitbucketApi.php
index b19946e..754b2d3 100644
--- a/lib/package-update-checker/Vcs/BitbucketApi.php
+++ b/lib/package-update-checker/Vcs/BitbucketApi.php
@@ -6,6 +6,10 @@ use WP_Error;
if ( ! class_exists( BitbucketApi::class, false ) ) :
+ /**
+ * Class BitbucketApi
+ * Handles interactions with the Bitbucket API.
+ */
class BitbucketApi extends Api {
/**
@@ -13,6 +17,13 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
*/
protected $app_password;
+ /**
+ * BitbucketApi constructor.
+ *
+ * @param string $repository_url The URL of the Bitbucket repository.
+ * @param string|null $app_password Optional. The Bitbucket app password.
+ * @throws \InvalidArgumentException If the repository URL is invalid.
+ */
public function __construct( $repository_url, $app_password = null ) {
$path = wp_parse_url( $repository_url, PHP_URL_PATH );
@@ -31,7 +42,9 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
/**
* Check if the VCS is accessible.
*
- * @return bool|WP_Error
+ * @param string $url The URL to test.
+ * @param string|null $app_password Optional. The Bitbucket app password.
+ * @return bool|WP_Error True if accessible, WP_Error otherwise.
*/
public static function test( $url, $app_password = null ) {
$instance = new self( $url . 'bogus/', $app_password );
@@ -42,7 +55,11 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
return $response;
}
- if ( $response && isset( $response->username ) && $instance->user_name === $response->username ) {
+ if (
+ $response &&
+ isset( $response->username ) &&
+ trailingslashit( $instance->user_name ) === trailingslashit( $response->username )
+ ) {
return true;
}
@@ -56,18 +73,33 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
);
}
+ /**
+ * Get update detection strategies.
+ *
+ * @param string $config_branch The branch to check for updates.
+ * @return array The update detection strategies.
+ */
protected function get_update_detection_strategies( $config_branch ) {
$strategies[ self::STRATEGY_BRANCH ] = function () use ( $config_branch ) {
return $this->get_branch( $config_branch );
};
- if ( ( 'main' === $config_branch || 'master' === $config_branch ) ) {
+ if (
+ ( 'main' === $config_branch || 'master' === $config_branch ) &&
+ ( ! defined( 'PUC_FORCE_BRANCH' ) || ! (bool) ( constant( 'PUC_FORCE_BRANCH' ) ) )
+ ) {
$strategies[ self::STRATEGY_LATEST_TAG ] = array( $this, 'get_latest_tag' );
}
return $strategies;
}
+ /**
+ * Get a specific branch.
+ *
+ * @param string $branch_name The name of the branch.
+ * @return Reference|null The branch reference or null if not found.
+ */
public function get_branch( $branch_name ) {
$branch = $this->api( '/refs/branches/' . $branch_name );
@@ -75,9 +107,8 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
return null;
}
- //The "/src/{stuff}/{path}" endpoint doesn't seem to handle branch names that contain slashes.
- //If we don't encode the slash, we get a 404. If we encode it as "%2F", we get a 401.
- //To avoid issues, if the branch name is not URL-safe, let's use the commit hash instead.
+ // The "/src/{something}/{path}" endpoint doesn't handle branch names with slashes.
+ // If the branch name is not URL-safe, use the commit hash instead.
$ref = $branch->name;
if ( ( rawurlencode( $ref ) !== $ref ) && isset( $branch->target->hash ) ) {
@@ -96,8 +127,8 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
/**
* Get a specific tag.
*
- * @param string $tag_name
- * @return Reference|null
+ * @param string $tag_name The name of the tag.
+ * @return Reference|null The tag reference or null if not found.
*/
public function get_tag( $tag_name ) {
$tag = $this->api( '/refs/tags/' . $tag_name );
@@ -117,9 +148,9 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
}
/**
- * Get the tag that looks like the highest version number.
+ * Get the latest tag that looks like the highest version number.
*
- * @return Reference|null
+ * @return Reference|null The latest tag reference or null if not found.
*/
public function get_latest_tag() {
$tags = $this->api( '/refs/tags?sort=-target.date' );
@@ -128,10 +159,10 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
return null;
}
- //Filter and sort the list of tags.
+ // Filter and sort the list of tags.
$version_tags = $this->sort_tags_by_version( $tags->values );
- //Return the first result.
+ // Return the first result.
if ( ! empty( $version_tags ) ) {
$tag = $version_tags[0];
@@ -149,8 +180,10 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
}
/**
- * @param string $ref
- * @return string
+ * Get the download URL for a specific reference.
+ *
+ * @param string $ref The reference name (e.g., branch or tag).
+ * @return string The download URL.
*/
protected function get_download_url( $ref ) {
return sprintf(
@@ -164,9 +197,9 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
/**
* Get the contents of a file from a specific branch or tag.
*
- * @param string $path File name.
- * @param string $ref
- * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
+ * @param string $path The file path.
+ * @param string $ref The reference name (e.g., branch or tag).
+ * @return null|string The file contents or null if not found.
*/
public function get_remote_file( $path, $ref = 'main' ) {
$response = $this->api( 'src/' . $ref . '/' . ltrim( $path ) );
@@ -181,8 +214,8 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
/**
* Get the timestamp of the latest commit that changed the specified branch or tag.
*
- * @param string $ref Reference name ( e.g. branch or tag ).
- * @return string|null
+ * @param string $ref The reference name (e.g., branch or tag).
+ * @return string|null The timestamp of the latest commit or null if not found.
*/
public function get_latest_commit_time( $ref ) {
$response = $this->api( 'commits/' . $ref );
@@ -197,9 +230,10 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
/**
* Perform a Bitbucket API 2.0 request.
*
- * @param string $url
- * @param string $version
- * @return mixed|WP_Error
+ * @param string $url The API endpoint URL.
+ * @param string $version The API version.
+ * @param bool $override_url Whether to override the base URL.
+ * @return mixed|WP_Error The API response or WP_Error on failure.
*/
public function api( $url, $version = '2.0', $override_url = false ) {
$url = ltrim( $url, '/' );
@@ -238,8 +272,7 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
if ( 200 === $code ) {
if ( $is_src_resource ) {
- //Most responses are JSON-encoded, but src resources just
- //return raw file contents.
+ // Most responses are JSON-encoded, but src resources return raw file contents.
$document = $body;
} else {
$document = json_decode( $body );
@@ -266,7 +299,9 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
}
/**
- * @param array $credentials
+ * Set authentication credentials.
+ *
+ * @param array|string $credentials The authentication credentials.
*/
public function set_authentication( $credentials ) {
parent::set_authentication( $credentials );
@@ -277,7 +312,7 @@ if ( ! class_exists( BitbucketApi::class, false ) ) :
/**
* Generate the value of the "Authorization" header.
*
- * @return string
+ * @return array The authorization headers.
*/
public function get_authorization_headers() {
return array(
diff --git a/lib/package-update-checker/Vcs/GitHubApi.php b/lib/package-update-checker/Vcs/GitHubApi.php
index 312fead..2bcddb4 100644
--- a/lib/package-update-checker/Vcs/GitHubApi.php
+++ b/lib/package-update-checker/Vcs/GitHubApi.php
@@ -8,6 +8,13 @@ use LogicException;
if ( ! class_exists( GitHubApi::class, false ) ) :
+ /**
+ * Class GitHubApi
+ *
+ * This class provides methods to interact with the GitHub API for various operations
+ * such as fetching releases, tags, branches, and commits. It also handles authentication
+ * and API request construction.
+ */
class GitHubApi extends Api {
use ReleaseAssetSupport;
use ReleaseFilteringFeature;
@@ -18,10 +25,17 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
protected $access_token;
/**
- * @var bool
+ * @var bool Indicates if the download filter has been added.
*/
private $download_filter_added = false;
+ /**
+ * GitHubApi constructor.
+ *
+ * @param string $repository_url The URL of the GitHub repository.
+ * @param string|null $access_token Optional GitHub access token.
+ * @throws InvalidArgumentException If the repository URL is invalid.
+ */
public function __construct( $repository_url, $access_token = null ) {
$path = wp_parse_url( $repository_url, PHP_URL_PATH );
@@ -40,7 +54,9 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
/**
* Check if the VCS is accessible.
*
- * @return bool|WP_Error
+ * @param string $url The URL to check.
+ * @param string|null $access_token Optional GitHub access token.
+ * @return bool|WP_Error True if accessible, false or WP_Error otherwise.
*/
public static function test( $url, $access_token = null ) {
$instance = new self( $url . 'bogus/', $access_token );
@@ -51,7 +67,10 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
return false;
}
- if ( isset( $response->html_url ) && $response->html_url === $url ) {
+ if (
+ isset( $response->html_url ) &&
+ trailingslashit( $url ) === trailingslashit( $response->html_url )
+ ) {
return true;
}
@@ -84,20 +103,20 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get the latest release from GitHub.
+ * Retrieve the latest release from GitHub.
*
- * @return Reference|null
+ * @return Reference|null The latest release or null if not found.
*/
public function get_latest_release() {
- //The "latest release" endpoint returns one release and always skips pre-releases, so we can only use it if that's compatible with the current filter settings.
+ // The "latest release" endpoint returns one release and always skips pre-releases, so we can only use it if that's compatible with the current filter settings.
if (
$this->should_skip_pre_releases()
&& (
( 1 === $this->release_filter_max_releases ) || ! $this->has_custom_release_filter()
)
) {
- //Just get the latest release.
+ // Fetch the latest release.
$release = $this->api( '/repos/:user/:repo/releases/latest' );
if ( is_wp_error( $release ) || ! is_object( $release ) || ! isset( $release->tag_name ) ) {
@@ -106,7 +125,7 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
$found_releases = array( $release );
} else {
- //Get a list of the most recent releases.
+ // Retrieve a list of the most recent releases.
$found_releases = $this->api(
'/repos/:user/:repo/releases',
array( 'per_page' => $this->release_filter_max_releases )
@@ -119,12 +138,12 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
foreach ( $found_releases as $release ) {
- //Always skip drafts.
+ // Always skip drafts.
if ( isset( $release->draft ) && ! empty( $release->draft ) ) {
continue;
}
- //Skip pre-releases unless specifically included.
+ // Skip pre-releases unless specifically included.
if (
$this->should_skip_pre_releases()
&& isset( $release->prerelease )
@@ -133,9 +152,9 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
continue;
}
- $version_number = ltrim( $release->tag_name, 'v' ); //Remove the "v" prefix from "v1.2.3".
+ $version_number = ltrim( $release->tag_name, 'v' ); // Remove the "v" prefix from "v1.2.3".
- //Custom release filtering.
+ // Custom release filtering.
if ( ! $this->matches_custom_release_filter( $version_number, $release ) ) {
continue;
}
@@ -156,7 +175,7 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
if ( $this->release_assets_enabled ) {
- //Use the first release asset that matches the specified regular expression.
+ // Use the first release asset that matches the specified regular expression.
if ( isset( $release->assets, $release->assets[0] ) ) {
$matching_assets = array_values( array_filter( $release->assets, array( $this, 'matchesAssetFilter' ) ) );
} else {
@@ -173,14 +192,14 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
*/
$reference->download_url = $matching_assets[0]->url;
} else {
- //It seems that browser_download_url only works for public repositories.
- //Using an access_token doesn't help. Maybe OAuth would work?
+ // It seems that browser_download_url only works for public repositories.
+ // Using an access_token doesn't help. Maybe OAuth would work?
$reference->download_url = $matching_assets[0]->browser_download_url;
}
$reference->download_count = $matching_assets[0]->download_count;
} elseif ( Api::REQUIRE_RELEASE_ASSETS === $this->release_asset_preference ) {
- //None of the assets match the filter, and we're not allowed to fall back to the auto-generated source ZIP.
+ // None of the assets match the filter, and we're not allowed to fall back to the auto-generated source ZIP.
return null;
}
}
@@ -192,9 +211,9 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get the tag that looks like the highest version number.
+ * Retrieve the tag that appears to be the highest version number.
*
- * @return Reference|null
+ * @return Reference|null The highest version tag or null if not found.
*/
public function get_latest_tag() {
$tags = $this->api( '/repos/:user/:repo/tags' );
@@ -222,10 +241,10 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get a branch by name.
+ * Retrieve a branch by its name.
*
- * @param string $branch_name
- * @return null|Reference
+ * @param string $branch_name The name of the branch.
+ * @return null|Reference The branch reference or null if not found.
*/
public function get_branch( $branch_name ) {
$branch = $this->api( '/repos/:user/:repo/branches/' . $branch_name );
@@ -250,11 +269,11 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get the latest commit that changed the specified file.
+ * Retrieve the latest commit that modified the specified file.
*
- * @param string $filename
- * @param string $ref Reference name ( e.g. branch or tag ).
- * @return \StdClass|null
+ * @param string $filename The name of the file.
+ * @param string $ref Reference name (e.g., branch or tag).
+ * @return \StdClass|null The latest commit or null if not found.
*/
public function get_latest_commit( $filename, $ref = 'main' ) {
$commits = $this->api(
@@ -273,10 +292,10 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get the timestamp of the latest commit that changed the specified branch or tag.
+ * Retrieve the timestamp of the latest commit that modified the specified branch or tag.
*
- * @param string $ref Reference name ( e.g. branch or tag ).
- * @return string|null
+ * @param string $ref Reference name (e.g., branch or tag).
+ * @return string|null The timestamp of the latest commit or null if not found.
*/
public function get_latest_commit_time( $ref ) {
$commits = $this->api( '/repos/:user/:repo/commits', array( 'sha' => $ref ) );
@@ -291,9 +310,10 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
/**
* Perform a GitHub API request.
*
- * @param string $url
- * @param array $query_params
- * @return mixed|WP_Error
+ * @param string $url The API endpoint URL.
+ * @param array $query_params Optional query parameters.
+ * @param bool $override_url Whether to override the base URL.
+ * @return mixed|WP_Error The API response or WP_Error on failure.
*/
protected function api( $url, $query_params = array(), $override_url = false ) {
$base_url = $url;
@@ -346,11 +366,11 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Build a fully qualified URL for an API request.
+ * Construct a fully qualified URL for an API request.
*
- * @param string $url
- * @param array $query_params
- * @return string
+ * @param string $url The API endpoint URL.
+ * @param array $query_params Optional query parameters.
+ * @return string The fully qualified URL.
*/
protected function build_api_url( $url, $query_params ) {
$variables = array(
@@ -372,11 +392,11 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get the contents of a file from a specific branch or tag.
+ * Retrieve the contents of a file from a specific branch or tag.
*
- * @param string $path File name.
- * @param string $ref
- * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
+ * @param string $path The file path.
+ * @param string $ref The reference name (e.g., branch or tag).
+ * @return null|string The file contents or null if not found.
*/
public function get_remote_file( $path, $ref = 'main' ) {
$api_url = '/repos/:user/:repo/contents/' . $path;
@@ -392,8 +412,8 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
/**
* Generate a URL to download a ZIP archive of the specified branch/tag/etc.
*
- * @param string $ref
- * @return string
+ * @param string $ref The reference name (e.g., branch or tag).
+ * @return string The download URL.
*/
public function build_archive_download_url( $ref = 'main' ) {
$url = sprintf(
@@ -407,33 +427,48 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get a specific tag.
+ * Retrieve a specific tag.
*
- * @param string $tag_name
+ * @param string $tag_name The name of the tag.
* @return void
+ * @throws LogicException If the method is not implemented.
*/
public function get_tag( $tag_name ) {
- //The current GitHub update checker doesn't use get_tag, so I didn't bother to implement it.
+ // The current GitHub update checker doesn't use get_tag, so I didn't bother to implement it.
throw new LogicException( 'The ' . __METHOD__ . ' method is not implemented and should not be used.' );
}
+ /**
+ * Set the authentication credentials.
+ *
+ * @param string|array $credentials The authentication credentials.
+ */
public function set_authentication( $credentials ) {
parent::set_authentication( $credentials );
$this->access_token = is_string( $credentials ) ? $credentials : null;
}
+ /**
+ * Retrieve the update detection strategies based on the configuration branch.
+ *
+ * @param string $config_branch The configuration branch.
+ * @return array The update detection strategies.
+ */
protected function get_update_detection_strategies( $config_branch ) {
$strategies = array();
- if ( 'main' === $config_branch || 'master' === $config_branch ) {
- //Use the latest release.
+ if (
+ ( 'main' === $config_branch || 'master' === $config_branch ) &&
+ ( ! defined( 'PUC_FORCE_BRANCH' ) || ! (bool) ( constant( '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.
+ // Failing that, use the tag with the highest version number.
$strategies[ self::STRATEGY_LATEST_TAG ] = array( $this, 'get_latest_tag' );
}
- //Alternatively, just use the branch itself.
+ // Alternatively, just use the branch itself.
$strategies[ self::STRATEGY_BRANCH ] = function () use ( $config_branch ) {
return $this->get_branch( $config_branch );
};
@@ -442,9 +477,9 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Get the unchanging part of a release asset URL. Used to identify download attempts.
+ * Retrieve the unchanging part of a release asset URL. Used to identify download attempts.
*
- * @return string
+ * @return string The base URL for release assets.
*/
protected function get_asset_api_base_url() {
return sprintf(
@@ -454,6 +489,12 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
);
}
+ /**
+ * Retrieve the filterable name of a release asset.
+ *
+ * @param object $release_asset The release asset object.
+ * @return string|null The name of the release asset or null if not found.
+ */
protected function get_filterable_asset_name( $release_asset ) {
if ( isset( $release_asset->name ) ) {
@@ -464,8 +505,10 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * @param bool $result
- * @return bool
+ * Add an HTTP request filter.
+ *
+ * @param bool $result The result of the filter.
+ * @return bool The result of the filter.
* @internal
*/
public function add_http_request_filter( $result ) {
@@ -482,9 +525,9 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
}
/**
- * Set the HTTP headers that are necessary to download updates from private repositories.
+ * Set the HTTP headers required to download updates from private repositories.
*
- * See GitHub docs:
+ * Refer to GitHub documentation:
*
* @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
* @link https://developer.github.com/v3/auth/#basic-authentication
@@ -496,12 +539,12 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
*/
public function set_update_download_headers( $request_args, $url = '' ) {
- //Is WordPress trying to download one of our release assets?
+ // Check if WordPress is attempting to download one of our release assets.
if ( $this->release_assets_enabled && ( strpos( $url, $this->get_asset_api_base_url() ) !== false ) ) {
$request_args['headers']['Accept'] = 'application/octet-stream';
}
- //Use Basic authentication, but only if the download is from our repository.
+ // Use Basic authentication only if the download is from our repository.
$repo_api_base_url = $this->build_api_url( '/repos/:user/:repo/', array() );
if ( $this->is_authentication_enabled() && ( strpos( $url, $repo_api_base_url ) ) === 0 ) {
@@ -513,8 +556,8 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
/**
* When following a redirect, the Requests library will automatically forward
- * the authorization header to other hosts. We don't want that because it breaks
- * AWS downloads and can leak authorization information.
+ * the authorization header to other hosts. This can cause issues with AWS downloads
+ * and may expose authorization information.
*
* @param string $location
* @param array $headers
@@ -524,17 +567,17 @@ if ( ! class_exists( GitHubApi::class, false ) ) :
$repo_api_base_url = $this->build_api_url( '/repos/:user/:repo/', array() );
if ( strpos( $location, $repo_api_base_url ) === 0 ) {
- return; //This request is going to GitHub, so it's fine.
+ return; // This request is going to GitHub, so it's acceptable.
}
- //Remove the header.
+ // Remove the authorization header.
if ( isset( $headers['Authorization'] ) ) {
unset( $headers['Authorization'] );
}
}
/**
- * Generate the value of the "Authorization" header.
+ * Create the value for the "Authorization" header.
*
* @return string
*/
diff --git a/lib/package-update-checker/Vcs/GitLabApi.php b/lib/package-update-checker/Vcs/GitLabApi.php
index ac2c35d..886d06c 100644
--- a/lib/package-update-checker/Vcs/GitLabApi.php
+++ b/lib/package-update-checker/Vcs/GitLabApi.php
@@ -8,25 +8,39 @@ use LogicException;
if ( ! class_exists( GitLabApi::class, false ) ) :
+ /**
+ * Class GitLabApi
+ *
+ * This class interacts with the GitLab API to fetch repository information,
+ * releases, tags, branches, and other relevant data.
+ */
class GitLabApi extends Api {
use ReleaseAssetSupport;
use ReleaseFilteringFeature;
/**
- * @var string GitLab server host.
+ * @var string The host of the GitLab server.
*/
protected $repository_host;
/**
- * @var string Protocol used by this GitLab server: "http" or "https".
+ * @var string The protocol used by the GitLab server, either "http" or "https".
*/
protected $repository_protocol = 'https';
/**
- * @var string GitLab authentication token. Optional.
+ * @var string The GitLab authentication token, which is optional.
*/
protected $access_token;
+ /**
+ * Constructor.
+ *
+ * @param string $repository_url The URL of the GitLab repository.
+ * @param string $access_token The authentication token for GitLab.
+ * @param string $sub_group The sub-group within the GitLab repository.
+ * @throws InvalidArgumentException If the repository URL is invalid.
+ */
public function __construct( $repository_url, $access_token = null, $sub_group = null ) {
- //Parse the repository host to support custom hosts.
+ // Extract the port from the repository URL to support custom hosts.
$port = wp_parse_url( $repository_url, PHP_URL_PORT );
if ( ! empty( $port ) ) {
@@ -36,17 +50,18 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
$this->repository_host = wp_parse_url( $repository_url, PHP_URL_HOST ) . $port;
if ( 'gitlab.com' !== $this->repository_host ) {
+ // Identify the protocol used by the GitLab server.
$this->repository_protocol = wp_parse_url( $repository_url, PHP_URL_SCHEME );
}
- //Find the repository information
+ // Extract repository information from the URL.
$path = wp_parse_url( $repository_url, PHP_URL_PATH );
if ( preg_match( '@^/?(?P' . $title . '
The update failed: function %1$s not found.
Please restore the previously used version of the %2$s, or delete the %2$s and its files in the %2$s directory if any and install the latest version.', 'wp-update-migrate' ),
$function_name,
$this->package_type,
@@ -295,7 +396,7 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
$result = $this->update_package_version( $version );
if ( true === $result ) {
- // translators: %1$s is the version we just updated to
+ // Translators: %1$s is the version we just updated to
$this->handle_success( sprintf( __( 'Updates for version %1$s applied.', 'wp-update-migrate' ), $version ) );
return true;
@@ -305,6 +406,11 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
}
}
+ /**
+ * Initialize the package type
+ *
+ * @param string $package_prefix The prefix of the package.
+ */
protected function init_package_type( $package_prefix ) {
$hook = current_filter();
@@ -317,14 +423,32 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
}
}
+ /**
+ * Check if update file exists for a specific version
+ *
+ * @param string $version The version to check.
+ * @return bool True if the update file exists, false otherwise.
+ */
protected function update_file_exists_for_version( $version ) {
return file_exists( $this->package_dir . 'updates' . DIRECTORY_SEPARATOR . $version . '.php' );
}
+ /**
+ * Get the update file path for a specific version
+ *
+ * @param string $version The version to get the file path for.
+ * @return string The update file path.
+ */
protected function get_update_file_path_for_version( $version ) {
return $this->package_dir . 'updates' . DIRECTORY_SEPARATOR . $version . '.php';
}
+ /**
+ * Update the package version
+ *
+ * @param string $version The version to update to.
+ * @return mixed The result of the update.
+ */
protected function update_package_version( $version ) {
if ( ! version_compare( $this->from_version, $version, '=' ) ) {
@@ -336,7 +460,7 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
if ( ! $result ) {
$result = new WP_Error(
__METHOD__,
- // translators: %1$s is the package prefix, %2$s is the package type, %3$s is the version number we're trying to update to
+ // Translators: %1$s is the package prefix, %2$s is the package type, %3$s is the version number we're trying to update to
sprintf( __( 'Failed to update the %1$s_%2$s_version to %3$s in the options table.', 'wp-update-migrate' ), $this->package_prefix, $this->package_type, $version )
);
}
@@ -344,16 +468,22 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
return $result;
}
+ /**
+ * Handle error
+ *
+ * @param WP_Error|null $error The error object.
+ * @return bool False indicating an error occurred.
+ */
protected function handle_error( $error = null ) {
$error_title = $this->package_name
. ' - '
. sprintf(
- // translators: %1$s is the package version to update to
+ // Translators: %1$s is the package version to update to
__( 'Error updating to version %1$s', 'wp-update-migrate' ),
$this->to_version
);
$error_message = sprintf(
- // translators: %1$s is the path to WP_CONTENT_DIR, %2$s is the package type
+ // Translators: %1$s is the path to WP_CONTENT_DIR, %2$s is the package type
__( 'An unexpected error has occured during the update.
Please restore the previously used version of the %2$s, or delete the %2$s and its files in the %1$s directory if any and install the latest version.', 'wp-update-migrate' ),
self::get_content_dir(),
$this->package_type
@@ -368,6 +498,11 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
return false;
}
+ /**
+ * Handle success
+ *
+ * @param string|null $message The success message.
+ */
protected function handle_success( $message = null ) {
$this->success_update_info .= $message . '
';
}
diff --git a/optimisation/upserv-default-optimizer.php b/optimisation/upserv-default-optimizer.php
index f0ba859..84984ad 100644
--- a/optimisation/upserv-default-optimizer.php
+++ b/optimisation/upserv-default-optimizer.php
@@ -30,27 +30,44 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
+/**
+ * Main optimization function for UpdatePulse Server API requests.
+ *
+ * Determines if the current request is an UpdatePulse API call and applies
+ * performance optimizations by removing unnecessary WordPress hooks.
+ *
+ * @since 2.0
+ *
+ * @return void
+ */
function upserv_muplugins_loaded() {
+ // Get host information from server variables
$host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : false;
$url = '';
+ // Fallback to SERVER_NAME if HTTP_HOST is not available
if ( ! $host && isset( $_SERVER['SERVER_NAME'] ) ) {
$host = sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) );
}
+ // Construct the full URL if host and request URI are available
if ( $host && isset( $_SERVER['REQUEST_URI'] ) ) {
$request_uri = sanitize_url( wp_unslash( $_SERVER['REQUEST_URI'] ) );
$url = sanitize_url( 'https://' . $host . $request_uri );
}
+ // Parse URL to determine if this is an API request
$path = str_replace( trailingslashit( home_url() ), '', $url );
$frags = explode( '/', $path );
$doing_api = preg_match( '/^updatepulse-server-((.*?)-api|nonce|token)$/', $frags[0] );
$hooks = array();
+ // Apply optimizations if this is an API request
if ( apply_filters( 'upserv_mu_optimizer_doing_api_request', $doing_api ) ) {
+ // Notify before applying optimizations
do_action( 'upserv_mu_optimizer_default_pre_apply' );
+ // Define hooks to be removed
$hooks = apply_filters(
'upserv_mu_optimizer_remove_all_hooks',
array(
@@ -86,18 +103,24 @@ function upserv_muplugins_loaded() {
)
);
+ // Remove all filters from specified hooks
foreach ( $hooks as $hook ) {
remove_all_filters( $hook );
}
+ // Override theme directories to prevent theme loading
add_filter( 'template_directory', fn() => __DIR__, PHP_INT_MAX - 100, 0 );
add_filter( 'stylesheet_directory', fn() => __DIR__, PHP_INT_MAX - 100, 0 );
+ // Disable advanced cache
add_filter( 'enable_loading_advanced_cache_dropin', fn() => false, PHP_INT_MAX - 100, 0 );
+ // Notify after applying optimizations
do_action( 'upserv_mu_optimizer_default_applied' );
}
+ // Set up caching for UpdatePulse Server
wp_cache_add_non_persistent_groups( 'updatepulse-server' );
wp_cache_set( 'upserv_mu_doing_api', $doing_api, 'updatepulse-server' );
+ // Signal that the optimizer is ready
do_action(
'upserv_mu_optimizer_ready',
$doing_api,
@@ -107,6 +130,10 @@ function upserv_muplugins_loaded() {
);
}
+/**
+ * Hook the optimization function to run on muplugins_loaded,
+ * except when running in WP-CLI environment.
+ */
if ( ! defined( 'WP_CLI' ) ) {
add_action( 'muplugins_loaded', 'upserv_muplugins_loaded', 0 );
}
diff --git a/readme.txt b/readme.txt
index d053feb..fc0fc1d 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,9 +1,10 @@
=== 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
-Stable tag: 1.0
+Stable tag: 1.0.10
Requires PHP: 8.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -13,7 +14,9 @@ Run your own update server for plugins, themes or any other software: manage pac
== Description ==
UpdatePulse Server allows developers to provide updates for software packages, including WordPress plugins and themes.
+
Some example use cases:
+
* provide updates for premium plugins or themes, with a license key
* provide custom theme or plugin updates to clients of a webdesign agency and not intended for the general public
* provide updates for a desktop software that integrates with UpdatePulse Server's update and license API
@@ -35,15 +38,87 @@ Make sure to read the full documentation and the content of the "Help" tab under
This plugin adds the following major features to WordPress:
-* **Package management:** to manage update packages, showing a listing with Package Name, Version, Type, File Name, Size, Last Modified and License Status; includes bulk operations to delete and download, and the ability to delete all the packages.
+* **Package management:** to manage update packages, showing a listing with Package Name, Version, Type, File Name, Size, File Modified and License Status; includes bulk operations to delete and download, and the ability to delete all the packages.
* **Add Packages:** Upload update packages from a local machine to the server, or download them to the server from a Version Control System.
* **Version Control Systems:** Instead of manually uploading packages, use Version Control Systems to host packages, and download them to UpdatePulse Server automatically. Supports Bitbucket, Github and Gitlab, as well as self-hosted installations of Gitlab.
* **Cloud Storage**: Instead of storing packages on the file system where UpdatePulse Server is installed, they can be stored on a cloud storage service, as long as it is compatible with Amazon S3's API. Examples: Amazon S3, Cloudflare R2, Backblaze B2, MinIO, and many more!
* **UpdatePulse Server does not** install executable code from the Version Control System onto your installation of WordPress, and **does not** track your activity. It is designed to only store packages and licenses, and to provide updates when they are requested.
-* **Licenses:** manage licenses with License Key, Registered Email, Status, Package Type, Package Slug, Creation Date, and Expiry Date; add and edit them with a form, or use the API for more control. Licenses prevent packages from being updated without a valid license. Licenses Keys are generated automatically by default and the values are unguessable (it is recommended to keep the default). When checking the validity of licenses, an extra license signature is also checked to prevent the use of a license on more than the configured allowed domains.
+* **Licenses:** manage licenses with License Key, Registered Email, Status, Package Type, Package Slug, Creation Date, and Expiration Date; add and edit them with a form, or use the API for more control. Licenses prevent packages from being updated without a valid license. Licenses Keys are generated automatically by default and the values are unguessable (it is recommended to keep the default). When checking the validity of licenses, an extra license signature is also checked to prevent the use of a license on more than the configured allowed domains.
* **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/?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.
+
+If you wish to see your plugin added to this list, please [contact the author](mailto:updatepulse@anyape.com).
+
+== Troubleshooting ==
+
+Please read the plugin FAQ, there is a lot that may help you there!
+
+UpdatePulse Server is regularly updated for compatibility, and bug reports are welcome, preferably on [Github](https://github.com/anyape/updatepulse-server/). Pull Requests from developers following the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) (`WordPress-Extra` ruleset) are highly appreciated and will be credited upon merge.
+
+In case the plugin has not been updated for a while, no panic: it simply means the compatibility flag has not been changed, and it very likely remains compatible with the latest version of WordPress. This is because it was designed with long-term compatibility in mind from the ground up.
+
+Each **bug** report will be addressed in a timely manner if properly documented – previously unanswered general inquiries and issues reported on the WordPress forum may take significantly longer to receive a response (if any).
+
+**Only issues occurring with WordPress core, WooCommerce, and default WordPress themes (incl. WooCommerce Storefront) will be considered.**
+
+**Troubleshooting involving 3rd-party plugins or themes will not be addressed on the WordPress support forum.**
+
+== Upgrade Notice ==
+
+= 1.0.9 =
+
+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? =
+UpdatePulse Server is a plugin for developers, not end-users. It allows developers to provide updates for their software packages, including WordPress plugins and themes. For more information on how to use it, please refer to the [documentation](https://github.com/anyape/updatepulse-server/blob/main/README.md).
+
+= How do I connect my plugin/theme to UpdatePulse Server? =
+To connect your plugin or theme to UpdatePulse Server, you can either use one of the integration examples provided in the [UpdatePulse Server Integration Examples](https://github.com/Anyape/updatepulse-server-integration), or develop your own on top of [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker).
+
+If you decide to develop your own, the key is to call the [UpdatePulse Server Update API](https://github.com/anyape/updatepulse-server/blob/main/docs/misc.md#update-api) to check for updates, with the necessary information in the request. The API will return a JSON response with the update information, which you can then use to display the update notification, check for a license for your plugin or theme, and download the update package.
+
+= How does the license system work? =
+The license system allows developers to manage licenses for their software packages. Licenses prevent packages from being updated without a valid license. License Keys are generated automatically by default and the values are unguessable (it is recommended to keep the default). When checking the validity of licenses, an extra license signature is also checked to prevent the use of a license on more than the configured allowed domains.
+
+= How do I manage packages? =
+You can manage packages through the UpdatePulse Server interface, through the API, or by letting the plugin download them automatically from a Version Control System (preferred). The interface allows you to view a listing of packages, view details, delete, download, and upload new packages manually (discouraged).
+
+= I have a problem with the plugin, what should I do? =
+If you have a problem with the plugin, please check the FAQ and the documentation first.
+
+Then, make sure to flush your WordPress permalinks (Settings > Permalinks > Save Changes), clear your browser cache, and clear any caching plugins you may have installed. If you are using a CDN, make sure to clear the cache there as well.
+
+Make sure you are not trying to update a package installed alongside UpdatePulse Server - the package must be installed on a different WordPress installation.
+
+If you still have a problem, please open an issue on [GitHub](https://github.com/Anyape/updatepulse-server/issues) with a **detailed description of the problem**, including any **error messages you are receiving**, and **most importantly, the steps to reproduce the issue, in details**.
+
+Only issues occurring with WordPress core, WooCommerce, and default WordPress themes (incl. WooCommerce Storefront) will be considered: integration with 3rd-party plugins or themes will only be addressed if you can provide a patch in a pull request, and if this makes sense for the author. If not, please either contact the author of the plugin/theme you are having issues with, or provide your own integration with a custom plugin.
+
+= How can I sell package licenses? =
+UpdatePulse Server does not provide a built-in way to sell licenses. To sell licenses, your chosen e-commerce solution must be integrated with UpdatePulse Server License API. This can be done by creating a custom plugin that connects your e-commerce solution with UpdatePulse Server License API, or by using an existing integration if available. At this time, there is no official e-commerce integration plugin for UpdatePulse Server.
+
+= Is UpdatePulse Server compatible with X Plugin/Theme? with multisite? =
+
+UpdatePulse Server by itself does not provide any frontend functionality to your users.
+
+As a general rule, the more isolated UpdatePulse Server is from the rest of your ecosystem, the better, as it allows the server to perform without interference: it is not meant to be used alongside other plugins or themes, but more as a standalone server.
+
+UpdatePulse Server is not meant to be used in a multisite environment either: it is a server delivering packages and licenses to clients, and has no place in a multisite environment.
+
+If you still decide to use UpdatePulse Server on a website not solely dedicated to it, it is still possible ; to avoid interference, you may want to add the MU Plugin `upserv-plugins-optimizer.php` provided in the [UpdatePulse Server Integration](https://github.com/Anyape/updatepulse-server-integration) repository to bypass plugins and themes when calling the UpdatePulse Server APIs.
== Screenshots ==
@@ -63,5 +138,62 @@ 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
+* Minor fix
+* Fix activation issue - `WP_Filesystem` call
+
+= 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
+
+= 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)
+* Implement a VCS candidates logic to handle events that do not specify a branch; gracefully fail with a message in the response if multiple candidates are found
+* Major in-code and .md documentation improvements
+
+= 1.0.5 =
+* Fix JSON details modal view - escaping characters
+* Make sure to differenciate between `file_last_modified` ("File Modified", the time the file was changed on the file system) and `last_updated` (package version update time)
+
+= 1.0.4 =
+* More flexibility when parsing `Require License` header
+* Fix VCS test
+* Fix file system permission check
+
+= 1.0.3 =
+* Minor Package API fix
+* All API: remove `JSON_NUMERIC_CHECK` when encoding output as it creates issues with values like version numbers.
+* Fix deprecated PHP 8.3 calls to `get_class()`
+* Add a URL to test the Update API endpoint in Packages JSON details
+* Minor code cleanup
+
+= 1.0.2 =
+* Minor Package API fix
+* Minor License API fix
+* Minor License Server fix
+* Improve record delete
+* Expiry => Expiration in all UI
+* Improved Licenses table styles
+* Add `@package` to main plugin file
+* Hard-force PHP min version to 8.0
+* Fix API details modal
+* Fix webhooks with empty license API keys (not recommended)
+* Fix minor scheduler issue
+
+= 1.0.1 =
+* Minor readme updates
+* Minor package API fixes
+* Manual upload validation fix
+* Cloud storage hooks fix
+
= 1.0 =
Major rewrite from the original WP Plugins Update Server - renamed to UpdatePulse Server, many new features, improvements and bugfixes. No upgrade path from WPPUS.
\ No newline at end of file
diff --git a/tests.php b/tests.php
index bf2a309..5fbbfc7 100644
--- a/tests.php
+++ b/tests.php
@@ -1,13 +1,40 @@
queries ) - count( $upserv_queries_before ) );
- $scripts_stats = 'Number of included/required scripts by the plugin: ' . count( $scripts );
- $mem_stats = 'Server memory used to run the plugin: ' . upserv_get_formatted_memory( $mem_after - $upserv_mem_before ) . ' / ' . ini_get( 'memory_limit' );
- $elapsed_time = 'N/A';
+ $query_list = array();
+ // Calculate diff stats for plugin-specific usage
+ $plugin_queries = count( $wpdb->queries ) - count( $upserv_queries_before );
+
+ // Format stats for logging
+ $query_stats = 'Number of queries executed by the plugin: ' . $plugin_queries;
+ $scripts_stats = 'Number of included/required scripts by the plugin: ' . count( $scripts );
+ $mem_stats = 'Server memory used to run the plugin: ' .
+ upserv_get_formatted_memory( $mem_after - $upserv_mem_before ) .
+ ' / ' . ini_get( 'memory_limit' );
+
+ // Format query data
foreach ( $wpdb->queries as $query ) {
$query_list[] = reset( $query );
}
+ // Calculate execution time if available
+ $elapsed_time = 'N/A';
+
if ( ! empty( $_SERVER['REQUEST_TIME_FLOAT'] ) ) {
$req_time_float = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_TIME_FLOAT'] ) );
@@ -79,16 +141,21 @@ function upserv_performance_stats_log() {
}
}
+ // Log all collected metrics
upserv_tests_log( '========================================================' );
upserv_tests_log( '--- Start load tests ---' );
upserv_tests_log( 'Time elapsed: ' . $elapsed_time . 'sec' );
- upserv_tests_log( 'Total server memory used: ' . upserv_get_formatted_memory( $mem_after ) . ' / ' . ini_get( 'memory_limit' ) );
+ upserv_tests_log(
+ 'Total server memory used: ' . upserv_get_formatted_memory( $mem_after ) .
+ ' / ' . ini_get( 'memory_limit' )
+ );
upserv_tests_log( 'Total number of queries: ' . count( $wpdb->queries ) );
upserv_tests_log( 'Total number of scripts: ' . count( $scripts_after ) );
upserv_tests_log( $mem_stats );
upserv_tests_log( $query_stats );
upserv_tests_log( $scripts_stats );
+ // Log detailed information based on configuration
if ( $upserv_tests_show_queries_details ) {
upserv_tests_log( 'Queries: ', $query_list );
}
@@ -99,4 +166,6 @@ function upserv_performance_stats_log() {
upserv_tests_log( '--- End load tests ---' );
}
+
+// Register the performance logging function to run at the end of request processing
add_action( 'shutdown', 'upserv_performance_stats_log' );
diff --git a/uninstall.php b/uninstall.php
index eba5e18..3ab53ec 100644
--- a/uninstall.php
+++ b/uninstall.php
@@ -1,16 +1,42 @@
dirlist( $mu_plugin_path );
@@ -37,19 +64,25 @@ function upserv_uninstall() {
}
}
+ // Remove the plugin's uploads directory
$wp_upload_dir = wp_upload_dir();
$upserv_dir = trailingslashit( $wp_upload_dir['basedir'] . '/updatepulse-server' );
$wp_filesystem->delete( $upserv_dir, true );
+ // Clean up Action Scheduler entries if available
if ( function_exists( 'as_unschedule_all_actions' ) ) {
as_unschedule_all_actions( 'upserv_cleanup' );
}
+ // Remove default WordPress cron hooks
wp_unschedule_hook( 'upserv_cleanup' );
+
+ // Delete all plugin options and database tables
$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE `option_name` LIKE %s", '%upserv_%' ) );
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}upserv_licenses;" );
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}upserv_nonce;" );
}
+// Execute the uninstall process
upserv_uninstall();
diff --git a/updatepulse-server.php b/updatepulse-server.php
index 2528a73..7a75ab4 100644
--- a/updatepulse-server.php
+++ b/updatepulse-server.php
@@ -1,20 +1,29 @@
queries;
}
+// Import required namespace components for the plugin
use Anyape\UpdatePulse\Server\Nonce\Nonce;
use Anyape\UpdatePulse\Server\API\License_API;
use Anyape\UpdatePulse\Server\API\Webhook_API;
@@ -45,6 +56,7 @@ use Anyape\UpdatePulse\Server\Manager\API_Manager;
use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
use Anyape\UpdatePulse\Server\UPServ;
+// Define essential plugin constants for file paths and URLs
if ( ! defined( 'UPSERV_PLUGIN_PATH' ) ) {
define( 'UPSERV_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
}
@@ -61,6 +73,7 @@ if ( ! defined( 'UPSERV_MB_TO_B' ) ) {
define( 'UPSERV_MB_TO_B', 1000000 );
}
+// Load required files, allowing for extension via filter
$require = apply_filters( 'upserv_mu_require', array( UPSERV_PLUGIN_PATH . 'autoload.php' ) );
foreach ( $require as $file ) {
@@ -70,6 +83,7 @@ foreach ( $require as $file ) {
}
}
+// Load plugin options and extract private API keys for authentication
$options = json_decode( get_option( 'upserv_options' ), true );
$private_keys = array();
@@ -83,9 +97,12 @@ if ( is_array( $options ) && ! empty( $options ) && isset( $options['api'] ) ) {
}
}
+// Initialize nonce authentication with the extracted private keys
Nonce::register();
Nonce::init_auth( $private_keys );
+// Register activation, deactivation and uninstall hooks for core plugin classes
+// Skip during API requests to optimize performance
if (
! Update_API::is_doing_api_request() &&
! License_API::is_doing_api_request()
@@ -118,12 +135,27 @@ if (
}
}
+// Register WP-CLI commands if the CLI environment is active
if ( defined( 'WP_CLI' ) && constant( 'WP_CLI' ) ) {
require_once UPSERV_PLUGIN_PATH . 'functions.php';
WP_CLI::add_command( 'updatepulse-server', 'Anyape\\UpdatePulse\\Server\\CLI\\CLI' );
}
+/**
+ * Main initialization function for UpdatePulse Server plugin.
+ *
+ * This function is responsible for initializing different components based on request type:
+ * - Sets up caching
+ * - Loads required dependencies
+ * - Initializes core components like Scheduler and APIs
+ * - Conditionally loads admin-specific components
+ *
+ * The function intelligently loads only what's needed for each request type to optimize performance.
+ *
+ * @since 1.0.0
+ * @return void
+ */
function upserv_run() {
if ( ! did_action( 'upserv_mu_optimizer_ready' ) ) {
@@ -134,11 +166,13 @@ function upserv_run() {
require_once UPSERV_PLUGIN_PATH . 'functions.php';
+ // Determine request type to optimize component loading
$license_api_request = upserv_is_doing_license_api_request();
$priority_api_request = apply_filters( 'upserv_is_priority_api_request', $license_api_request );
$is_api_request = $priority_api_request;
$objects = apply_filters( 'upserv_objects', array() );
+ // Initialize core API components needed for all request types
if ( ! isset( $objects['scheduler'] ) ) {
$objects['scheduler'] = new Scheduler( true );
}
@@ -151,12 +185,14 @@ function upserv_run() {
$objects['webhook_api'] = new Webhook_API( true );
}
+ // Load additional components for non-priority API requests
if ( ! $priority_api_request ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
do_action( 'upserv_no_priority_api_includes' );
+ // Check for other API request types
$is_api_request = (
upserv_is_doing_update_api_request() ||
upserv_is_doing_webhook_api_request() ||
@@ -178,6 +214,7 @@ function upserv_run() {
$is_api_request = apply_filters( 'upserv_is_api_request', $is_api_request );
+ // Load admin interface components only when not handling API requests
if ( ! $is_api_request ) {
if ( ! class_exists( 'WP_List_Table' ) ) {
@@ -215,6 +252,16 @@ function upserv_run() {
}
add_action( 'plugins_loaded', 'upserv_run', PHP_INT_MIN + 100, 0 );
+/**
+ * Handles plugin updates and migrations.
+ *
+ * This function checks if we're in an API context, and if not, loads the update/migration
+ * functionality. During admin page loads, it hooks into plugins_loaded at a very early
+ * priority to ensure migrations run before the main plugin code.
+ *
+ * @since 1.0.0
+ * @return void
+ */
function upserv_updater() {
$doing_api = wp_cache_get( 'upserv_mu_doing_api', 'updatepulse-server' );
@@ -241,6 +288,7 @@ function upserv_updater() {
}
upserv_updater();
+// Load testing functionality if enabled via constant
if ( defined( 'UPSERV_ENABLE_TEST' ) && constant( 'UPSERV_ENABLE_TEST' ) ) {
if ( Update_API::is_doing_api_request() || License_API::is_doing_api_request() ) {