mirror of
https://github.com/Anyape/updatepulse-server.git
synced 2025-11-22 04:35:56 +08:00
Compare commits
51 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4847ed1ed7 | ||
|
|
84da98bc6a | ||
|
|
8356ac6ca2 | ||
|
|
8efebb1774 | ||
|
|
575c7c3cdb | ||
|
|
e1fc90780e | ||
|
|
deda3090c3 | ||
|
|
15ce059512 | ||
|
|
c6155be1fa | ||
|
|
ec8856175b | ||
|
|
d4a206ab1f | ||
|
|
49adf7f6b4 | ||
|
|
2589b5071b | ||
|
|
4bb83b4837 | ||
|
|
0c3c1ab306 | ||
|
|
58131d74f7 | ||
|
|
15a3ffdaaa | ||
|
|
87aec68366 | ||
|
|
ae4cc6e0e2 | ||
|
|
452184d062 | ||
|
|
791ca0559e | ||
|
|
9cd489575e | ||
|
|
3b7f404f7f | ||
|
|
8020d655e3 | ||
|
|
475b89c0f3 | ||
|
|
db07e75b7d | ||
|
|
76de3434c5 | ||
|
|
28c10e9e45 | ||
|
|
8f02af5c90 | ||
|
|
03398f03a2 | ||
|
|
df4ca42486 | ||
|
|
362ccb6640 | ||
|
|
be55f7bb9f | ||
|
|
909c91e02e | ||
|
|
78611f86e4 | ||
|
|
2b5b31ac59 | ||
|
|
a0cc494bad | ||
|
|
e7b64d6b2a | ||
|
|
0e3f1c8fce | ||
|
|
16ae581fab | ||
|
|
b8d92631dd | ||
|
|
2b841124ad | ||
|
|
8163c7baf8 | ||
|
|
e00deb45a9 | ||
|
|
fc43a2c8de | ||
|
|
81e345930d | ||
|
|
8f95234285 | ||
|
|
3c2968a453 | ||
|
|
1e3add450e | ||
|
|
b3dd2c6f53 | ||
|
|
80097427dc |
56 changed files with 8632 additions and 895 deletions
|
|
@ -50,7 +50,7 @@ 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 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).
|
||||
|
|
@ -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.<br/>Supports Bitbucket, Github and Gitlab.<br/>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.<br/>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.<br/>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.<br/>If the VCS supports releases or tags, they will be prioritised over the branch name (release first, then tag, then branch).<br/>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.<br/>In the case of Github and Gitlab, a Personal Access Token; in the case of Bitckucket, an App Password.<br/>**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.<br>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.<br>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.<br>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.<br>Leave at `0` to schedule a package update during the cron run happening immediately after the Webhook notification was received.
|
||||
|
|
|
|||
21
autoload.php
21
autoload.php
|
|
@ -1,7 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* Custom PSR-4 Autoloader for UpdatePulse Server
|
||||
*
|
||||
* Handles class autoloading for the plugin, mapping namespaces to file paths
|
||||
* using both explicit class mappings and PSR-4 style directory structure.
|
||||
*
|
||||
* @package UPServ
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Anyape\UpdatePulse\Server;
|
||||
|
||||
/**
|
||||
* Autoload function for loading plugin classes
|
||||
*
|
||||
* This function is responsible for automatically loading classes based on their namespace.
|
||||
* It uses a static class map for direct class-to-file mappings, and falls back to
|
||||
* PSR-4 style path generation for plugin-specific namespaces.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $class_name The fully qualified class name to load
|
||||
* @return void
|
||||
*/
|
||||
function autoload( $class_name ) {
|
||||
static $f_root = UPSERV_PLUGIN_PATH;
|
||||
static $ns_root = __NAMESPACE__;
|
||||
|
|
@ -37,4 +57,5 @@ function autoload( $class_name ) {
|
|||
}
|
||||
}
|
||||
|
||||
// Register the autoloader with SPL
|
||||
spl_autoload_register( 'Anyape\UpdatePulse\Server\autoload' );
|
||||
|
|
|
|||
112
deploy.sh
112
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
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
___
|
||||
173
docs/misc.md
173
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 '<div>The credentials are: ' . esc_html( $values['credentials'] ) . '</div>
|
|||
echo '<div>The signature is: ' . esc_html( $values['signature'] ) . '</div>';
|
||||
```
|
||||
|
||||
## Update API
|
||||
|
||||
The Update API is accessible via `GET` requests on the `/updatepulse-server-update-api/` endpoint.
|
||||
It has two actions: `get_metadata` and `download`.
|
||||
|
||||
### The `get_metadata` action
|
||||
|
||||
The `get_metadata` action is used to check for updates. It accepts the following parameters:
|
||||
|
||||
| Parameter | Description | Required |
|
||||
| --- | --- | --- |
|
||||
| `action` | The action to perform. Must be `get_metadata`. | Yes |
|
||||
| `package_id` | The ID of the package to check for updates. | Yes |
|
||||
| `installed_version` | The version of the package currently installed. | No |
|
||||
| `php` | The PHP version of the client. | No |
|
||||
| `locale` | The locale of the client. | No |
|
||||
| `checking_for_updates` | A flag indicating whether the client is checking for updates. | No |
|
||||
| `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 |
|
||||
|
||||
Example of a request to the Update API with:
|
||||
- `get_metadata` action
|
||||
- `package_id` set to `dummy-plugin`
|
||||
- `installed_version` set to `1.0`
|
||||
- `php` set to `8.3`
|
||||
- `locale` set to `en_US`
|
||||
- `checking_for_updates` set to `1`
|
||||
- `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=get_metadata&package_id=dummy-plugin&installed_version=1.0&php=8.3&locale=en_US&checking_for_updates=1&license_key=abcdef1234567890&license_signature=signabcdef1234567890&update_type=Plugin"
|
||||
```
|
||||
|
||||
Example of a response (success):
|
||||
```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": "<div class=\"readme-section\" data-name=\"Description\"><p>Update Plugin description. <strong>Basic HTML<\/strong> can be used in all sections.<\/p><\/div>",
|
||||
"dummy_section": "<div class=\"readme-section\" data-name=\"Dummy Section\"><p>An extra, dummy section.<\/p><\/div>",
|
||||
"installation": "<div class=\"readme-section\" data-name=\"Installation\"><p>Installation instructions.<\/p><\/div>",
|
||||
"changelog": "<div class=\"readme-section\" data-name=\"Changelog\"><p>This section will be displayed by default when the user clicks 'View version x.y.z details'.<\/p><\/div>",
|
||||
"frequently_asked_questions": "<div class=\"readme-section\" data-name=\"Frequently Asked Questions\"><h4>Question<\/h4><p>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": "<div class=\"readme-section\" data-name=\"Description\"><p>Update Plugin description. <strong>Basic HTML<\/strong> can be used in all sections.<\/p><\/div>",
|
||||
"dummy_section": "<div class=\"readme-section\" data-name=\"Dummy Section\"><p>An extra, dummy section.<\/p><\/div>",
|
||||
"installation": "<div class=\"readme-section\" data-name=\"Installation\"><p>Installation instructions.<\/p><\/div>",
|
||||
"changelog": "<div class=\"readme-section\" data-name=\"Changelog\"><p>This section will be displayed by default when the user clicks 'View version x.y.z details'.<\/p><\/div>",
|
||||
"frequently_asked_questions": "<div class=\"readme-section\" data-name=\"Frequently Asked Questions\"><h4>Question<\/h4><p>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`
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
545
functions.php
545
functions.php
|
|
@ -1,4 +1,13 @@
|
|||
<?php
|
||||
/**
|
||||
* UpdatePulse Server Core Functions
|
||||
*
|
||||
* This file contains essential functions for the UpdatePulse Server plugin.
|
||||
* It handles updates, licensing, package management and other core functionality.
|
||||
*
|
||||
* @package UPServ
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
|
|
@ -18,6 +27,18 @@ use Anyape\UpdatePulse\Server\UPServ;
|
|||
*******************************************************************/
|
||||
|
||||
if ( ! function_exists( 'upserv_get_vcs_name' ) ) {
|
||||
/**
|
||||
* Get the formatted name of a version control system
|
||||
*
|
||||
* Returns the name of the given VCS type formatted according to the context.
|
||||
* When context is 'view', returns the translatable string meant for display.
|
||||
* When context is something else, returns the plain string name.
|
||||
*
|
||||
* @param string $type The VCS type ('github', 'gitlab', 'bitbucket')
|
||||
* @param string $context The context for the return value ('view' or other)
|
||||
*
|
||||
* @return string|null The VCS name formatted according to context, or null if invalid type with non-view context
|
||||
*/
|
||||
function upserv_get_vcs_name( $type, $context = 'view' ) {
|
||||
|
||||
switch ( $type ) {
|
||||
|
|
@ -38,36 +59,102 @@ if ( ! function_exists( 'upserv_get_vcs_name' ) ) {
|
|||
*******************************************************************/
|
||||
|
||||
if ( ! function_exists( 'upserv_get_options' ) ) {
|
||||
/**
|
||||
* Retrieves all plugin options
|
||||
*
|
||||
* Gets the complete options array from the main UPServ instance.
|
||||
*
|
||||
* @since 1.0
|
||||
*
|
||||
* @return array All plugin options
|
||||
*/
|
||||
function upserv_get_options() {
|
||||
return UPServ::get_instance()->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 (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
|
@ -1116,6 +1576,14 @@ 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',
|
||||
(
|
||||
|
|
@ -1130,6 +1598,13 @@ 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 ) ) {
|
||||
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
/**
|
||||
* 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 ) ) {
|
||||
|
|
@ -73,6 +143,16 @@ class Package_API {
|
|||
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 ) {
|
||||
|
|
@ -101,6 +196,16 @@ class Package_API {
|
|||
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 (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 (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;
|
||||
|
|
@ -208,6 +387,16 @@ class Package_API {
|
|||
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,14 +411,39 @@ 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 );
|
||||
/**
|
||||
* 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 ) {
|
||||
|
|
@ -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;
|
||||
|
|
@ -276,13 +505,19 @@ class Package_API {
|
|||
|
||||
// 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();
|
||||
|
|
|
|||
|
|
@ -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,12 +554,30 @@ 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
|
||||
);
|
||||
/**
|
||||
* 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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -280,7 +416,27 @@ class Webhook_API {
|
|||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +135,13 @@ 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(), '8.0', '>=' ) ) {
|
||||
|
|
@ -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(
|
||||
'<a href="' . admin_url( 'admin.php?page=upserv-page-help' ) . '">' . __( 'Help', 'updatepulse-server' ) . '</a>',
|
||||
|
|
@ -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,6 +579,17 @@ 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(
|
||||
|
|
@ -340,12 +598,28 @@ class UPServ {
|
|||
'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( '<div class="%1$s"><p>%2$s</p></div>', $class, $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Display MU plugin setup success notice
|
||||
*
|
||||
* Shows admin notice when MU plugin was successfully installed.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function setup_mu_plugin_success_notice() {
|
||||
$class = 'notice notice-info is-dismissible';
|
||||
$message = sprintf(
|
||||
|
|
@ -398,6 +706,14 @@ class UPServ {
|
|||
printf( '<div class="%1$s"><p>%2$s</p></div>', $class, $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Display settings header
|
||||
*
|
||||
* Renders the header for settings pages with notices.
|
||||
*
|
||||
* @param string|array $notice Optional notice to display
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display_settings_header( $notice ) {
|
||||
echo '<h1>' . esc_html__( 'UpdatePulse Server', 'updatepulse-server' ) . '</h1>';
|
||||
|
||||
|
|
@ -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 ) ) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
|
|
@ -132,10 +156,12 @@ class Utils {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $regex
|
||||
* Get remote IP address
|
||||
*
|
||||
* @return int|null
|
||||
* Safely retrieves the remote IP address of the client.
|
||||
*
|
||||
* @return string IP address of the client or '0.0.0.0' if not available or invalid
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_remote_ip() {
|
||||
|
||||
|
|
@ -152,6 +178,15 @@ class Utils {
|
|||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable status string
|
||||
*
|
||||
* Converts a status code to a localized human-readable string.
|
||||
*
|
||||
* @param string $status Status code to convert
|
||||
* @return string Localized human-readable status string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_status_string( $status ) {
|
||||
switch ( $status ) {
|
||||
case 'pending':
|
||||
|
|
|
|||
|
|
@ -11,10 +11,33 @@ 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;
|
||||
/**
|
||||
* 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',
|
||||
|
|
@ -25,6 +48,12 @@ class CLI extends WP_CLI_Command {
|
|||
'halt',
|
||||
'error_multi_line',
|
||||
);
|
||||
/**
|
||||
* Available package types supported by the plugin.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected const PACKAGE_TYPES = array(
|
||||
'plugin',
|
||||
'theme',
|
||||
|
|
@ -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 <nonce>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_nonce_expiry( $args, $assoc_args ) {
|
||||
$nonce = $args[0];
|
||||
|
|
@ -374,6 +426,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse get_nonce_data <nonce>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_nonce_data( $args, $assoc_args ) {
|
||||
$nonce = $args[0];
|
||||
|
|
@ -395,6 +449,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse delete_nonce <nonce>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function delete_nonce( $args, $assoc_args ) {
|
||||
$nonce = $args[0];
|
||||
|
|
@ -416,6 +472,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse browse_licenses <license_query>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function browse_licenses( $args, $assoc_args ) {
|
||||
$result = upserv_browse_licenses( $args[0] );
|
||||
|
|
@ -436,6 +494,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse read_license <license_key_or_id>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function read_license( $args, $assoc_args ) {
|
||||
$license_data = array();
|
||||
|
|
@ -464,6 +524,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse add_license <license_data>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_license( $args, $assoc_args ) {
|
||||
$payload = json_decode( $args[0], true );
|
||||
|
|
@ -488,6 +550,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse edit_license <license_data>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function edit_license( $args, $assoc_args ) {
|
||||
$payload = json_decode( $args[0], true );
|
||||
|
|
@ -512,6 +576,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse delete_license <license_key_or_id>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function delete_license( $args, $assoc_args ) {
|
||||
$license_data = array();
|
||||
|
|
@ -539,6 +605,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse check_license <license_key_or_id>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function check_license( $args, $assoc_args ) {
|
||||
$license_data = array();
|
||||
|
|
@ -576,6 +644,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse activate_license <license_key> <package-slug> <domain>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function activate_license( $args, $assoc_args ) {
|
||||
$license_data = array(
|
||||
|
|
@ -610,6 +680,8 @@ class CLI extends WP_CLI_Command {
|
|||
* ## EXAMPLES
|
||||
*
|
||||
* wp updatepulse deactivate_license <license_key> <package-slug> <domain>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function deactivate_license( $args, $assoc_args ) {
|
||||
$license_data = array(
|
||||
|
|
@ -631,6 +703,13 @@ class CLI extends WP_CLI_Command {
|
|||
* Protected methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Cleans up a specific folder in the plugin directory.
|
||||
*
|
||||
* @param string $method The folder to clean up ('cache', 'logs', or 'tmp').
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function cleanup( $method ) {
|
||||
$method = 'upserv_force_cleanup_' . $method;
|
||||
$result = $method();
|
||||
|
|
@ -640,6 +719,17 @@ class CLI extends WP_CLI_Command {
|
|||
$this->process_result( $result, $success_message, $error_message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the result of a command and outputs a message based on success or failure.
|
||||
*
|
||||
* @param mixed $result The result to evaluate.
|
||||
* @param mixed $success_message Message to display on success.
|
||||
* @param string $error_message Message to display on error.
|
||||
* @param int $error_code Error code to return on failure. Default: 1.
|
||||
* @param string $error_level WP_CLI error level to use. Default: 'warning'.
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function process_result( $result, $success_message, $error_message, $error_code = 1, $error_level = 'warning' ) {
|
||||
|
||||
if ( $result instanceof WP_Error ) {
|
||||
|
|
@ -680,6 +770,17 @@ class CLI extends WP_CLI_Command {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a message to the CLI.
|
||||
*
|
||||
* Handles both string and array message formats. If an array is provided,
|
||||
* it should contain 'level' and 'output' keys. The level determines which
|
||||
* WP_CLI output method to use.
|
||||
*
|
||||
* @param string|array $message The message to output.
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function output( $message ) {
|
||||
|
||||
if ( is_string( $message ) ) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,19 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* API Manager class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class API_Manager {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boolean $init_hooks Whether to initialize hooks
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $init_hooks = false ) {
|
||||
|
||||
if ( $init_hooks ) {
|
||||
|
|
@ -28,6 +39,15 @@ class API_Manager {
|
|||
|
||||
// WordPress hooks ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Register admin styles
|
||||
*
|
||||
* Add custom styles used by the API admin interface.
|
||||
*
|
||||
* @param array $styles Existing admin styles.
|
||||
* @return array Modified admin styles.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_styles( $styles ) {
|
||||
$styles['api'] = array(
|
||||
'path' => UPSERV_PLUGIN_PATH . 'css/admin/api' . upserv_assets_suffix() . '.css',
|
||||
|
|
@ -37,6 +57,15 @@ class API_Manager {
|
|||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin scripts
|
||||
*
|
||||
* Add custom scripts used by the API admin interface.
|
||||
*
|
||||
* @param array $scripts Existing admin scripts.
|
||||
* @return array Modified admin scripts.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_scripts( $scripts ) {
|
||||
$page = ! empty( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
|
|
@ -93,6 +122,13 @@ class API_Manager {
|
|||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin menu
|
||||
*
|
||||
* Add the API settings page to the admin menu.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function admin_menu() {
|
||||
$function = array( $this, 'plugin_page' );
|
||||
$page_title = __( 'UpdatePulse Server - API & Webhooks', 'updatepulse-server' );
|
||||
|
|
@ -102,6 +138,15 @@ class API_Manager {
|
|||
add_submenu_page( 'upserv-page', $page_title, $menu_title, 'manage_options', $menu_slug, $function );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin tab links
|
||||
*
|
||||
* Add API tab to the admin navigation.
|
||||
*
|
||||
* @param array $links Existing tab links.
|
||||
* @return array Modified tab links.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_tab_links( $links ) {
|
||||
$links['api'] = array(
|
||||
admin_url( 'admin.php?page=upserv-page-api' ),
|
||||
|
|
@ -111,6 +156,16 @@ class API_Manager {
|
|||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin tab states
|
||||
*
|
||||
* Set active state for API tab in admin navigation.
|
||||
*
|
||||
* @param array $states Existing tab states.
|
||||
* @param string $page Current admin page.
|
||||
* @return array Modified tab states.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_tab_states( $states, $page ) {
|
||||
$states['api'] = 'upserv-page-api' === $page;
|
||||
|
||||
|
|
@ -119,6 +174,13 @@ class API_Manager {
|
|||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Render plugin page
|
||||
*
|
||||
* Output the API settings admin interface.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function plugin_page() {
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
|
|
@ -160,14 +222,35 @@ class API_Manager {
|
|||
'plugin-api-page.php',
|
||||
array(
|
||||
'options' => $options,
|
||||
/**
|
||||
* Filter the list of available License API actions.
|
||||
*
|
||||
* @param array $actions The list of available License API actions
|
||||
* @return array The filtered list of actions
|
||||
* @since 1.0.0
|
||||
*/
|
||||
'license_api_actions' => apply_filters(
|
||||
'upserv_api_license_actions',
|
||||
array()
|
||||
),
|
||||
/**
|
||||
* Filter the list of available Package API actions.
|
||||
*
|
||||
* @param array $actions The list of available Package API actions
|
||||
* @return array The filtered list of actions
|
||||
* @since 1.0.0
|
||||
*/
|
||||
'package_api_actions' => apply_filters(
|
||||
'upserv_api_package_actions',
|
||||
array()
|
||||
),
|
||||
/**
|
||||
* Filter the list of available webhook events.
|
||||
*
|
||||
* @param array $webhook_events The list of available webhook events
|
||||
* @return array The filtered list of webhook events
|
||||
* @since 1.0.0
|
||||
*/
|
||||
'webhook_events' => apply_filters(
|
||||
'upserv_api_webhook_events',
|
||||
array(
|
||||
|
|
@ -189,6 +272,14 @@ class API_Manager {
|
|||
* Protected methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Handle plugin options
|
||||
*
|
||||
* Process and save API settings form submissions.
|
||||
*
|
||||
* @return string|array Success message or array of errors.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function plugin_options_handler() {
|
||||
$errors = array();
|
||||
$result = '';
|
||||
|
|
@ -355,6 +446,16 @@ class API_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter whether an API option should be updated.
|
||||
*
|
||||
* @param bool $condition Whether the condition for updating the option is met
|
||||
* @param string $option_name The name of the option
|
||||
* @param array $option_info Information about the option
|
||||
* @param array $options All submitted options
|
||||
* @return bool Whether the option should be updated
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$condition = apply_filters(
|
||||
'upserv_api_option_update',
|
||||
$condition,
|
||||
|
|
@ -364,6 +465,16 @@ class API_Manager {
|
|||
);
|
||||
|
||||
if ( $condition ) {
|
||||
/**
|
||||
* Filter the value of an API option before it is saved.
|
||||
*
|
||||
* @param mixed $value The value to save
|
||||
* @param string $option_name The name of the option
|
||||
* @param array $option_info Information about the option
|
||||
* @param array $options All submitted options
|
||||
* @return mixed The filtered value to save
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$to_save[ $option_info['path'] ] = apply_filters(
|
||||
'upserv_api_option_save_value',
|
||||
$option_info['value'],
|
||||
|
|
@ -395,12 +506,33 @@ class API_Manager {
|
|||
$result = $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after API options have been updated.
|
||||
*
|
||||
* @param array $errors Array of errors that occurred during the update process
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_api_options_updated', $errors );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get submitted options
|
||||
*
|
||||
* Retrieve and sanitize form data from API settings form.
|
||||
*
|
||||
* @return array Sanitized form data.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_submitted_options() {
|
||||
/**
|
||||
* Filter the submitted API configuration options.
|
||||
*
|
||||
* @param array $config The submitted API configuration options
|
||||
* @return array The filtered configuration options
|
||||
* @since 1.0.0
|
||||
*/
|
||||
return apply_filters(
|
||||
'upserv_submitted_api_config',
|
||||
array(
|
||||
|
|
|
|||
|
|
@ -18,18 +18,70 @@ use Anyape\UpdatePulse\Server\Server\Update\Package;
|
|||
use Anyape\UpdatePulse\Package_Parser\Parser;
|
||||
use Anyape\Utils\Utils;
|
||||
|
||||
/**
|
||||
* Cloud Storage Manager class
|
||||
*
|
||||
* Handles integration with S3-compatible cloud storage for package management.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Cloud_Storage_Manager {
|
||||
|
||||
/**
|
||||
* Instance of the Cloud Storage Manager
|
||||
*
|
||||
* @var Cloud_Storage_Manager|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $instance;
|
||||
/**
|
||||
* Cloud storage configuration
|
||||
*
|
||||
* @var array|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $config;
|
||||
/**
|
||||
* Cloud storage client instance
|
||||
*
|
||||
* @var PhpS3|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $cloud_storage;
|
||||
/**
|
||||
* Virtual directory path in cloud storage
|
||||
*
|
||||
* @var string|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $virtual_dir;
|
||||
/**
|
||||
* Hooks registered by the manager
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $hooks = array();
|
||||
|
||||
/**
|
||||
* Whether we're currently performing a redirect
|
||||
*
|
||||
* @var bool
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $doing_redirect = false;
|
||||
|
||||
/**
|
||||
* Download URL lifetime in seconds
|
||||
*/
|
||||
public const DOWNLOAD_URL_LIFETIME = MINUTE_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boolean $init_hooks Whether to initialize hooks
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $init_hooks = false ) {
|
||||
$config = self::get_config();
|
||||
|
||||
|
|
@ -57,6 +109,12 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cloud storage manager
|
||||
*
|
||||
* @param array $config Cloud storage configuration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function init_manager( $config ) {
|
||||
|
||||
if ( ! self::$cloud_storage instanceof PhpS3 ) {
|
||||
|
|
@ -70,10 +128,22 @@ class Cloud_Storage_Manager {
|
|||
|
||||
self::$cloud_storage->setExceptions();
|
||||
|
||||
/**
|
||||
* Filter the virtual directory path used in cloud storage.
|
||||
*
|
||||
* @param string $virtual_dir The default virtual directory name
|
||||
* @return string The filtered virtual directory name
|
||||
* @since 1.0.0
|
||||
*/
|
||||
self::$virtual_dir = apply_filters( 'upserv_cloud_storage_virtual_dir', 'updatepulse-packages' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hooks for cloud storage functionality
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function add_hooks() {
|
||||
|
||||
if ( ! empty( self::$hooks ) ) {
|
||||
|
|
@ -136,6 +206,11 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove hooks for cloud storage functionality
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function remove_hooks() {
|
||||
|
||||
if ( empty( self::$hooks ) ) {
|
||||
|
|
@ -159,6 +234,13 @@ class Cloud_Storage_Manager {
|
|||
self::$hooks = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cloud storage configuration
|
||||
*
|
||||
* @param boolean $force Whether to force reload the configuration
|
||||
* @return array Cloud storage configuration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_config( $force = false ) {
|
||||
|
||||
if ( $force || ! self::$config ) {
|
||||
|
|
@ -168,9 +250,22 @@ class Cloud_Storage_Manager {
|
|||
self::$config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the configuration of the Cloud Storage Manager.
|
||||
*
|
||||
* @param array $config The configuration of the Cloud Storage Manager
|
||||
* @return array The filtered configuration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
return apply_filters( 'upserv_cloud_storage_config', self::$config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cloud Storage Manager instance
|
||||
*
|
||||
* @return Cloud_Storage_Manager The singleton instance
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
if ( ! self::$instance ) {
|
||||
|
|
@ -180,6 +275,13 @@ class Cloud_Storage_Manager {
|
|||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add cloud storage-specific scripts to admin
|
||||
*
|
||||
* @param array $scripts Existing registered scripts
|
||||
* @return array Modified scripts array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_scripts( $scripts ) {
|
||||
$page = ! empty( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
|
|
@ -196,6 +298,13 @@ class Cloud_Storage_Manager {
|
|||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process submitted package configurations
|
||||
*
|
||||
* @param array $config Existing configuration
|
||||
* @return array Modified configuration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_submitted_package_config( $config ) {
|
||||
$config = array_merge(
|
||||
$config,
|
||||
|
|
@ -268,6 +377,16 @@ class Cloud_Storage_Manager {
|
|||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate package option updates
|
||||
*
|
||||
* @param boolean $condition Current validation condition
|
||||
* @param string $option_name Option being updated
|
||||
* @param array $option_info Option information
|
||||
* @param array $options All options being processed
|
||||
* @return boolean Whether option is valid
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_package_option_update( $condition, $option_name, $option_info, $options ) {
|
||||
|
||||
if ( 'use-cloud-storage' === $option_info['condition'] ) {
|
||||
|
|
@ -300,6 +419,11 @@ class Cloud_Storage_Manager {
|
|||
return $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render cloud storage options in template
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_template_package_manager_option_before_miscellaneous() {
|
||||
$options = array(
|
||||
'access_key' => upserv_get_option( 'cloud_storage/access_key' ),
|
||||
|
|
@ -319,6 +443,13 @@ class Cloud_Storage_Manager {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add cloud storage paths to bulk delete
|
||||
*
|
||||
* @param array $package_paths Current package paths
|
||||
* @return array Modified package paths
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_delete_packages_bulk_paths( $package_paths ) {
|
||||
$config = self::get_config();
|
||||
|
||||
|
|
@ -347,6 +478,15 @@ class Cloud_Storage_Manager {
|
|||
return $package_paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if package exists in cloud storage
|
||||
*
|
||||
* @param boolean $package_exists Current existence state
|
||||
* @param array $payload Request payload
|
||||
* @param string $slug Package slug
|
||||
* @return boolean|null Whether package exists in cloud storage
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_webhook_package_exists( $package_exists, $payload, $slug ) {
|
||||
|
||||
if ( null !== $package_exists ) {
|
||||
|
|
@ -383,6 +523,15 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process package removal from cloud storage
|
||||
*
|
||||
* @param boolean $result Current removal result
|
||||
* @param string $type Package type
|
||||
* @param string $slug Package slug
|
||||
* @return boolean Whether removal was successful
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_remove_package_result( $result, $type, $slug ) {
|
||||
$config = self::get_config();
|
||||
|
||||
|
|
@ -427,6 +576,14 @@ class Cloud_Storage_Manager {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify admin template arguments
|
||||
*
|
||||
* @param array $args Current template arguments
|
||||
* @param string $template_name Template being rendered
|
||||
* @return array Modified template arguments
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_get_admin_template_args( $args, $template_name ) {
|
||||
$template_names = array( 'plugin-packages-page.php', 'plugin-help-page.php', 'plugin-remote-sources-page.php' );
|
||||
|
||||
|
|
@ -437,6 +594,13 @@ class Cloud_Storage_Manager {
|
|||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cloud storage connectivity
|
||||
*
|
||||
* AJAX handler for testing cloud storage configuration
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function cloud_storage_test() {
|
||||
$result = array();
|
||||
$nonce = sanitize_text_field( wp_unslash( filter_input( INPUT_POST, 'nonce' ) ) );
|
||||
|
|
@ -527,6 +691,13 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle package options update
|
||||
*
|
||||
* Set up cloud storage after options are updated
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_package_options_updated() {
|
||||
$config = self::get_config( true );
|
||||
|
||||
|
|
@ -566,6 +737,15 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update local package metadata from cloud storage
|
||||
*
|
||||
* @param array|false $local_meta Current local metadata
|
||||
* @param object $local_package Local package instance
|
||||
* @param string $slug Package slug
|
||||
* @return array|false Updated metadata or false
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_check_remote_package_update_local_meta( $local_meta, $local_package, $slug ) {
|
||||
|
||||
if ( ! $local_meta ) {
|
||||
|
|
@ -606,6 +786,14 @@ class Cloud_Storage_Manager {
|
|||
return $local_meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle saving remote package to cloud storage
|
||||
*
|
||||
* @param boolean $local_ready Whether local package is ready
|
||||
* @param string $type Package type
|
||||
* @param string $slug Package slug
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_saved_remote_package_to_local( $local_ready, $type, $slug ) {
|
||||
$config = self::get_config();
|
||||
$package_directory = Data_Manager::get_data_dir( 'packages' );
|
||||
|
|
@ -651,6 +839,14 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle manual package upload to cloud storage
|
||||
*
|
||||
* @param boolean $result Upload result
|
||||
* @param string $type Package type
|
||||
* @param string $slug Package slug
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_did_manual_upload_package( $result, $type, $slug ) {
|
||||
|
||||
if ( ! $result ) {
|
||||
|
|
@ -684,6 +880,16 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to save remote package locally
|
||||
*
|
||||
* @param boolean $save Current save decision
|
||||
* @param string $slug Package slug
|
||||
* @param string $filename Target filename
|
||||
* @param boolean $check_remote Whether to check remote storage
|
||||
* @return boolean Whether to save package locally
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_save_remote_to_local( $save, $slug, $filename, $check_remote ) {
|
||||
$config = self::get_config();
|
||||
|
||||
|
|
@ -718,6 +924,14 @@ class Cloud_Storage_Manager {
|
|||
return $save;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pre-download actions for packages
|
||||
*
|
||||
* @param string $archive_name Archive name
|
||||
* @param string $archive_path Archive path
|
||||
* @param array $package_slugs Package slugs to download
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_before_packages_download( $archive_name, $archive_path, $package_slugs ) {
|
||||
|
||||
if ( 1 === count( $package_slugs ) ) {
|
||||
|
|
@ -753,6 +967,14 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pre-download repack actions for packages
|
||||
*
|
||||
* @param string $archive_name Archive name
|
||||
* @param string $archive_path Archive path
|
||||
* @param array $package_slugs Package slugs to download
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_before_packages_download_repack( $archive_name, $archive_path, $package_slugs ) {
|
||||
|
||||
if ( ! empty( $package_slugs ) ) {
|
||||
|
|
@ -783,6 +1005,13 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle post-download actions for packages
|
||||
*
|
||||
* @param string $archive_name Archive name
|
||||
* @param string $archive_path Archive path
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_after_packages_download( $archive_name, $archive_path ) {
|
||||
|
||||
if ( is_file( $archive_path ) ) {
|
||||
|
|
@ -790,6 +1019,13 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle package API requests
|
||||
*
|
||||
* @param string $method API method being called
|
||||
* @param array $payload Request payload
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_package_api_request( $method, $payload ) {
|
||||
$config = self::get_config();
|
||||
|
||||
|
|
@ -828,12 +1064,26 @@ class Cloud_Storage_Manager {
|
|||
);
|
||||
$this->doing_redirect = wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
|
||||
|
||||
/**
|
||||
* Fired after a package is downloaded.
|
||||
*
|
||||
* @param string $package_slug the slug of the downloaded package
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_download_package', $package_id );
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch package from cloud storage when not in cache
|
||||
*
|
||||
* @param string $slug Package slug
|
||||
* @param string $filename Target filename
|
||||
* @param object $cache Cache instance
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_find_package_no_cache( $slug, $filename, $cache ) {
|
||||
|
||||
if ( is_file( $filename ) ) {
|
||||
|
|
@ -887,12 +1137,29 @@ class Cloud_Storage_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cache key for cloud storage metadata
|
||||
*
|
||||
* @param string $cache_key Current cache key
|
||||
* @param string $slug Package slug
|
||||
* @param string $filename Package filename
|
||||
* @return string Modified cache key
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_zip_metadata_parser_cache_key( $cache_key, $slug, $filename ) {
|
||||
$cloud_cache_key = self::build_cache_key( $slug, $filename );
|
||||
|
||||
return $cloud_cache_key ? $cloud_cache_key : $cache_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package information from cloud storage
|
||||
*
|
||||
* @param array|false $package_info Current package information
|
||||
* @param string $slug Package slug
|
||||
* @return array|false Updated package information
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_package_manager_get_package_info( $package_info, $slug ) {
|
||||
$cache = new Cache( Data_Manager::get_data_dir( 'cache' ) );
|
||||
$config = self::get_config();
|
||||
|
|
@ -1018,6 +1285,14 @@ class Cloud_Storage_Manager {
|
|||
return $package_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch package information from cloud storage
|
||||
*
|
||||
* @param array $packages Current packages information
|
||||
* @param string $search Search term
|
||||
* @return array Updated packages information
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_package_manager_get_batch_package_info( $packages, $search ) {
|
||||
$config = self::get_config();
|
||||
$contents = wp_cache_get( 'upserv-getBucket', 'updatepulse-server' );
|
||||
|
|
@ -1054,6 +1329,14 @@ class Cloud_Storage_Manager {
|
|||
false === strpos( strtolower( $info['slug'] ) . '.zip', strtolower( $search ) )
|
||||
)
|
||||
);
|
||||
/**
|
||||
* Filter 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
|
||||
*/
|
||||
$include = apply_filters( 'upserv_package_info_include', $include, $info );
|
||||
|
||||
if ( $include ) {
|
||||
|
|
@ -1076,6 +1359,12 @@ class Cloud_Storage_Manager {
|
|||
return $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle package download action
|
||||
*
|
||||
* @param object $request Download request
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_update_server_action_download( $request ) {
|
||||
$config = self::get_config();
|
||||
$url = self::$cloud_storage->getAuthenticatedUrlV4(
|
||||
|
|
@ -1087,10 +1376,24 @@ class Cloud_Storage_Manager {
|
|||
$this->doing_redirect = wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if download request is already handled
|
||||
*
|
||||
* @return boolean Whether download is handled
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_update_server_action_download_handled() {
|
||||
return $this->doing_redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if package is whitelisted in cloud storage
|
||||
*
|
||||
* @param boolean $whitelisted Current whitelist status
|
||||
* @param string $package_slug Package slug
|
||||
* @return boolean Updated whitelist status
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_is_package_whitelisted( $whitelisted, $package_slug ) {
|
||||
$data = upserv_get_package_metadata( $package_slug, false );
|
||||
|
||||
|
|
@ -1107,6 +1410,14 @@ class Cloud_Storage_Manager {
|
|||
return $whitelisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update package data when whitelisted
|
||||
*
|
||||
* @param array $data Package data
|
||||
* @param string $slug Package slug
|
||||
* @return array Updated package data
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_whitelist_package_data( $data, $slug ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
$data['whitelisted']['cloud'] = array(
|
||||
true,
|
||||
|
|
@ -1116,6 +1427,14 @@ class Cloud_Storage_Manager {
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update package data when unwhitelisted
|
||||
*
|
||||
* @param array $data Package data
|
||||
* @param string $slug Package slug
|
||||
* @return array Updated package data
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_unwhitelist_package_data( $data, $slug ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
$data['whitelisted']['cloud'] = array(
|
||||
false,
|
||||
|
|
@ -1125,6 +1444,14 @@ class Cloud_Storage_Manager {
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build cache key for cloud storage items
|
||||
*
|
||||
* @param string $slug Package slug
|
||||
* @param string $filename Package filename
|
||||
* @return string|false Cache key or false
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function build_cache_key( $slug, $filename ) {
|
||||
$config = self::get_config();
|
||||
$info = wp_cache_get( $slug . '-getObjectInfo', 'updatepulse-server' );
|
||||
|
|
@ -1147,6 +1474,13 @@ class Cloud_Storage_Manager {
|
|||
return $cache_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if virtual folder exists in cloud storage
|
||||
*
|
||||
* @param string $name Folder name
|
||||
* @return boolean Whether folder exists
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function virtual_folder_exists( $name ) {
|
||||
$config = self::get_config();
|
||||
|
||||
|
|
@ -1156,6 +1490,14 @@ class Cloud_Storage_Manager {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a virtual folder in cloud storage
|
||||
*
|
||||
* @param string $name Folder name
|
||||
* @param string|null $storage_unit Storage unit name
|
||||
* @return boolean Whether folder was created
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function create_virtual_folder( $name, $storage_unit = null ) {
|
||||
|
||||
if ( ! $storage_unit ) {
|
||||
|
|
|
|||
|
|
@ -12,21 +12,48 @@ use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
|
|||
|
||||
class Data_Manager {
|
||||
|
||||
/**
|
||||
* Transient data directories
|
||||
*
|
||||
* List of directories that store temporary data.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $transient_data_dirs = array(
|
||||
'cache',
|
||||
'logs',
|
||||
'tmp',
|
||||
);
|
||||
|
||||
/**
|
||||
* Persistent data directories
|
||||
*
|
||||
* List of directories that store permanent data.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $persistent_data_dirs = array(
|
||||
'packages',
|
||||
'metadata',
|
||||
);
|
||||
|
||||
/**
|
||||
* Transient data in database
|
||||
*
|
||||
* List of temporary data stored in the database.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $transient_data_db = array(
|
||||
'update_from_remote_locks',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boolean $init_hooks Whether to initialize hooks.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $init_hooks = false ) {
|
||||
|
||||
if ( $init_hooks ) {
|
||||
|
|
@ -40,6 +67,13 @@ class Data_Manager {
|
|||
|
||||
// WordPress hooks ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Activate
|
||||
*
|
||||
* Actions to perform when the plugin is activated.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function activate() {
|
||||
set_transient( 'upserv_flush', 1, 60 );
|
||||
|
||||
|
|
@ -64,10 +98,24 @@ class Data_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate
|
||||
*
|
||||
* Actions to perform when the plugin is deactivated.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function deactivate() {
|
||||
self::clear_schedules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize scheduler
|
||||
*
|
||||
* Register cleanup events and schedules.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_scheduler_init() {
|
||||
self::register_cleanup_events();
|
||||
self::register_cleanup_schedules();
|
||||
|
|
@ -75,10 +123,25 @@ class Data_Manager {
|
|||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Clear schedules
|
||||
*
|
||||
* Remove all scheduled cleanup events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function clear_schedules() {
|
||||
self::clear_cleanup_schedules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup directories
|
||||
*
|
||||
* Create data directories if they don't exist.
|
||||
*
|
||||
* @return bool True if directories were created successfully, false otherwise.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function maybe_setup_directories() {
|
||||
$root_dir = self::get_data_dir();
|
||||
$result = true;
|
||||
|
|
@ -100,7 +163,17 @@ class Data_Manager {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup MU plugin
|
||||
*
|
||||
* Create or update the must-use plugin file.
|
||||
*
|
||||
* @return bool True if the MU plugin was setup successfully, false otherwise.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function maybe_setup_mu_plugin() {
|
||||
WP_Filesystem();
|
||||
|
||||
global $wp_filesystem;
|
||||
|
||||
$result = true;
|
||||
|
|
@ -125,6 +198,15 @@ class Data_Manager {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data directory path
|
||||
*
|
||||
* Retrieve the path to a specific data directory.
|
||||
*
|
||||
* @param string $dir Directory name or 'root' for the base directory.
|
||||
* @return string Path to the requested directory.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_data_dir( $dir = 'root' ) {
|
||||
$data_dir = wp_cache_get( 'data_dir_' . $dir, 'updatepulse-server' );
|
||||
|
||||
|
|
@ -156,6 +238,16 @@ class Data_Manager {
|
|||
return $data_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if directory is valid
|
||||
*
|
||||
* Determine whether a directory name is a valid data directory.
|
||||
*
|
||||
* @param string $dir The directory name to check.
|
||||
* @param bool $require_persistent Whether the directory must be persistent.
|
||||
* @return bool Whether the directory is valid.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function is_valid_data_dir( $dir, $require_persistent = false ) {
|
||||
$is_valid = false;
|
||||
|
||||
|
|
@ -168,6 +260,16 @@ class Data_Manager {
|
|||
return $is_valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe cleanup data
|
||||
*
|
||||
* Clean up transient data if needed.
|
||||
*
|
||||
* @param string $type The type of data to clean up.
|
||||
* @param bool $force Whether to force cleanup regardless of conditions.
|
||||
* @return bool Whether cleanup was performed.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function maybe_cleanup( $type, $force = false ) {
|
||||
|
||||
if ( in_array( $type, self::$transient_data_db, true ) ) {
|
||||
|
|
@ -191,6 +293,16 @@ class Data_Manager {
|
|||
* Protected methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Maybe cleanup data directory
|
||||
*
|
||||
* Clean up a data directory if it exceeds its size limit or if forced.
|
||||
*
|
||||
* @param string $type The directory to clean up.
|
||||
* @param bool $force Whether to force cleanup regardless of conditions.
|
||||
* @return bool Whether cleanup was performed.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function maybe_cleanup_data_dir( $type, $force ) {
|
||||
WP_Filesystem();
|
||||
|
||||
|
|
@ -234,6 +346,15 @@ class Data_Manager {
|
|||
$result = $result && self::generate_restricted_htaccess( $directory );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after a data directory cleanup operation.
|
||||
*
|
||||
* @param bool $result Whether the cleanup was successful
|
||||
* @param string $type The type of data that was cleaned up
|
||||
* @param int $total_size The total size of the data before cleanup
|
||||
* @param bool $force Whether the cleanup was forced
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_cleanup', $result, $type, $total_size, $force );
|
||||
|
||||
return $result;
|
||||
|
|
@ -242,6 +363,14 @@ class Data_Manager {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe cleanup update from remote locks
|
||||
*
|
||||
* Clean up expired remote update locks from the database.
|
||||
*
|
||||
* @return bool Whether cleanup was performed.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function maybe_cleanup_update_from_remote_locks() {
|
||||
$locks = get_option( 'upserv_update_from_remote_locks' );
|
||||
|
||||
|
|
@ -258,6 +387,17 @@ class Data_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create data directory
|
||||
*
|
||||
* Create a directory for storing plugin data.
|
||||
*
|
||||
* @param string $name The name of the directory to create.
|
||||
* @param bool $include_htaccess Whether to create an .htaccess file.
|
||||
* @param bool $is_root_dir Whether this is the root data directory.
|
||||
* @return bool Whether the directory was created successfully.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function create_data_dir( $name, $include_htaccess = true, $is_root_dir = false ) {
|
||||
$wp_upload_dir = wp_upload_dir();
|
||||
$root_dir = trailingslashit( $wp_upload_dir['basedir'] . '/updatepulse-server' );
|
||||
|
|
@ -271,6 +411,15 @@ class Data_Manager {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate restricted htaccess
|
||||
*
|
||||
* Create an .htaccess file that prevents direct access to files.
|
||||
*
|
||||
* @param string $directory The directory path where to create the .htaccess file.
|
||||
* @return bool Whether the .htaccess file was created successfully.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function generate_restricted_htaccess( $directory ) {
|
||||
WP_Filesystem();
|
||||
|
||||
|
|
@ -288,6 +437,13 @@ class Data_Manager {
|
|||
return $wp_filesystem->put_contents( $htaccess, $contents, 0644 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cleanup schedules
|
||||
*
|
||||
* Unschedule all cleanup events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function clear_cleanup_schedules() {
|
||||
|
||||
if ( upserv_is_doing_update_api_request() ) {
|
||||
|
|
@ -304,10 +460,26 @@ class Data_Manager {
|
|||
}
|
||||
|
||||
Scheduler::get_instance()->unschedule_all_actions( 'upserv_cleanup', $params );
|
||||
|
||||
/**
|
||||
* Fired after a cleanup schedule has been cleared.
|
||||
*
|
||||
* @param string $type The type of data for which the schedule was cleared
|
||||
* @param array $params The parameters that were used for the schedule
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_cleared_cleanup_schedule', $type, $params );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register cleanup schedules
|
||||
*
|
||||
* Register action hooks for cleanup events.
|
||||
*
|
||||
* @return bool Whether the schedules were registered successfully.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function register_cleanup_schedules() {
|
||||
|
||||
if ( upserv_is_doing_update_api_request() ) {
|
||||
|
|
@ -326,10 +498,25 @@ class Data_Manager {
|
|||
$hook = array( __NAMESPACE__ . '\\Data_Manager', 'maybe_cleanup' );
|
||||
|
||||
add_action( 'upserv_cleanup', $hook, 10, 2 );
|
||||
|
||||
/**
|
||||
* Fired after a cleanup schedule has been registered.
|
||||
*
|
||||
* @param string $type The type of data for which the schedule was registered
|
||||
* @param array $params The parameters that are used for the schedule
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_registered_cleanup_schedule', $type, $params );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register cleanup events
|
||||
*
|
||||
* Schedule recurring cleanup events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function register_cleanup_events() {
|
||||
$cleanable_datatypes = array_merge( self::$transient_data_dirs, self::$transient_data_db );
|
||||
|
||||
|
|
@ -342,6 +529,14 @@ class Data_Manager {
|
|||
}
|
||||
|
||||
if ( ! Scheduler::get_instance()->has_scheduled_action( $hook, $params ) ) {
|
||||
/**
|
||||
* Filter the cleanup schedule frequency.
|
||||
*
|
||||
* @param string $frequency The frequency of the cleanup schedule (default 'hourly')
|
||||
* @param string $type The type of data to clean up
|
||||
* @return string The filtered frequency
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$frequency = apply_filters( 'upserv_schedule_cleanup_frequency', 'hourly', $type );
|
||||
$schedules = wp_get_schedules();
|
||||
$timestamp = time();
|
||||
|
|
@ -352,6 +547,17 @@ class Data_Manager {
|
|||
$params
|
||||
);
|
||||
|
||||
/**
|
||||
* Fired after a cleanup event has been scheduled.
|
||||
*
|
||||
* @param bool $result Whether the scheduling was successful
|
||||
* @param string $type The type of data for which the event was scheduled
|
||||
* @param int $timestamp The timestamp at which the event will first run
|
||||
* @param string $frequency The frequency of the scheduled event
|
||||
* @param string $hook The hook that will be triggered
|
||||
* @param array $params The parameters that will be passed to the hook
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action(
|
||||
'upserv_scheduled_cleanup_event',
|
||||
$result,
|
||||
|
|
|
|||
|
|
@ -13,13 +13,48 @@ use Anyape\UpdatePulse\Server\Table\Licenses_Table;
|
|||
use Anyape\UpdatePulse\Server\Server\License\License_Server;
|
||||
use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
|
||||
|
||||
/**
|
||||
* License Manager class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class License_Manager {
|
||||
|
||||
/**
|
||||
* Licenses table
|
||||
*
|
||||
* @var Licenses_Table|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $licenses_table;
|
||||
/**
|
||||
* Message to display
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $message = '';
|
||||
/**
|
||||
* Error messages
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $errors = array();
|
||||
/**
|
||||
* License server instance
|
||||
*
|
||||
* @var License_Server|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $license_server;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boolean $init_hooks Whether to initialize hooks
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $init_hooks = false ) {
|
||||
|
||||
if ( $init_hooks ) {
|
||||
|
|
@ -53,6 +88,13 @@ class License_Manager {
|
|||
|
||||
// WordPress hooks ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Activate license system
|
||||
*
|
||||
* Creates necessary database tables and sets up license expiration schedule.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function activate() {
|
||||
$result = self::maybe_create_or_upgrade_db();
|
||||
|
||||
|
|
@ -63,21 +105,49 @@ class License_Manager {
|
|||
}
|
||||
|
||||
$manager = new self();
|
||||
/**
|
||||
* Filter the frequency at which the license maintenance task runs.
|
||||
*
|
||||
* @param string $frequency The WordPress schedule frequency (hourly, daily, etc.)
|
||||
*/
|
||||
$frequency = apply_filters( 'upserv_schedule_license_frequency', 'hourly' );
|
||||
|
||||
$manager->register_license_schedules( $frequency );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate license system
|
||||
*
|
||||
* Removes scheduled license expiration tasks.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function deactivate() {
|
||||
Scheduler::get_instance()->unschedule_all_actions( 'upserv_expire_licenses' );
|
||||
|
||||
/**
|
||||
* Fired after the license maintenance event has been unscheduled.
|
||||
*/
|
||||
do_action( 'upserv_cleared_license_schedule' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize scheduler
|
||||
*
|
||||
* Sets up recurring schedule for license expiration checks.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_scheduler_init() {
|
||||
$hook = 'upserv_expire_licenses';
|
||||
|
||||
if ( ! Scheduler::get_instance()->has_scheduled_action( $hook ) ) {
|
||||
$frequency = apply_filters( 'upserv_schedule_license_frequency', 'daily' );
|
||||
/**
|
||||
* Filter the frequency at which the license maintenance task runs.
|
||||
*
|
||||
* @param string $frequency The WordPress schedule frequency (daily, etc.)
|
||||
*/
|
||||
$frequency = apply_filters( 'upserv_schedule_license_frequency', 'hourly' );
|
||||
$schedules = wp_get_schedules();
|
||||
$d = new DateTime( 'now', new DateTimeZone( wp_timezone_string() ) );
|
||||
|
||||
|
|
@ -90,12 +160,27 @@ class License_Manager {
|
|||
$hook
|
||||
);
|
||||
|
||||
/**
|
||||
* Fired after the license maintenance event has been scheduled.
|
||||
*
|
||||
* @param bool $result Whether the event was scheduled successfully
|
||||
* @param int $timestamp Timestamp for when to run the event the first time
|
||||
* @param string $frequency Frequency at which the event would be ran
|
||||
* @param string $hook Event hook to fire when the event is ran
|
||||
*/
|
||||
do_action( 'upserv_scheduled_license_event', $result, $timestamp, $frequency, $hook );
|
||||
}
|
||||
|
||||
$this->register_license_schedules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize admin area
|
||||
*
|
||||
* Sets up license table and processes form submissions.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function admin_init() {
|
||||
|
||||
if ( ! is_admin() || wp_doing_ajax() || wp_doing_cron() ) {
|
||||
|
|
@ -188,6 +273,15 @@ class License_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin styles
|
||||
*
|
||||
* Adds license-specific styles to the admin area.
|
||||
*
|
||||
* @param array $styles Existing admin styles
|
||||
* @return array Modified admin styles
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_styles( $styles ) {
|
||||
$styles['license'] = array(
|
||||
'path' => UPSERV_PLUGIN_PATH . 'css/admin/license' . upserv_assets_suffix() . '.css',
|
||||
|
|
@ -199,6 +293,15 @@ class License_Manager {
|
|||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin scripts
|
||||
*
|
||||
* Adds license-specific scripts to the admin area.
|
||||
*
|
||||
* @param array $scripts Existing admin scripts
|
||||
* @return array Modified admin scripts
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_scripts( $scripts ) {
|
||||
$page = ! empty( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
|
|
@ -221,6 +324,12 @@ class License_Manager {
|
|||
'params' => array(
|
||||
'cm_settings' => wp_enqueue_code_editor( array( 'type' => 'text/json' ) ),
|
||||
),
|
||||
/**
|
||||
* Filter the internationalization strings passed to the frontend scripts.
|
||||
*
|
||||
* @param array $l10n The internationalization strings passed to the frontend scripts
|
||||
* @param string $handle The handle of the script
|
||||
*/
|
||||
'l10n' => apply_filters( 'upserv_scripts_l10n', $l10n, 'license' ),
|
||||
);
|
||||
|
||||
|
|
@ -229,6 +338,13 @@ class License_Manager {
|
|||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add page options
|
||||
*
|
||||
* Adds screen options for the licenses page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_page_options() {
|
||||
$option = 'per_page';
|
||||
$args = array(
|
||||
|
|
@ -240,22 +356,61 @@ class License_Manager {
|
|||
add_screen_option( $option, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save page options
|
||||
*
|
||||
* Handles saving of screen options.
|
||||
*
|
||||
* @param mixed $status Status of the option
|
||||
* @param string $option Option name
|
||||
* @param mixed $value Option value
|
||||
* @return mixed Filtered option value
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function set_page_options( $status, $option, $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add package table columns
|
||||
*
|
||||
* Adds license columns to the packages table.
|
||||
*
|
||||
* @param array $columns Existing table columns
|
||||
* @return array Modified table columns
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_packages_table_columns( $columns ) {
|
||||
$columns['col_use_license'] = __( 'License status', 'updatepulse-server' );
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add sortable columns
|
||||
*
|
||||
* Adds sortable license columns to the packages table.
|
||||
*
|
||||
* @param array $columns Existing sortable columns
|
||||
* @return array Modified sortable columns
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_packages_table_sortable_columns( $columns ) {
|
||||
$columns['col_use_license'] = __( 'License status', 'updatepulse-server' );
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display package table cell content
|
||||
*
|
||||
* Populates license status cells in the packages table.
|
||||
*
|
||||
* @param string $column_name Name of the column
|
||||
* @param array $record Record data
|
||||
* @param string $record_key Record identifier
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_packages_table_cell( $column_name, $record, $record_key ) {
|
||||
$use_license = upserv_is_package_require_license( $record_key );
|
||||
|
||||
|
|
@ -264,6 +419,13 @@ class License_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin menu
|
||||
*
|
||||
* Registers the licenses submenu page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function admin_menu() {
|
||||
$function = array( $this, 'plugin_page' );
|
||||
$page_title = __( 'UpdatePulse Server - Licenses', 'updatepulse-server' );
|
||||
|
|
@ -275,6 +437,15 @@ class License_Manager {
|
|||
add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin tab links
|
||||
*
|
||||
* Adds licenses tab to the admin navigation.
|
||||
*
|
||||
* @param array $links Existing tab links
|
||||
* @return array Modified tab links
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_tab_links( $links ) {
|
||||
$links['licenses'] = array(
|
||||
admin_url( 'admin.php?page=upserv-page-licenses' ),
|
||||
|
|
@ -284,6 +455,16 @@ class License_Manager {
|
|||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage admin tab states
|
||||
*
|
||||
* Updates active state for the licenses tab.
|
||||
*
|
||||
* @param array $states Current 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['licenses'] = 'upserv-page-licenses' === $page;
|
||||
|
||||
|
|
@ -292,17 +473,44 @@ class License_Manager {
|
|||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Expire licenses
|
||||
*
|
||||
* Changes status of expired licenses.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function expire_licenses() {
|
||||
$this->license_server->switch_expired_licenses_status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register license schedules
|
||||
*
|
||||
* Sets up hooks for scheduled license tasks.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_license_schedules() {
|
||||
$scheduled_hook = array( $this, 'expire_licenses' );
|
||||
|
||||
add_action( 'upserv_expire_licenses', $scheduled_hook, 10, 2 );
|
||||
|
||||
/**
|
||||
* Fired after the license maintenance action has been registered.
|
||||
*
|
||||
* @param string $scheduled_hook The license event hook that has been registered
|
||||
*/
|
||||
do_action( 'upserv_registered_license_schedule', $scheduled_hook );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display plugin page
|
||||
*
|
||||
* Renders the licenses management page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function plugin_page() {
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
|
|
@ -349,6 +557,14 @@ class License_Manager {
|
|||
* Protected methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Create or upgrade database
|
||||
*
|
||||
* Sets up and updates the licenses database table.
|
||||
*
|
||||
* @return bool Whether database setup was successful
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function maybe_create_or_upgrade_db() {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -398,6 +614,14 @@ class License_Manager {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle plugin options
|
||||
*
|
||||
* Processes and saves plugin settings.
|
||||
*
|
||||
* @return string|bool Success message or false on failure
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function plugin_options_handler() {
|
||||
$errors = array();
|
||||
$result = '';
|
||||
|
|
@ -456,7 +680,20 @@ class License_Manager {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get submitted options
|
||||
*
|
||||
* Retrieves and validates options from form submission.
|
||||
*
|
||||
* @return array Options array with validation parameters
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_submitted_options() {
|
||||
/**
|
||||
* Filter the submitted license configuration values before saving them.
|
||||
*
|
||||
* @param array $config The submitted license configuration values
|
||||
*/
|
||||
return apply_filters(
|
||||
'upserv_submitted_licenses_config',
|
||||
array(
|
||||
|
|
@ -470,6 +707,15 @@ class License_Manager {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change license statuses in bulk
|
||||
*
|
||||
* Updates status for multiple licenses at once.
|
||||
*
|
||||
* @param string $status New status to apply
|
||||
* @param array $license_data Licenses to update
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function change_license_statuses_bulk( $status, $license_data ) {
|
||||
|
||||
if ( ! is_array( $license_data ) ) {
|
||||
|
|
@ -524,6 +770,15 @@ class License_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete licenses in bulk
|
||||
*
|
||||
* Removes multiple licenses from the system.
|
||||
*
|
||||
* @param array $license_ids IDs of licenses to delete
|
||||
* @return array IDs of deleted licenses
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function delete_license_bulk( $license_ids ) {
|
||||
|
||||
if ( ! is_array( $license_ids ) ) {
|
||||
|
|
@ -550,6 +805,14 @@ class License_Manager {
|
|||
return $license_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a license
|
||||
*
|
||||
* Updates an existing license with new data.
|
||||
*
|
||||
* @param string $license_data JSON encoded license data
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function update_license( $license_data ) {
|
||||
$payload = json_decode( $license_data, true );
|
||||
$payload['data'] = json_decode( $payload['data'], true );
|
||||
|
|
@ -564,6 +827,14 @@ class License_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new license
|
||||
*
|
||||
* Adds a new license to the system.
|
||||
*
|
||||
* @param string $license_data JSON encoded license data
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function create_license( $license_data ) {
|
||||
$payload = json_decode( $license_data, true );
|
||||
$payload['data'] = json_decode( $payload['data'], true );
|
||||
|
|
@ -578,6 +849,13 @@ class License_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all licenses
|
||||
*
|
||||
* Removes all licenses from the system.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function delete_all_licenses() {
|
||||
$this->license_server->purge_licenses();
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -11,8 +11,19 @@ use Anyape\UpdatePulse\Server\Manager\Data_Manager;
|
|||
use Anyape\UpdatePulse\Server\API\Update_API;
|
||||
use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
|
||||
|
||||
/**
|
||||
* Remote Sources Manager class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Remote_Sources_Manager {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boolean $init_hooks Whether to initialize WordPress hooks.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $init_hooks = false ) {
|
||||
|
||||
if ( $init_hooks ) {
|
||||
|
|
@ -40,14 +51,35 @@ class Remote_Sources_Manager {
|
|||
|
||||
// WordPress hooks ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Activate
|
||||
*
|
||||
* Register schedules when the plugin is activated.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function activate() {
|
||||
self::register_schedules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate
|
||||
*
|
||||
* Clear schedules when the plugin is deactivated.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function deactivate() {
|
||||
self::clear_schedules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin scripts
|
||||
*
|
||||
* @param array $scripts List of scripts to enqueue.
|
||||
* @return array Modified list of scripts.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_scripts( $scripts ) {
|
||||
$page = ! empty( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
|
|
@ -64,6 +96,13 @@ class Remote_Sources_Manager {
|
|||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin styles
|
||||
*
|
||||
* @param array $styles List of styles to enqueue.
|
||||
* @return array Modified list of styles.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_styles( $styles ) {
|
||||
$styles['remote_sources'] = array(
|
||||
'path' => UPSERV_PLUGIN_PATH . 'css/admin/remote-sources' . upserv_assets_suffix() . '.css',
|
||||
|
|
@ -73,6 +112,11 @@ class Remote_Sources_Manager {
|
|||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register remote check scheduled hooks
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_remote_check_scheduled_hooks() {
|
||||
|
||||
if ( upserv_is_doing_update_api_request() ) {
|
||||
|
|
@ -89,7 +133,7 @@ class Remote_Sources_Manager {
|
|||
|
||||
foreach ( $vcs_configs as $vcs_c ) {
|
||||
|
||||
if ( $vcs_c['use_webhooks'] || ! isset( $vcs_c['url'] ) ) {
|
||||
if ( ! isset( $vcs_c['url'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +148,15 @@ class Remote_Sources_Manager {
|
|||
|
||||
foreach ( $slugs as $slug ) {
|
||||
add_action( 'upserv_check_remote_' . $slug, $action_hook, 10, 3 );
|
||||
|
||||
/**
|
||||
* Fired after a remote check action has been registered for a package.
|
||||
* Fired during client update API request.
|
||||
*
|
||||
* @param string $package_slug The slug of the package for which an action has been registered
|
||||
* @param string $scheduled_hook The event hook the action has been registered to
|
||||
* @param string $action_hook The action that has been registered
|
||||
*/
|
||||
do_action(
|
||||
'upserv_registered_check_remote_schedule',
|
||||
$slug,
|
||||
|
|
@ -114,6 +167,13 @@ class Remote_Sources_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear remote check scheduled hooks
|
||||
*
|
||||
* @param array|null $vcs_configs VCS configurations.
|
||||
* @return bool True on success, false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function clear_remote_check_scheduled_hooks( $vcs_configs = null ) {
|
||||
|
||||
if ( upserv_is_doing_update_api_request() ) {
|
||||
|
|
@ -144,6 +204,14 @@ class Remote_Sources_Manager {
|
|||
$scheduled_hook = 'upserv_check_remote_' . $slug;
|
||||
|
||||
Scheduler::get_instance()->unschedule_all_actions( $scheduled_hook );
|
||||
|
||||
/**
|
||||
* Fired after a remote check schedule event has been unscheduled for a package.
|
||||
* Fired during client update API request.
|
||||
*
|
||||
* @param string $package_slug The slug of the package for which a remote check event has been unscheduled
|
||||
* @param string $scheduled_hook The remote check event hook that has been unscheduled
|
||||
*/
|
||||
do_action( 'upserv_cleared_check_remote_schedule', $slug, $scheduled_hook );
|
||||
}
|
||||
}
|
||||
|
|
@ -151,6 +219,11 @@ class Remote_Sources_Manager {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin menu
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function admin_menu() {
|
||||
$function = array( $this, 'plugin_page' );
|
||||
$page_title = __( 'UpdatePulse Server - Version Control Systems ', 'updatepulse-server' );
|
||||
|
|
@ -160,6 +233,13 @@ class Remote_Sources_Manager {
|
|||
add_submenu_page( 'upserv-page', $page_title, $menu_title, 'manage_options', $menu_slug, $function );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin tab links
|
||||
*
|
||||
* @param array $links List of admin tab links.
|
||||
* @return array Modified list of admin tab links.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_tab_links( $links ) {
|
||||
$links['remote-sources'] = array(
|
||||
admin_url( 'admin.php?page=upserv-page-remote-sources' ),
|
||||
|
|
@ -169,12 +249,25 @@ class Remote_Sources_Manager {
|
|||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin tab states
|
||||
*
|
||||
* @param array $states List of admin tab states.
|
||||
* @param string $page Current admin page.
|
||||
* @return array Modified list of admin tab states.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function upserv_admin_tab_states( $states, $page ) {
|
||||
$states['remote-sources'] = 'upserv-page-remote-sources' === $page;
|
||||
|
||||
return $states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force clean
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function force_clean() {
|
||||
$result = false;
|
||||
$type = false;
|
||||
|
|
@ -225,6 +318,11 @@ class Remote_Sources_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* VCS test
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function vcs_test() {
|
||||
$result = false;
|
||||
|
||||
|
|
@ -301,12 +399,23 @@ class Remote_Sources_Manager {
|
|||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Clear schedules
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function clear_schedules() {
|
||||
$manager = new self();
|
||||
|
||||
return $manager->clear_remote_check_scheduled_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register schedules
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function register_schedules() {
|
||||
$options = get_option( 'upserv_options' );
|
||||
$options = json_decode( $options, true );
|
||||
|
|
@ -329,6 +438,13 @@ class Remote_Sources_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule remote check recurring events
|
||||
*
|
||||
* @param array $vcs_c VCS configuration.
|
||||
* @return bool True on success, false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function reschedule_remote_check_recurring_events( $vcs_c ) {
|
||||
|
||||
if (
|
||||
|
|
@ -350,6 +466,13 @@ class Remote_Sources_Manager {
|
|||
$type = isset( $meta['type'] ) ? $meta['type'] : null;
|
||||
$hook = 'upserv_check_remote_' . $slug;
|
||||
$params = array( $slug, $type, false );
|
||||
|
||||
/**
|
||||
* Filter the frequency at which remote checks for updates are performed for a package.
|
||||
*
|
||||
* @param string $frequency The frequency at which remote checks are performed
|
||||
* @param string $package_slug The slug of the package
|
||||
*/
|
||||
$frequency = apply_filters(
|
||||
'upserv_check_remote_frequency',
|
||||
isset( $vcs_c['check_frequency'] ) ? $vcs_c['check_frequency'] : 'daily',
|
||||
|
|
@ -359,6 +482,14 @@ class Remote_Sources_Manager {
|
|||
$schedules = wp_get_schedules();
|
||||
|
||||
Scheduler::get_instance()->unschedule_all_actions( $hook, $params );
|
||||
|
||||
/**
|
||||
* Fired after a remote check schedule event has been unscheduled for a package.
|
||||
* Fired during client update API request.
|
||||
*
|
||||
* @param string $package_slug The slug of the package for which a remote check event has been unscheduled
|
||||
* @param string $scheduled_hook The remote check event hook that has been unscheduled
|
||||
*/
|
||||
do_action( 'upserv_cleared_check_remote_schedule', $slug, $hook );
|
||||
|
||||
$result = Scheduler::get_instance()->schedule_recurring_action(
|
||||
|
|
@ -368,6 +499,17 @@ class Remote_Sources_Manager {
|
|||
$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 The 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
|
||||
*/
|
||||
do_action(
|
||||
'upserv_scheduled_check_remote_event',
|
||||
$result,
|
||||
|
|
@ -382,6 +524,11 @@ class Remote_Sources_Manager {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin page
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function plugin_page() {
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
|
|
@ -419,6 +566,12 @@ class Remote_Sources_Manager {
|
|||
* Protected methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Plugin options handler
|
||||
*
|
||||
* @return array|string Result of the options update.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function plugin_options_handler() {
|
||||
$errors = array();
|
||||
$result = '';
|
||||
|
|
@ -454,6 +607,14 @@ class Remote_Sources_Manager {
|
|||
$option_info['value'] = (bool) $option_info['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter whether to update the remote source option.
|
||||
*
|
||||
* @param bool $condition Whether to update the option
|
||||
* @param string $option_name The name of the option
|
||||
* @param array $option_info Information about the option
|
||||
* @param array $options All submitted options
|
||||
*/
|
||||
$condition = apply_filters(
|
||||
'upserv_remote_source_option_update',
|
||||
$condition,
|
||||
|
|
@ -463,6 +624,14 @@ class Remote_Sources_Manager {
|
|||
);
|
||||
|
||||
if ( $condition ) {
|
||||
/**
|
||||
* Filter the value of the remote source option before saving it.
|
||||
*
|
||||
* @param mixed $value The value of the option
|
||||
* @param string $option_name The name of the option
|
||||
* @param array $option_info Information about the option
|
||||
* @param array $options All submitted options
|
||||
*/
|
||||
$to_save[ $option_info['path'] ] = apply_filters(
|
||||
'upserv_remote_sources_option_save_value',
|
||||
$option_info['value'],
|
||||
|
|
@ -567,11 +736,26 @@ class Remote_Sources_Manager {
|
|||
}
|
||||
|
||||
set_transient( 'upserv_flush', 1, 60 );
|
||||
|
||||
/**
|
||||
* Fired after the options in "Remote Sources" have been updated.
|
||||
*
|
||||
* @param array|string $result The result of the options update, an array of errors or a success message
|
||||
*/
|
||||
do_action( 'upserv_remote_sources_options_updated', $result );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter JSON input
|
||||
*
|
||||
* @param array $inputs JSON input data.
|
||||
* @param string $option_name Option name.
|
||||
* @param array $errors List of errors.
|
||||
* @return array Filtered JSON input data.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function filter_json_input( $inputs, $option_name, &$errors ) {
|
||||
$filtered = array();
|
||||
$index = 0;
|
||||
|
|
@ -642,7 +826,19 @@ class Remote_Sources_Manager {
|
|||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get submitted options
|
||||
*
|
||||
* @return array List of submitted options.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_submitted_options() {
|
||||
/**
|
||||
* Filter the submitted remote sources configuration values before using them.
|
||||
*
|
||||
* @param array $config The submitted remote sources configuration values
|
||||
* @return array The filtered configuration
|
||||
*/
|
||||
return apply_filters(
|
||||
'upserv_submitted_remote_sources_config',
|
||||
array(
|
||||
|
|
@ -663,6 +859,13 @@ class Remote_Sources_Manager {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package slugs
|
||||
*
|
||||
* @param string $vcs_url VCS URL.
|
||||
* @return array List of package slugs.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_package_slugs( $vcs_url ) {
|
||||
$slugs = wp_cache_get( 'package_slugs', 'updatepulse-server' );
|
||||
|
||||
|
|
@ -682,7 +885,7 @@ class Remote_Sources_Manager {
|
|||
}
|
||||
}
|
||||
|
||||
if ( empty( $slugs ) ) {
|
||||
if ( ! empty( $slugs ) ) {
|
||||
|
||||
foreach ( $slugs as $idx => $slug ) {
|
||||
$meta = upserv_get_package_metadata( $slug );
|
||||
|
|
|
|||
|
|
@ -6,19 +6,61 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
use WP_Error;
|
||||
use ZipArchive;
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use FilesystemIterator;
|
||||
use Anyape\Utils\Utils;
|
||||
|
||||
/**
|
||||
* Zip Package Manager class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Zip_Package_Manager {
|
||||
|
||||
/**
|
||||
* Package slug
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $package_slug;
|
||||
|
||||
/**
|
||||
* Path to the received package
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $received_package_path;
|
||||
|
||||
/**
|
||||
* Temporary directory path
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $tmp_dir;
|
||||
|
||||
/**
|
||||
* Packages directory path
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $packages_dir;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $package_slug The package slug.
|
||||
* @param string $received_package_path Path to the received package.
|
||||
* @param string $tmp_dir Temporary directory path.
|
||||
* @param string $packages_dir Packages directory path.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $package_slug, $received_package_path, $tmp_dir, $packages_dir ) {
|
||||
$this->package_slug = $package_slug;
|
||||
$this->received_package_path = $received_package_path;
|
||||
|
|
@ -30,10 +72,31 @@ class Zip_Package_Manager {
|
|||
* Public methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Unzip package
|
||||
*
|
||||
* Extract a zip package to a destination.
|
||||
*
|
||||
* @param string $source Path to the source zip file.
|
||||
* @param string $destination Path to the destination directory.
|
||||
* @return bool|WP_Error True on success, WP_Error on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function unzip_package( $source, $destination ) {
|
||||
return unzip_file( $source, $destination );
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip package
|
||||
*
|
||||
* Create a zip archive from a directory or file.
|
||||
*
|
||||
* @param string $source Path to the source directory or file.
|
||||
* @param string $destination Path to the destination zip file.
|
||||
* @param string $container_dir Optional container directory within the zip.
|
||||
* @return bool Whether the zip creation was successful.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function zip_package( $source, $destination, $container_dir = '' ) {
|
||||
$zip = new ZipArchive();
|
||||
|
||||
|
|
@ -86,6 +149,14 @@ class Zip_Package_Manager {
|
|||
return $zip->close() && file_exists( $destination );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean package
|
||||
*
|
||||
* Clean the received package by moving and repacking it.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function clean_package() {
|
||||
WP_Filesystem();
|
||||
|
||||
|
|
@ -94,7 +165,7 @@ class Zip_Package_Manager {
|
|||
$return = true;
|
||||
$error_message = __METHOD__ . ': ';
|
||||
|
||||
if ( is_wp_error( $this->received_package_path ) ) {
|
||||
if ( $this->received_package_path instanceof WP_Error ) {
|
||||
$return = false;
|
||||
$error_message .= $this->received_package_path->get_error_message();
|
||||
}
|
||||
|
|
@ -169,6 +240,14 @@ class Zip_Package_Manager {
|
|||
* Protected methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Repack package
|
||||
*
|
||||
* Repack the received package by unzipping and zipping it again.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function repack_package() {
|
||||
WP_Filesystem();
|
||||
|
||||
|
|
@ -207,6 +286,14 @@ class Zip_Package_Manager {
|
|||
|
||||
$wp_filesystem->chmod( $temp_path, false, true );
|
||||
|
||||
/**
|
||||
* Fired before packing the files received from the Version Control System. Can be used for extra files manipulation.
|
||||
* Fired during client update API request.
|
||||
*
|
||||
* @param string $package_slug The slug of the package.
|
||||
* @param string $files_path The path of the directory where the package files are located.
|
||||
* @param string $archive_path The path where the package archive will be located after packing.
|
||||
*/
|
||||
do_action( 'upserv_before_remote_package_zip', $this->package_slug, $temp_path, $archive_path );
|
||||
|
||||
$zipped = self::zip_package( $temp_path, $archive_path );
|
||||
|
|
|
|||
|
|
@ -12,25 +12,91 @@ use PasswordHash;
|
|||
use Anyape\Utils\Utils;
|
||||
use Anyape\UpdatePulse\Server\Scheduler\Scheduler;
|
||||
|
||||
/**
|
||||
* Nonce class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Nonce {
|
||||
|
||||
/**
|
||||
* Default expiry length
|
||||
*
|
||||
* Default time in seconds before a nonce expires.
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
const DEFAULT_EXPIRY_LENGTH = MINUTE_IN_SECONDS / 2;
|
||||
/**
|
||||
* Nonce only return type
|
||||
*
|
||||
* Constant indicating to return just the nonce string.
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
const NONCE_ONLY = 1;
|
||||
/**
|
||||
* Nonce info array return type
|
||||
*
|
||||
* Constant indicating to return the nonce with additional information.
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
const NONCE_INFO_ARRAY = 2;
|
||||
|
||||
/**
|
||||
* True nonce flag
|
||||
*
|
||||
* Indicates if a nonce is a true nonce.
|
||||
*
|
||||
* @var bool|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $true_nonce;
|
||||
/**
|
||||
* Expiry length
|
||||
*
|
||||
* Time in seconds before a nonce expires.
|
||||
*
|
||||
* @var int|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $expiry_length;
|
||||
/**
|
||||
* API request flag
|
||||
*
|
||||
* Indicates if the current request is an API request.
|
||||
*
|
||||
* @var bool|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $doing_api_request = null;
|
||||
/**
|
||||
* Private keys
|
||||
*
|
||||
* Array of private keys used for authentication.
|
||||
*
|
||||
* @var array|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $private_keys;
|
||||
|
||||
/*******************************************************************
|
||||
* Public methods
|
||||
*******************************************************************/
|
||||
|
||||
// API action --------------------------------------------------
|
||||
|
||||
// WordPress hooks ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Activate
|
||||
*
|
||||
* Setup necessary database tables on plugin activation.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function activate() {
|
||||
$result = self::maybe_create_or_upgrade_db();
|
||||
|
||||
|
|
@ -41,12 +107,33 @@ class Nonce {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate
|
||||
*
|
||||
* Clean up scheduled actions on plugin deactivation.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function deactivate() {
|
||||
Scheduler::get_instance()->unschedule_all_actions( 'upserv_nonce_cleanup' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall
|
||||
*
|
||||
* Placeholder for uninstall logic.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function uninstall() {}
|
||||
|
||||
/**
|
||||
* Initialize scheduler
|
||||
*
|
||||
* Schedule recurring actions for nonce cleanup.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function upserv_scheduler_init() {
|
||||
|
||||
if ( Scheduler::get_instance()->has_scheduled_action( 'upserv_nonce_cleanup' ) ) {
|
||||
|
|
@ -63,6 +150,13 @@ class Nonce {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add endpoints
|
||||
*
|
||||
* Add rewrite rules for nonce and token endpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function add_endpoints() {
|
||||
add_rewrite_rule(
|
||||
'^updatepulse-server-token/*?$',
|
||||
|
|
@ -76,6 +170,13 @@ class Nonce {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse request
|
||||
*
|
||||
* Handle incoming requests to the nonce and token endpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function parse_request() {
|
||||
global $wp;
|
||||
|
||||
|
|
@ -101,12 +202,18 @@ class Nonce {
|
|||
|
||||
unset( $payload['action'] );
|
||||
|
||||
/**
|
||||
* Filter the payload sent to the Nonce API.
|
||||
*
|
||||
* @param array $payload The payload sent to the Nonce API
|
||||
* @param string $method The api action - `token` or `nonce`
|
||||
*/
|
||||
$payload = apply_filters( 'upserv_nonce_api_payload', $payload, $method );
|
||||
|
||||
if (
|
||||
is_string( $wp->query_vars['action'] ) &&
|
||||
method_exists(
|
||||
get_class(),
|
||||
__CLASS__,
|
||||
'generate_' . $wp->query_vars['action'] . '_api_response'
|
||||
)
|
||||
) {
|
||||
|
|
@ -128,12 +235,35 @@ class Nonce {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the HTTP response code to be sent by the Nonce API.
|
||||
*
|
||||
* @param string $code The HTTP response code to be sent by the Nonce API
|
||||
* @param array $request_params The request's parameters
|
||||
*/
|
||||
$code = apply_filters( 'upserv_nonce_api_code', $code, $wp->query_vars );
|
||||
|
||||
/**
|
||||
* Filter the response to be sent by the Nonce API.
|
||||
*
|
||||
* @param array $response The response to be sent by the Nonce API
|
||||
* @param string $code The HTTP response code sent by the Nonce API
|
||||
* @param array $request_params The request's parameters
|
||||
*/
|
||||
$response = apply_filters( 'upserv_nonce_api_response', $response, $code, $wp->query_vars );
|
||||
|
||||
wp_send_json( $response, $code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add query vars
|
||||
*
|
||||
* Add custom query variables for nonce and token endpoints.
|
||||
*
|
||||
* @param array $query_vars Existing query variables.
|
||||
* @return array Modified query variables.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function query_vars( $query_vars ) {
|
||||
$query_vars = array_merge(
|
||||
$query_vars,
|
||||
|
|
@ -150,10 +280,16 @@ class Nonce {
|
|||
return $query_vars;
|
||||
}
|
||||
|
||||
// Overrides ---------------------------------------------------
|
||||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create or upgrade database
|
||||
*
|
||||
* Create or upgrade the necessary database tables.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function maybe_create_or_upgrade_db() {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -191,23 +327,46 @@ class Nonce {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks
|
||||
*
|
||||
* Register WordPress hooks for the nonce functionality.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function register() {
|
||||
|
||||
if ( ! self::is_doing_api_request() ) {
|
||||
add_action( 'upserv_scheduler_init', array( get_class(), 'upserv_scheduler_init' ) );
|
||||
add_action( 'upserv_nonce_cleanup', array( get_class(), 'upserv_nonce_cleanup' ) );
|
||||
add_action( 'upserv_scheduler_init', array( __CLASS__, 'upserv_scheduler_init' ) );
|
||||
add_action( 'upserv_nonce_cleanup', array( __CLASS__, 'upserv_nonce_cleanup' ) );
|
||||
}
|
||||
|
||||
add_action( 'init', array( get_class(), 'add_endpoints' ) );
|
||||
add_action( 'parse_request', array( get_class(), 'parse_request' ), -99, 0 );
|
||||
add_action( 'init', array( __CLASS__, 'add_endpoints' ) );
|
||||
add_action( 'parse_request', array( __CLASS__, 'parse_request' ), -99, 0 );
|
||||
|
||||
add_filter( 'query_vars', array( get_class(), 'query_vars' ), -99, 1 );
|
||||
add_filter( 'query_vars', array( __CLASS__, 'query_vars' ), -99, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize authentication
|
||||
*
|
||||
* Initialize the private keys used for authentication.
|
||||
*
|
||||
* @param array $private_keys Array of private keys.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function init_auth( $private_keys ) {
|
||||
self::$private_keys = $private_keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if doing API request
|
||||
*
|
||||
* Check if the current request is an API request.
|
||||
*
|
||||
* @return bool True if doing API request, false otherwise.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function is_doing_api_request() {
|
||||
|
||||
if ( null === self::$doing_api_request ) {
|
||||
|
|
@ -217,6 +376,19 @@ class Nonce {
|
|||
return self::$doing_api_request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create nonce
|
||||
*
|
||||
* Create a new nonce.
|
||||
*
|
||||
* @param bool $true_nonce Indicates if the nonce is a true nonce.
|
||||
* @param int $expiry_length Time in seconds before the nonce expires.
|
||||
* @param array $data Additional data to store with the nonce.
|
||||
* @param int $return_type Return type (nonce only or nonce info array).
|
||||
* @param bool $store Indicates if the nonce should be stored in the database.
|
||||
* @return mixed The nonce or nonce info array.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function create_nonce(
|
||||
$true_nonce = true,
|
||||
$expiry_length = self::DEFAULT_EXPIRY_LENGTH,
|
||||
|
|
@ -224,6 +396,17 @@ class Nonce {
|
|||
$return_type = self::NONCE_ONLY,
|
||||
$store = true
|
||||
) {
|
||||
/**
|
||||
* Filter the value of the nonce before it is created; if $nonce_value is truthy,
|
||||
* the value is used as nonce and the default generation algorithm is bypassed;
|
||||
* developers must respect the $return_type.
|
||||
*
|
||||
* @param bool|string|array $nonce_value The value of the nonce before it is created - if truthy, the nonce is considered created with this value
|
||||
* @param bool $true_nonce Whether the nonce is a true, one-time-use nonce
|
||||
* @param int $expiry_length The expiry length of the nonce in seconds
|
||||
* @param array $data Data to store along the nonce
|
||||
* @param int $return_type UPServ_Nonce::NONCE_ONLY or UPServ_Nonce::NONCE_INFO_ARRAY
|
||||
*/
|
||||
$nonce = apply_filters(
|
||||
'upserv_created_nonce',
|
||||
false,
|
||||
|
|
@ -279,6 +462,15 @@ class Nonce {
|
|||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nonce expiry
|
||||
*
|
||||
* Get the expiry time of a nonce.
|
||||
*
|
||||
* @param string $nonce The nonce string.
|
||||
* @return int The expiry time in seconds.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_nonce_expiry( $nonce ) {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -304,6 +496,15 @@ class Nonce {
|
|||
return intval( $nonce_expiry );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nonce data
|
||||
*
|
||||
* Get the data associated with a nonce.
|
||||
*
|
||||
* @param string $nonce The nonce string.
|
||||
* @return array The nonce data.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_nonce_data( $nonce ) {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -329,6 +530,15 @@ class Nonce {
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate nonce
|
||||
*
|
||||
* Validate a nonce.
|
||||
*
|
||||
* @param string $value The nonce string.
|
||||
* @return bool True if the nonce is valid, false otherwise.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function validate_nonce( $value ) {
|
||||
|
||||
if ( empty( $value ) ) {
|
||||
|
|
@ -341,7 +551,15 @@ class Nonce {
|
|||
return $valid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete nonce
|
||||
*
|
||||
* Delete a nonce from the database.
|
||||
*
|
||||
* @param string $value The nonce string.
|
||||
* @return bool True on success, false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function delete_nonce( $value ) {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -352,6 +570,13 @@ class Nonce {
|
|||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nonce cleanup
|
||||
*
|
||||
* Clean up expired nonces from the database.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function upserv_nonce_cleanup() {
|
||||
|
||||
if ( defined( 'WP_SETUP_CONFIG' ) || defined( 'WP_INSTALLING' ) ) {
|
||||
|
|
@ -363,6 +588,8 @@ class Nonce {
|
|||
$sql = "DELETE FROM {$wpdb->prefix}upserv_nonce
|
||||
WHERE expiry < %d
|
||||
AND (
|
||||
JSON_VALID(`data`) = 0
|
||||
OR (
|
||||
JSON_VALID(`data`) = 1
|
||||
AND (
|
||||
JSON_EXTRACT(`data` , '$.permanent') IS NULL
|
||||
|
|
@ -370,10 +597,24 @@ class Nonce {
|
|||
OR JSON_EXTRACT(`data` , '$.permanent') = '0'
|
||||
OR JSON_EXTRACT(`data` , '$.permanent') = false
|
||||
)
|
||||
) OR
|
||||
JSON_VALID(`data`) = 0;";
|
||||
)
|
||||
);";
|
||||
$sql_args = array( time() - self::DEFAULT_EXPIRY_LENGTH );
|
||||
|
||||
/**
|
||||
* Filter the SQL query used to clear expired nonces.
|
||||
*
|
||||
* @param string $sql The SQL query used to clear expired nonces
|
||||
* @param array $sql_args The arguments passed to the SQL query used to clear expired nonces
|
||||
*/
|
||||
$sql = apply_filters( 'upserv_clear_nonces_query', $sql, $sql_args );
|
||||
|
||||
/**
|
||||
* Filter the arguments passed to the SQL query used to clear expired nonces.
|
||||
*
|
||||
* @param array $sql_args The arguments passed to the SQL query used to clear expired nonces
|
||||
* @param string $sql The SQL query used to clear expired nonces
|
||||
*/
|
||||
$sql_args = apply_filters( 'upserv_clear_nonces_query_args', $sql_args, $sql );
|
||||
$result = $wpdb->query( $wpdb->prepare( $sql, $sql_args ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
|
|
@ -386,14 +627,42 @@ class Nonce {
|
|||
|
||||
// API action --------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generate token API response
|
||||
*
|
||||
* Generate a response for the token API endpoint.
|
||||
*
|
||||
* @param array $payload The request payload.
|
||||
* @return array The API response.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function generate_token_api_response( $payload ) {
|
||||
return self::generate_api_response( $payload, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate nonce API response
|
||||
*
|
||||
* Generate a response for the nonce API endpoint.
|
||||
*
|
||||
* @param array $payload The request payload.
|
||||
* @return array The API response.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function generate_nonce_api_response( $payload ) {
|
||||
return self::generate_api_response( $payload, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate API response
|
||||
*
|
||||
* Generate a response for the API endpoint.
|
||||
*
|
||||
* @param array $payload The request payload.
|
||||
* @param bool $is_nonce Indicates if the response is for a nonce.
|
||||
* @return array The API response.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function generate_api_response( $payload, $is_nonce ) {
|
||||
return self::create_nonce(
|
||||
$is_nonce,
|
||||
|
|
@ -407,6 +676,15 @@ class Nonce {
|
|||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch nonce
|
||||
*
|
||||
* Fetch a nonce from the database.
|
||||
*
|
||||
* @param string $value The nonce string.
|
||||
* @return string|null The nonce or null if not found.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function fetch_nonce( $value ) {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -442,6 +720,16 @@ class Nonce {
|
|||
}
|
||||
|
||||
if ( $row->expiry < time() && ! $permanent ) {
|
||||
/**
|
||||
* Filter whether to consider the nonce has expired.
|
||||
*
|
||||
* @param bool $expire_nonce Whether to consider the nonce has expired
|
||||
* @param string $nonce_value The value of the nonce
|
||||
* @param bool $true_nonce Whether the nonce is a true, one-time-use nonce
|
||||
* @param int $expiry The timestamp at which the nonce expires
|
||||
* @param array $data Data stored along the nonce
|
||||
* @param object $row The database record corresponding to the nonce
|
||||
*/
|
||||
$row->nonce = apply_filters(
|
||||
'upserv_expire_nonce',
|
||||
null,
|
||||
|
|
@ -453,6 +741,16 @@ class Nonce {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter whether to delete the nonce.
|
||||
*
|
||||
* @param bool $delete Whether to delete the nonce
|
||||
* @param string $nonce_value The value of the nonce
|
||||
* @param bool $true_nonce Whether the nonce is a true, one-time-use nonce
|
||||
* @param int $expiry The timestamp at which the nonce expires
|
||||
* @param array $data Data stored along the nonce
|
||||
* @param object $row The database record corresponding to the nonce
|
||||
*/
|
||||
$delete_nonce = apply_filters(
|
||||
'upserv_delete_nonce',
|
||||
$row->true_nonce || null === $row->nonce,
|
||||
|
|
@ -466,11 +764,32 @@ class Nonce {
|
|||
self::delete_nonce( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the value of the nonce after it has been fetched from the database.
|
||||
*
|
||||
* @param string $nonce_value The value of the nonce after it has been fetched from the database
|
||||
* @param bool $true_nonce Whether the nonce is a true, one-time-use nonce
|
||||
* @param int $expiry The timestamp at which the nonce expires
|
||||
* @param array $data Data stored along the nonce
|
||||
* @param object $row The database record corresponding to the nonce
|
||||
*/
|
||||
$nonce = apply_filters( 'upserv_fetch_nonce', $row->nonce, $row->true_nonce, $row->expiry, $data, $row );
|
||||
|
||||
return $nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store nonce
|
||||
*
|
||||
* Store a nonce in the database.
|
||||
*
|
||||
* @param string $nonce The nonce string.
|
||||
* @param bool $true_nonce Indicates if the nonce is a true nonce.
|
||||
* @param int $expiry The expiry time in seconds.
|
||||
* @param string $data The nonce data.
|
||||
* @return array|false The stored nonce data or false on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function store_nonce( $nonce, $true_nonce, $expiry, $data ) {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -489,6 +808,14 @@ class Nonce {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ID
|
||||
*
|
||||
* Generate a unique ID.
|
||||
*
|
||||
* @return string The generated ID.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function generate_id() {
|
||||
require_once ABSPATH . 'wp-includes/class-phpass.php';
|
||||
|
||||
|
|
@ -497,6 +824,14 @@ class Nonce {
|
|||
return md5( $hasher->get_random_bytes( 100, false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize request
|
||||
*
|
||||
* Authorize the incoming request using the provided credentials and signature.
|
||||
*
|
||||
* @return bool True if the request is authorized, false otherwise.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function authorize() {
|
||||
$sign = false;
|
||||
$key_id = false;
|
||||
|
|
@ -560,6 +895,13 @@ class Nonce {
|
|||
$auth = hash_equals( $values['signature'], $sign );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter whether the request for a nonce is authorized.
|
||||
*
|
||||
* @param bool $authorized Whether the request is authorized
|
||||
* @param string $received_key The key use to attempt the authorization
|
||||
* @param array $private_auth_keys The valid authorization keys
|
||||
*/
|
||||
return apply_filters(
|
||||
'upserv_nonce_authorize',
|
||||
$auth,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,26 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Scheduler class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Scheduler {
|
||||
/**
|
||||
* Instance
|
||||
*
|
||||
* @var Scheduler|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param boolean $init_hooks Whether to initialize hooks.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $init_hooks = false ) {
|
||||
|
||||
if ( $init_hooks ) {
|
||||
|
|
@ -19,6 +36,14 @@ class Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*
|
||||
* Retrieve or create the Scheduler singleton instance.
|
||||
*
|
||||
* @return Scheduler The scheduler instance.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
if ( ! self::$instance ) {
|
||||
|
|
@ -28,6 +53,16 @@ class Scheduler {
|
|||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method handler
|
||||
*
|
||||
* Routes method calls to either ActionScheduler functions or native WordPress functions.
|
||||
*
|
||||
* @param string $name The method name.
|
||||
* @param array $arguments The method arguments.
|
||||
* @return mixed|WP_Error The result of the method call or error if method doesn't exist.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __call( $name, $arguments ) {
|
||||
|
||||
if ( ! method_exists( $this, $name ) ) {
|
||||
|
|
@ -51,10 +86,24 @@ class Scheduler {
|
|||
return $this->$name( ...$arguments );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action scheduler initialization
|
||||
*
|
||||
* Fires when the Action Scheduler is initialized.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function action_scheduler_init() {
|
||||
do_action( 'upserv_scheduler_init' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* Handles plugin initialization logic.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
if ( ! class_exists( 'ActionScheduler', false ) ) {
|
||||
|
|
@ -62,6 +111,20 @@ class Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule single action
|
||||
*
|
||||
* Schedule a one-time action event.
|
||||
*
|
||||
* @param int $timestamp When the action should run (Unix timestamp).
|
||||
* @param string $hook The hook to execute.
|
||||
* @param array $args Arguments to pass to the hook's callback.
|
||||
* @param string $group The group to assign this action to.
|
||||
* @param bool $unique Whether to ensure this action is unique.
|
||||
* @param int $priority The priority of the action.
|
||||
* @return bool|int The action ID or false if not scheduled.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
|
||||
if ( $unique ) {
|
||||
|
|
@ -71,6 +134,21 @@ class Scheduler {
|
|||
return wp_schedule_single_event( $timestamp, $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule recurring action
|
||||
*
|
||||
* Schedule a repeating action event.
|
||||
*
|
||||
* @param int $timestamp When the action should first run (Unix timestamp).
|
||||
* @param int $interval_in_seconds How long to wait between runs.
|
||||
* @param string $hook The hook to execute.
|
||||
* @param array $args Arguments to pass to the hook's callback.
|
||||
* @param string $group The group to assign this action to.
|
||||
* @param bool $unique Whether to ensure this action is unique.
|
||||
* @param int $priority The priority of the action.
|
||||
* @return bool|int The action ID or false if not scheduled.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
|
||||
if ( $unique ) {
|
||||
|
|
@ -101,6 +179,17 @@ class Scheduler {
|
|||
return wp_schedule_event( $timestamp, $interval, $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule all actions
|
||||
*
|
||||
* Cancel all scheduled instances of a specific action.
|
||||
*
|
||||
* @param string $hook The action hook to unschedule.
|
||||
* @param array $args Args matching those of the action to unschedule.
|
||||
* @param string $group The group to which the action belongs.
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function unschedule_all_actions( $hook, $args = array(), $group = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
$timestamp = wp_next_scheduled( $hook, $args );
|
||||
|
||||
|
|
@ -110,10 +199,32 @@ class Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next scheduled action
|
||||
*
|
||||
* Retrieve the next timestamp for a scheduled action.
|
||||
*
|
||||
* @param string $hook The hook to check.
|
||||
* @param array $args Args matching those of the action to check.
|
||||
* @param string $group The group to which the action belongs.
|
||||
* @return int|false The timestamp for the next occurrence or false if not scheduled.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function next_scheduled_action( $hook, $args = array(), $group = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
return wp_next_scheduled( $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if action is scheduled
|
||||
*
|
||||
* Determine whether an action is currently scheduled.
|
||||
*
|
||||
* @param string $hook The hook to check.
|
||||
* @param array $args Args matching those of the action to check.
|
||||
* @param string $group The group to which the action belongs.
|
||||
* @return bool Whether the action is scheduled.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function has_scheduled_action( $hook, $args = array(), $group = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
return (bool) wp_next_scheduled( $hook, $args );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,19 @@ use WP_Error;
|
|||
use Anyape\Crypto\Crypto;
|
||||
use Anyape\Utils\Utils;
|
||||
|
||||
/**
|
||||
* License Server class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class License_Server {
|
||||
|
||||
/**
|
||||
* License definition template
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $license_definition = array(
|
||||
'id' => 0,
|
||||
'license_key' => '',
|
||||
|
|
@ -32,6 +43,13 @@ class License_Server {
|
|||
'package_type' => '',
|
||||
'data' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Default browsing query settings
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $browsing_query = array(
|
||||
'relationship' => 'AND',
|
||||
'limit' => 999,
|
||||
|
|
@ -39,6 +57,13 @@ class License_Server {
|
|||
'order_by' => 'date_created',
|
||||
'criteria' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Supported browsing operators
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $browsing_operators = array(
|
||||
'=',
|
||||
'!=',
|
||||
|
|
@ -53,6 +78,13 @@ class License_Server {
|
|||
'LIKE',
|
||||
'NOT LIKE',
|
||||
);
|
||||
|
||||
/**
|
||||
* Supported license statuses
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $license_statuses = array(
|
||||
'pending',
|
||||
'activated',
|
||||
|
|
@ -62,12 +94,26 @@ class License_Server {
|
|||
'expired',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
/*******************************************************************
|
||||
* Public methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Build license payload
|
||||
*
|
||||
* Creates a properly structured license payload from input data.
|
||||
*
|
||||
* @param array $payload The raw license data.
|
||||
* @return array The processed license payload.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function build_license_payload( $payload ) {
|
||||
$payload = $this->extend_license_payload( $this->filter_license_payload( $payload ) );
|
||||
|
||||
|
|
@ -76,10 +122,26 @@ class License_Server {
|
|||
return $this->cleanup_license_payload( $payload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse licenses
|
||||
*
|
||||
* Retrieve multiple licenses based on query criteria.
|
||||
*
|
||||
* @param array $payload The query parameters.
|
||||
* @return array|WP_Error Array of licenses or WP_Error on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function browse_licenses( $payload ) {
|
||||
global $wpdb;
|
||||
|
||||
$prepare_args = array();
|
||||
/**
|
||||
* Filter the payload used to browse licenses - before the payload has been cleaned up and the License Query has been validated.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param array $payload A dirty payload for a License Query
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$payload = apply_filters( 'upserv_browse_licenses_payload', $payload );
|
||||
|
||||
try {
|
||||
|
|
@ -138,11 +200,29 @@ class License_Server {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after browsing license records.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param array $licenses The license records retrieved or an empty array
|
||||
* @param array $payload The payload of the request
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_browse_licenses', $licenses, $payload );
|
||||
|
||||
return $licenses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read license
|
||||
*
|
||||
* Retrieve a single license by ID or license key.
|
||||
*
|
||||
* @param array $payload The query parameters containing ID or license key.
|
||||
* @param bool $force Whether to bypass cache.
|
||||
* @return object|WP_Error License object or WP_Error on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function read_license( $payload, $force = false ) {
|
||||
$where_field = isset( $payload['license_key'] ) ? 'license_key' : 'id';
|
||||
$where_value = isset( $payload[ $where_field ] ) ? $payload[ $where_field ] : null;
|
||||
|
|
@ -152,6 +232,13 @@ class License_Server {
|
|||
|
||||
if ( ( $force || ! $found ) && true === $validation ) {
|
||||
$payload = $this->filter_license_payload( $payload );
|
||||
/**
|
||||
* Filter the payload used to read a license record - after the payload has been cleaned up, before the payload has been validated.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param array $payload Payload used to read a license record
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$payload = apply_filters( 'upserv_read_license_payload', $payload );
|
||||
$validation = $this->validate_license_payload( $payload, true );
|
||||
$return = $validation;
|
||||
|
|
@ -177,13 +264,37 @@ class License_Server {
|
|||
$return = $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after reading a license record.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param mixed $return The result of the operation - a license object record or an empty array
|
||||
* @param array $payload The payload of the request
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_read_license', $return, $payload );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit license
|
||||
*
|
||||
* Update an existing license.
|
||||
*
|
||||
* @param array $payload The license data to update.
|
||||
* @return object|WP_Error Updated license object or WP_Error on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function edit_license( $payload ) {
|
||||
$payload = $this->cleanup_license_payload( $this->filter_license_payload( $payload ) );
|
||||
/**
|
||||
* Filter the payload used to edit a license record - after the payload has been cleaned up, before the payload has been validated.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param array $payload Payload used to edit a license record
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$payload = apply_filters( 'upserv_edit_license_payload', $payload );
|
||||
$validation = $this->validate_license_payload( $payload, true );
|
||||
$return = $validation;
|
||||
|
|
@ -221,13 +332,38 @@ class License_Server {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after editing a license record.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param mixed $return The result of the operation - a license record object or an array of errors
|
||||
* @param array $payload The payload of the request
|
||||
* @param mixed $original The original record to edit - a license record object or an array of errors
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_edit_license', $return, $payload, $original );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add license
|
||||
*
|
||||
* Create a new license.
|
||||
*
|
||||
* @param array $payload The license data.
|
||||
* @return object|WP_Error New license object or WP_Error on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_license( $payload ) {
|
||||
$payload = $this->build_license_payload( $payload );
|
||||
/**
|
||||
* Filter the payload used to add a license record - after the payload has been cleaned up, before the payload has been validated.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param array $payload Payload used to add a license record
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$payload = apply_filters( 'upserv_add_license_payload', $payload );
|
||||
$validation = $this->validate_license_payload( $payload );
|
||||
$return = $validation;
|
||||
|
|
@ -266,13 +402,37 @@ class License_Server {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after adding a license record.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param mixed $return The result of the operation - a license record object or an array of errors
|
||||
* @param array $payload The payload of the request
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_add_license', $return, $payload );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete license
|
||||
*
|
||||
* Remove a license from the system.
|
||||
*
|
||||
* @param array $payload The license identifier data.
|
||||
* @return object|WP_Error Deleted license object or WP_Error on failure.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function delete_license( $payload ) {
|
||||
$payload = $this->filter_license_payload( $payload );
|
||||
/**
|
||||
* Filter the payload used to delete a license record - after the payload has been cleaned up, before the payload has been validated.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param array $payload Payload used to delete a license record
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$payload = apply_filters( 'upserv_delete_license_payload', $payload );
|
||||
$validation = $this->validate_license_payload( $payload, true );
|
||||
$return = $validation;
|
||||
|
|
@ -305,11 +465,29 @@ class License_Server {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after deleting a license record.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param mixed $return The result of the operation - a license record object or an empty array
|
||||
* @param array $payload The payload of the request
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_delete_license', $return, $payload );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate license signature
|
||||
*
|
||||
* Create a cryptographic signature for a license and domain.
|
||||
*
|
||||
* @param object $license The license object.
|
||||
* @param string $domain The domain to generate signature for.
|
||||
* @return string The generated signature.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function generate_license_signature( $license, $domain ) {
|
||||
$hmac_key = $license->hmac_key;
|
||||
$crypto_key = $license->crypto_key;
|
||||
|
|
@ -319,6 +497,16 @@ class License_Server {
|
|||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is signature valid
|
||||
*
|
||||
* Verify if a license signature is valid.
|
||||
*
|
||||
* @param string $license_key The license key.
|
||||
* @param string $license_signature The signature to validate.
|
||||
* @return bool Whether the signature is valid.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function is_signature_valid( $license_key, $license_signature ) {
|
||||
$valid = false;
|
||||
$crypt = $license_signature;
|
||||
|
|
@ -344,7 +532,15 @@ class License_Server {
|
|||
in_array( $domain, $license->allowed_domains, true ) &&
|
||||
$license->package_slug === $package_slug
|
||||
) {
|
||||
$valid = true;
|
||||
/**
|
||||
* Filter whether to bypass the license signature check.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param bool $bypass Whether to bypass the license signature check
|
||||
* @param object $license The license object
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$valid = apply_filters( 'upserv_license_bypass_signature', true, $license );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -352,6 +548,13 @@ class License_Server {
|
|||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch expired licenses status
|
||||
*
|
||||
* Update status of licenses that have reached their expiry date.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function switch_expired_licenses_status() {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -401,6 +604,15 @@ class License_Server {
|
|||
$item->data['operation'] = 'edit';
|
||||
$item->data['operation_id'] = bin2hex( random_bytes( 16 ) );
|
||||
|
||||
/**
|
||||
* Fired after editing a license record.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param mixed $item The result of the operation - a license record object or an array of errors
|
||||
* @param array $payload The payload of the request
|
||||
* @param mixed $original The original record to edit - a license record object or an array of errors
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action(
|
||||
'upserv_did_edit_license',
|
||||
$item,
|
||||
|
|
@ -414,6 +626,15 @@ class License_Server {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update licenses status
|
||||
*
|
||||
* Bulk update status for multiple licenses.
|
||||
*
|
||||
* @param string $status The new status to set.
|
||||
* @param array $license_ids Optional array of license IDs to update.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function update_licenses_status( $status, $license_ids = array() ) {
|
||||
$license_query = array( 'limit' => '-1' );
|
||||
|
||||
|
|
@ -455,6 +676,15 @@ class License_Server {
|
|||
$item->data['operation'] = 'edit';
|
||||
$item->data['operation_id'] = bin2hex( random_bytes( 16 ) );
|
||||
|
||||
/**
|
||||
* Fired after editing a license record.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param mixed $item The result of the operation - a license record object or an array of errors
|
||||
* @param array $payload The payload of the request
|
||||
* @param mixed $original The original record to edit - a license record object or an array of errors
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action(
|
||||
'upserv_did_edit_license',
|
||||
$item,
|
||||
|
|
@ -468,6 +698,14 @@ class License_Server {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge licenses
|
||||
*
|
||||
* Delete licenses from the database.
|
||||
*
|
||||
* @param array $license_ids Optional array of license IDs to delete.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function purge_licenses( $license_ids = array() ) {
|
||||
$license_query = array( 'limit' => '-1' );
|
||||
|
||||
|
|
@ -509,6 +747,14 @@ class License_Server {
|
|||
$item->data['operation'] = 'delete';
|
||||
$item->data['operation_id'] = bin2hex( random_bytes( 16 ) );
|
||||
|
||||
/**
|
||||
* Fired after deleting a license record.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param mixed $item The result of the operation - a license record object or an empty array
|
||||
* @param array $payload The payload of the request
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'upserv_did_delete_license', $item, array( $item->license_key ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -518,6 +764,16 @@ class License_Server {
|
|||
* Protected methods
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Build browsing query
|
||||
*
|
||||
* Construct a valid query structure for browsing licenses.
|
||||
*
|
||||
* @param array $payload The raw query parameters.
|
||||
* @return array The processed query structure.
|
||||
* @throws Exception If query parameters are invalid.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function build_browsing_query( $payload ) {
|
||||
$original = $payload;
|
||||
$payload = array_intersect_key( $payload, self::$browsing_query );
|
||||
|
|
@ -669,6 +925,15 @@ class License_Server {
|
|||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup license payload
|
||||
*
|
||||
* Fill in default values for missing license data.
|
||||
*
|
||||
* @param array $payload The license data to clean up.
|
||||
* @return array The processed license data.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function cleanup_license_payload( $payload ) {
|
||||
|
||||
if ( isset( $payload['license_key'] ) && empty( $payload['license_key'] ) ) {
|
||||
|
|
@ -688,14 +953,41 @@ class License_Server {
|
|||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter license payload
|
||||
*
|
||||
* Remove any properties not in the license definition.
|
||||
*
|
||||
* @param array $payload The license data to filter.
|
||||
* @return array The filtered license data.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function filter_license_payload( $payload ) {
|
||||
return is_array( $payload ) ? array_intersect_key( $payload, self::$license_definition ) : self::$license_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend license payload
|
||||
*
|
||||
* Add default values for missing properties in license data.
|
||||
*
|
||||
* @param array $payload The license data to extend.
|
||||
* @return array The extended license data.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function extend_license_payload( $payload ) {
|
||||
return array_merge( self::$license_definition, $payload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize payload
|
||||
*
|
||||
* Clean and validate license data.
|
||||
*
|
||||
* @param array $license The license data to sanitize.
|
||||
* @return array The sanitized license data.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function sanitize_payload( $license ) {
|
||||
|
||||
foreach ( $license as $key => $value ) {
|
||||
|
|
@ -749,6 +1041,16 @@ class License_Server {
|
|||
return $license;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate license payload
|
||||
*
|
||||
* Check if license data is valid.
|
||||
*
|
||||
* @param array $license The license data to validate.
|
||||
* @param bool $partial Whether to perform partial validation.
|
||||
* @return bool|array True if valid, array of errors otherwise.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function validate_license_payload( $license, $partial = false ) {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -835,8 +1137,21 @@ class License_Server {
|
|||
! ( $partial && ! isset( $license['status'] ) ) &&
|
||||
! in_array( $license['status'], self::$license_statuses, true )
|
||||
) {
|
||||
/**
|
||||
* Filter whether a license is valid when requesting for an update.
|
||||
* Fired during client license API request.
|
||||
*
|
||||
* @param bool $is_valid Whether the license is valid
|
||||
* @param mixed $license The license to validate
|
||||
* @param string $license_signature The signature of the license
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$valid_status = apply_filters( 'upserv_license_valid', false, $license, '' );
|
||||
|
||||
if ( ! $valid_status ) {
|
||||
$errors['invalid_status'] = __( 'The license status is invalid.', 'updatepulse-server' );
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
! ( $partial && ! isset( $license['email'] ) ) &&
|
||||
|
|
|
|||
|
|
@ -9,13 +9,33 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
/**
|
||||
* Cache class.
|
||||
*
|
||||
* Cache data to the filesystem.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Cache {
|
||||
|
||||
/**
|
||||
* Cache directory path
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $cache_directory;
|
||||
|
||||
/**
|
||||
* File extension for cache files
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $cache_directory Directory to store cache files.
|
||||
* @param string $extension File extension for cache files. Default 'dat'.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $cache_directory, $extension = 'dat' ) {
|
||||
$this->cache_directory = $cache_directory;
|
||||
$this->extension = $extension;
|
||||
|
|
@ -24,8 +44,11 @@ class Cache {
|
|||
/**
|
||||
* Get cached value.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed|null
|
||||
* Retrieves a value from the cache if it exists and hasn't expired.
|
||||
*
|
||||
* @param string $key Cache key identifier.
|
||||
* @return mixed|null Cached value or null if not found or expired.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get( $key ) {
|
||||
$filename = $this->get_cache_filename( $key );
|
||||
|
|
@ -53,10 +76,13 @@ class Cache {
|
|||
/**
|
||||
* Update the cache.
|
||||
*
|
||||
* @param string $key Cache key.
|
||||
* Stores a value in the cache with the specified expiration time.
|
||||
*
|
||||
* @param string $key Cache key identifier.
|
||||
* @param mixed $value The value to store in the cache.
|
||||
* @param int $expiration Time until expiration, in seconds. Optional. Default `0`.
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function set( $key, $value, $expiration = 0 ) {
|
||||
$cache = array(
|
||||
|
|
@ -75,8 +101,11 @@ class Cache {
|
|||
/**
|
||||
* Clear the cache by key.
|
||||
*
|
||||
* @param string $key Cache key.
|
||||
* Removes a specific cached value by its key.
|
||||
*
|
||||
* @param string $key Cache key identifier.
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function clear( $key ) {
|
||||
$file = $this->get_cache_filename( $key );
|
||||
|
|
@ -87,8 +116,13 @@ class Cache {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return string
|
||||
* Get cache filename
|
||||
*
|
||||
* Constructs the full path to a cache file based on its key.
|
||||
*
|
||||
* @param string $key Cache key identifier.
|
||||
* @return string Full path to the cache file.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_cache_filename( $key ) {
|
||||
return $this->cache_directory . '/' . $key . '.' . $this->extension;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ use Countable;
|
|||
use ArrayIterator;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Headers class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
||||
|
||||
/**
|
||||
|
|
@ -19,6 +24,7 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
* These special headers don't have that prefix, so we need an explicit list to identify them.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $unprefixed_names = array(
|
||||
'CONTENT_TYPE',
|
||||
|
|
@ -29,8 +35,24 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
'AUTH_TYPE',
|
||||
);
|
||||
|
||||
/**
|
||||
* Headers collection
|
||||
*
|
||||
* Stores all HTTP headers.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $headers = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Initialize headers from provided array.
|
||||
*
|
||||
* @param array $headers Initial headers to set.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $headers = array() ) {
|
||||
|
||||
foreach ( $headers as $name => $value ) {
|
||||
|
|
@ -41,8 +63,9 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
/**
|
||||
* Extract HTTP headers from an array of data ( usually $_SERVER ).
|
||||
*
|
||||
* @param array $environment
|
||||
* @return array
|
||||
* @param array $environment Server environment variables.
|
||||
* @return array Extracted HTTP headers.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function parse_server() {
|
||||
$results = array();
|
||||
|
|
@ -65,8 +88,9 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
/**
|
||||
* Check if a $_SERVER key looks like a HTTP header name.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
* @param string $key The key to check.
|
||||
* @return bool Whether the key is a HTTP header name.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function is_header_name( $key ) {
|
||||
return (
|
||||
|
|
@ -80,7 +104,8 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
* Parse headers for the current HTTP request.
|
||||
* Will automatically choose the best way to get the headers from PHP.
|
||||
*
|
||||
* @return array
|
||||
* @return array HTTP headers from the current request.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function parse_current() {
|
||||
|
||||
|
|
@ -98,8 +123,9 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
/**
|
||||
* Convert a header name to "Title-Case-With-Dashes".
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
* @param string $name Header name to normalize.
|
||||
* @return string Normalized header name.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function normalize_name( $name ) {
|
||||
$name = strtolower( $name );
|
||||
|
|
@ -113,9 +139,10 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
/**
|
||||
* Check if a string starts with the given prefix.
|
||||
*
|
||||
* @param string $string
|
||||
* @param string $prefix
|
||||
* @return bool
|
||||
* @param string $_string The string to check.
|
||||
* @param string $prefix The prefix to look for.
|
||||
* @return bool Whether the string starts with the prefix.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static function starts_with( $_string, $prefix ) {
|
||||
return ( substr( $_string, 0, strlen( $prefix ) ) === $prefix );
|
||||
|
|
@ -126,7 +153,8 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
*
|
||||
* @param string $name Header name.
|
||||
* @param mixed $_default The default value to return if the header doesn't exist.
|
||||
* @return string|null
|
||||
* @return string|null Header value or default if not found.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get( $name, $_default = null ) {
|
||||
$name = $this->normalize_name( $name );
|
||||
|
|
@ -141,8 +169,9 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
/**
|
||||
* Set a header to value.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param string $name Header name.
|
||||
* @param string $value Header value.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function set( $name, $value ) {
|
||||
$name = $this->normalize_name( $name );
|
||||
|
|
@ -151,34 +180,83 @@ class Headers implements ArrayAccess, IteratorAggregate, Countable {
|
|||
|
||||
/* ArrayAccess interface */
|
||||
|
||||
/**
|
||||
* Check if header exists
|
||||
*
|
||||
* Implementation for ArrayAccess interface.
|
||||
*
|
||||
* @param mixed $offset The header name.
|
||||
* @return bool Whether the header exists.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists( $offset ): bool {
|
||||
return array_key_exists( $offset, $this->headers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header value
|
||||
*
|
||||
* Implementation for ArrayAccess interface.
|
||||
*
|
||||
* @param mixed $offset The header name.
|
||||
* @return mixed The header value.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet( $offset ): mixed {
|
||||
return $this->get( $offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set header value
|
||||
*
|
||||
* Implementation for ArrayAccess interface.
|
||||
*
|
||||
* @param mixed $offset The header name.
|
||||
* @param mixed $value The header value.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet( $offset, $value ): void {
|
||||
$this->set( $offset, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset header
|
||||
*
|
||||
* Implementation for ArrayAccess interface.
|
||||
*
|
||||
* @param mixed $offset The header name.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset( $offset ): void {
|
||||
$name = $this->normalize_name( $offset );
|
||||
unset( $this->headers[ $name ] );
|
||||
}
|
||||
|
||||
/* Countable interface */
|
||||
/**
|
||||
* Count headers
|
||||
*
|
||||
* Implementation for Countable interface.
|
||||
*
|
||||
* @return int Number of headers.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count(): int {
|
||||
return count( $this->headers );
|
||||
}
|
||||
|
||||
/* IteratorAggregate interface */
|
||||
/**
|
||||
* Get iterator for headers
|
||||
*
|
||||
* Implementation for IteratorAggregate interface.
|
||||
*
|
||||
* @return Traversable Iterator for headers.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator(): Traversable {
|
||||
return new ArrayIterator( $this->headers );
|
||||
|
|
|
|||
|
|
@ -10,16 +10,32 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* This class represents the collection of files and metadata that make up
|
||||
* a WordPress plugin or theme, or a generic software package.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Package {
|
||||
|
||||
/** @var string Path to the Zip archive that contains the package. */
|
||||
/**
|
||||
* Path to the Zip archive that contains the package.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/** @var array Package metadata in a format suitable for the update checker. */
|
||||
/**
|
||||
* Package metadata in a format suitable for the update checker.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $metadata = array();
|
||||
|
||||
/** @var string Package slug. */
|
||||
/**
|
||||
* Package slug.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $slug;
|
||||
|
||||
/**
|
||||
|
|
@ -29,9 +45,10 @@ class Package {
|
|||
* of instantiating this class directly. Still, you can do it if you want to, for example,
|
||||
* load package metadata from the database instead of extracting it from a Zip file.
|
||||
*
|
||||
* @param string $slug
|
||||
* @param string $filename
|
||||
* @param array $metadata
|
||||
* @param string $slug The package slug.
|
||||
* @param string $filename The path to the package file.
|
||||
* @param array $metadata The package metadata.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $slug, $filename = null, $metadata = array() ) {
|
||||
$this->slug = $slug;
|
||||
|
|
@ -42,7 +59,8 @@ class Package {
|
|||
/**
|
||||
* Get the full file path of this package.
|
||||
*
|
||||
* @return string
|
||||
* @return string The full file path of the package.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_filename() {
|
||||
return $this->filename;
|
||||
|
|
@ -52,7 +70,8 @@ class Package {
|
|||
* Get package metadata.
|
||||
*
|
||||
* @see self::extractMetadata()
|
||||
* @return array
|
||||
* @return array The package metadata merged with the slug.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_metadata() {
|
||||
return array_merge( $this->metadata, array( 'slug' => $this->slug ) );
|
||||
|
|
@ -63,10 +82,11 @@ class Package {
|
|||
*
|
||||
* @param string $filename Path to a Zip archive that contains a package.
|
||||
* @param string $slug Optional package slug. Will be detected automatically.
|
||||
* @param Cache $cache
|
||||
* @return Package
|
||||
* @param Cache|null $cache Optional cache object for metadata.
|
||||
* @return Package A new Package instance with the extracted metadata.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function from_archive( $filename, $slug = null, Cache $cache = null ) {
|
||||
public static function from_archive( $filename, $slug = null, $cache = null ) {
|
||||
$meta_obj = new Zip_Metadata_Parser( $slug, $filename, $cache );
|
||||
$metadata = $meta_obj->get();
|
||||
|
||||
|
|
@ -80,7 +100,8 @@ class Package {
|
|||
/**
|
||||
* Get the size of the package (in bytes).
|
||||
*
|
||||
* @return int
|
||||
* @return int The size of the package file in bytes.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_file_size() {
|
||||
return filesize( $this->filename );
|
||||
|
|
@ -89,7 +110,8 @@ class Package {
|
|||
/**
|
||||
* Get the Unix timestamp of the last time this package was modified.
|
||||
*
|
||||
* @return int
|
||||
* @return int The Unix timestamp when the package was last modified.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_last_modified() {
|
||||
return filemtime( $this->filename );
|
||||
|
|
|
|||
|
|
@ -8,29 +8,94 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
|
||||
/**
|
||||
* Simple request class for the update server.
|
||||
*
|
||||
* Handles incoming update requests, parsing parameters and headers.
|
||||
*/
|
||||
class Request {
|
||||
|
||||
/** @var array Query parameters. */
|
||||
/**
|
||||
* Query parameters
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $query = array();
|
||||
/** @var string Client's IP address. */
|
||||
/**
|
||||
* Client's IP address
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $client_ip;
|
||||
/** @var string The HTTP method, e.g. "POST" or "GET". */
|
||||
/**
|
||||
* The HTTP method
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $http_method;
|
||||
/** @var string The name of the current action. For example, "get_metadata". */
|
||||
/**
|
||||
* The name of the current action
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $action;
|
||||
/** @var string Package slug from the current request. */
|
||||
/**
|
||||
* Package slug from the current request
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $slug;
|
||||
/** @var Package The package that matches the current slug, if any. */
|
||||
/**
|
||||
* The package that matches the current slug
|
||||
*
|
||||
* @var Package|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $package = null;
|
||||
/** @var string WordPress version number as extracted from the User-Agent header. */
|
||||
/**
|
||||
* WordPress version number
|
||||
*
|
||||
* @var string|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $wp_version = null;
|
||||
/** @var string WordPress site URL, also from the User-Agent. */
|
||||
/**
|
||||
* WordPress site URL
|
||||
*
|
||||
* @var string|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $wp_site_url = null;
|
||||
/**
|
||||
* Request headers container
|
||||
*
|
||||
* @var Headers
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $headers;
|
||||
|
||||
/** @var array Other, arbitrary request properties. */
|
||||
/**
|
||||
* Other, arbitrary request properties
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $props = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Initialize a new request object with query parameters, headers and connection info.
|
||||
*
|
||||
* @param array $query Request query parameters.
|
||||
* @param array $headers Request HTTP headers.
|
||||
* @param string $client_ip Client's IP address, defaults to '0.0.0.0'.
|
||||
* @param string $http_method HTTP method used for the request, defaults to 'GET'.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $query, $headers, $client_ip = '0.0.0.0', $http_method = 'GET' ) {
|
||||
$this->query = $query;
|
||||
$this->headers = new Headers( $headers );
|
||||
|
|
@ -58,9 +123,12 @@ class Request {
|
|||
/**
|
||||
* Get the value of a query parameter.
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
* Safely retrieves a parameter from the query array with an optional default value.
|
||||
*
|
||||
* @param string $name Parameter name to retrieve.
|
||||
* @param mixed $_default The value to return if the parameter doesn't exist. Defaults to null.
|
||||
* @return mixed
|
||||
* @return mixed The parameter value or default if not found.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function param( $name, $_default = null ) {
|
||||
|
||||
|
|
@ -71,6 +139,15 @@ class Request {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter for dynamic properties
|
||||
*
|
||||
* Retrieves dynamically stored properties from the props array.
|
||||
*
|
||||
* @param string $name Property name to retrieve.
|
||||
* @return mixed The property value or null if not found.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
|
||||
if ( array_key_exists( $name, $this->props ) ) {
|
||||
|
|
@ -80,14 +157,40 @@ class Request {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic setter for dynamic properties
|
||||
*
|
||||
* Sets values in the dynamic props array.
|
||||
*
|
||||
* @param string $name Property name to set.
|
||||
* @param mixed $value Value to assign to the property.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __set( $name, $value ) {
|
||||
$this->props[ $name ] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic isset checker for dynamic properties
|
||||
*
|
||||
* Checks if a dynamic property exists in the props array.
|
||||
*
|
||||
* @param string $name Property name to check.
|
||||
* @return bool Whether the property exists.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __isset( $name ) {
|
||||
return isset( $this->props[ $name ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic unset for dynamic properties
|
||||
*
|
||||
* Removes a property from the props array.
|
||||
*
|
||||
* @param string $name Property name to remove.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __unset( $name ) {
|
||||
unset( $this->props[ $name ] );
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -14,13 +14,23 @@ use Anyape\UpdatePulse\Package_Parser\Parser;
|
|||
class Zip_Metadata_Parser {
|
||||
|
||||
/**
|
||||
* @var int $cache_time How long the package metadata should be cached in seconds.
|
||||
* Cache time
|
||||
*
|
||||
* How long the package metadata should be cached in seconds.
|
||||
* Defaults to 1 week ( 7 * 24 * 60 * 60 ).
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static $cache_time = 604800;
|
||||
|
||||
/**
|
||||
* @var array Package PHP header mapping, i.e. which tags to add to the metadata under which array key
|
||||
* Header map
|
||||
*
|
||||
* Package PHP header mapping, i.e. which tags to add to the metadata under which array key.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $header_map = array(
|
||||
'Name' => 'name',
|
||||
|
|
@ -36,48 +46,75 @@ class Zip_Metadata_Parser {
|
|||
'Depends' => 'depends',
|
||||
'Provides' => 'provides',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array Plugin readme file mapping, i.e. which tags to add to the metadata
|
||||
* Readme map
|
||||
*
|
||||
* Plugin readme file mapping, i.e. which tags to add to the metadata.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $readme_map = array(
|
||||
'requires',
|
||||
'tested',
|
||||
'requires_php',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array Package info as retrieved by the parser
|
||||
* Package info
|
||||
*
|
||||
* Package info as retrieved by the parser.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $package_info;
|
||||
|
||||
/**
|
||||
* @var string Path to the Zip archive that contains the package.
|
||||
* Filename
|
||||
*
|
||||
* Path to the Zip archive that contains the package.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* @var string Package slug.
|
||||
* Slug
|
||||
*
|
||||
* Package slug.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $slug;
|
||||
|
||||
/**
|
||||
* @var Cache object.
|
||||
* Cache
|
||||
*
|
||||
* Cache object.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var array Package metadata in a format suitable for the update checker.
|
||||
* Metadata
|
||||
*
|
||||
* Package metadata in a format suitable for the update checker.
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $metadata;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Get the metadata from a zip file.
|
||||
*
|
||||
* @param string $slug
|
||||
* @param string $filename
|
||||
* @param $cache
|
||||
* @param string $slug Package slug.
|
||||
* @param string $filename Path to the Zip archive.
|
||||
* @param object $cache Cache object.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $slug, $filename, $cache = null ) {
|
||||
$this->slug = $slug;
|
||||
|
|
@ -88,7 +125,14 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Build the cache key (cache filename) for a file
|
||||
* Build cache key
|
||||
*
|
||||
* Build the cache key (cache filename) for a file.
|
||||
*
|
||||
* @param string $slug Package slug.
|
||||
* @param string $filename Path to the Zip archive.
|
||||
* @return string The cache key.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function build_cache_key( $slug, $filename ) {
|
||||
$cache_key = $slug . '-b64-';
|
||||
|
|
@ -97,6 +141,15 @@ class Zip_Metadata_Parser {
|
|||
$cache_key .= md5( $filename . '|' . filesize( $filename ) . '|' . filemtime( $filename ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the cache key used for storing package metadata.
|
||||
*
|
||||
* @param string $cache_key The generated cache key for the package.
|
||||
* @param string $slug The package slug.
|
||||
* @param string $filename The path to the Zip archive.
|
||||
* @return string The filtered cache key.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
return apply_filters(
|
||||
'upserv_zip_metadata_parser_cache_key',
|
||||
$cache_key,
|
||||
|
|
@ -106,19 +159,26 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get metadata.
|
||||
* Get metadata
|
||||
*
|
||||
* @return array
|
||||
* Get the package metadata.
|
||||
*
|
||||
* @return array Package metadata.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get() {
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set metadata
|
||||
*
|
||||
* Load metadata information from a cache or create it.
|
||||
*
|
||||
* We'll try to load processed metadata from the cache first (if available), and if that
|
||||
* fails we'll extract package details from the specified Zip file.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_metadata() {
|
||||
$cache_key = self::build_cache_key( $this->slug, $this->filename );
|
||||
|
|
@ -151,10 +211,13 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract metadata
|
||||
*
|
||||
* Extract package headers and readme contents from a ZIP file and convert them
|
||||
* into a structure compatible with the custom update checker.
|
||||
*
|
||||
* @throws Invalid_Package_Exception if the input file can't be parsed as a package.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function extract_metadata() {
|
||||
$this->package_info = Parser::parse_package( $this->filename, true );
|
||||
|
|
@ -162,10 +225,10 @@ class Zip_Metadata_Parser {
|
|||
if ( is_array( $this->package_info ) && ! empty( $this->package_info ) ) {
|
||||
$this->set_info_from_header();
|
||||
$this->set_info_from_readme();
|
||||
$this->set_last_update_date();
|
||||
$this->set_info_from_assets();
|
||||
$this->set_slug();
|
||||
$this->set_type();
|
||||
$this->set_last_update_date();
|
||||
} else {
|
||||
throw new Invalid_Package_Exception(
|
||||
sprintf(
|
||||
|
|
@ -177,7 +240,11 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract relevant metadata from the package header information
|
||||
* Set info from header
|
||||
*
|
||||
* Extract relevant metadata from the package header information.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_info_from_header() {
|
||||
|
||||
|
|
@ -188,7 +255,11 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract relevant metadata from the plugin readme
|
||||
* Set info from readme
|
||||
*
|
||||
* Extract relevant metadata from the plugin readme.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_info_from_readme() {
|
||||
|
||||
|
|
@ -202,14 +273,17 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract selected metadata from the retrieved package info
|
||||
* Set mapped fields
|
||||
*
|
||||
* Extract selected metadata from the retrieved package info.
|
||||
*
|
||||
* @see http://codex.wordpress.org/File_Header
|
||||
* @see https://wordpress.org/plugins/about/readme.txt
|
||||
*
|
||||
* @param array $input The package info sub-array to use to retrieve the info from
|
||||
* @param array $input The package info sub-array to use to retrieve the info from.
|
||||
* @param array $map The key mapping for that sub-array where the key is the key as used in the
|
||||
* input array and the value is the key to use for the output array
|
||||
* input array and the value is the key to use for the output array.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_mapped_fields( $input, $map ) {
|
||||
|
||||
|
|
@ -222,11 +296,15 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Determine the details url for themes
|
||||
* Set theme details URL
|
||||
*
|
||||
* Determine the details url for themes.
|
||||
*
|
||||
* Theme metadata should include a "details_url" that specifies the page to display
|
||||
* when the user clicks "View version x.y.z details". If the developer didn't provide
|
||||
* it by setting the "Details URI" header, we'll default to the theme homepage ( "Theme URI" ).
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_theme_details_url() {
|
||||
|
||||
|
|
@ -239,9 +317,12 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract the texual information sections from a readme file
|
||||
* Set readme sections
|
||||
*
|
||||
* Extract the texual information sections from a readme file.
|
||||
*
|
||||
* @see https://wordpress.org/plugins/about/readme.txt
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_readme_sections() {
|
||||
|
||||
|
|
@ -263,9 +344,12 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract the upgrade notice for the current version from a readme file
|
||||
* Set readme upgrade notice
|
||||
*
|
||||
* Extract the upgrade notice for the current version from a readme file.
|
||||
*
|
||||
* @see https://wordpress.org/plugins/about/readme.txt
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_readme_upgrade_notice() {
|
||||
|
||||
|
|
@ -282,19 +366,55 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add last update date to the metadata
|
||||
* Set last update date
|
||||
*
|
||||
* Add last update date to the metadata; this is tied to the version.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_last_update_date() {
|
||||
|
||||
if ( ! isset( $this->metadata['last_updated'] ) ) {
|
||||
$this->metadata['last_updated'] = gmdate( 'Y-m-d H:i:s', filemtime( $this->filename ) );
|
||||
}
|
||||
if ( isset( $this->metadata['last_updated'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = upserv_get_package_metadata( $this->slug );
|
||||
|
||||
if ( ! is_array( $meta ) ) {
|
||||
$meta = array();
|
||||
}
|
||||
|
||||
if (
|
||||
! isset( $meta['version'], $meta['version_time'] ) ||
|
||||
$meta['version'] !== $this->metadata['version']
|
||||
) {
|
||||
$meta['version'] = $this->metadata['version'];
|
||||
$meta['version_time'] = gmdate( 'Y-m-d H:i:s', filemtime( $this->filename ) );
|
||||
|
||||
upserv_set_package_metadata( $this->slug, $meta );
|
||||
}
|
||||
|
||||
$this->metadata['last_updated'] = $meta['version_time'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type
|
||||
*
|
||||
* Set the package type in the metadata.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_type() {
|
||||
$this->metadata['type'] = $this->package_info['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set slug
|
||||
*
|
||||
* Set the package slug in the metadata.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_slug() {
|
||||
|
||||
if ( 'plugin' === $this->package_info['type'] ) {
|
||||
|
|
@ -309,7 +429,11 @@ class Zip_Metadata_Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract icons and banners info for plugins
|
||||
* Set info from assets
|
||||
*
|
||||
* Extract icons and banners info for plugins.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function set_info_from_assets() {
|
||||
|
||||
|
|
@ -324,13 +448,14 @@ class Zip_Metadata_Parser {
|
|||
$this->metadata['banners'] = $extra_meta['banners'];
|
||||
}
|
||||
|
||||
if ( ! empty( $extra_meta['require_license'] ) ) {
|
||||
$this->metadata['require_license'] = (
|
||||
! empty( $extra_meta['require_license'] ) &&
|
||||
(
|
||||
'yes' === $extra_meta['require_license'] ||
|
||||
'true' === $extra_meta['require_license'] ||
|
||||
1 === intval( $extra_meta['require_license'] )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $extra_meta['licensed_with'] ) ) {
|
||||
$this->metadata['licensed_with'] = $extra_meta['licensed_with'];
|
||||
|
|
|
|||
|
|
@ -9,13 +9,43 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
use WP_List_Table;
|
||||
use Anyape\Utils\Utils;
|
||||
|
||||
/**
|
||||
* Licenses table class
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Licenses_Table extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* Bulk action error
|
||||
*
|
||||
* @var mixed
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $bulk_action_error;
|
||||
/**
|
||||
* Nonce action name
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $nonce_action;
|
||||
|
||||
/**
|
||||
* Table rows data
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $rows;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Sets up the table properties and hooks
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
array(
|
||||
|
|
@ -34,6 +64,14 @@ class Licenses_Table extends WP_List_Table {
|
|||
|
||||
// Overrides ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get table columns
|
||||
*
|
||||
* Define the columns for the licenses table
|
||||
*
|
||||
* @return array The table columns
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
|
|
@ -48,10 +86,28 @@ class Licenses_Table extends WP_List_Table {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default column rendering
|
||||
*
|
||||
* Default handler for displaying column data
|
||||
*
|
||||
* @param array $item The row item
|
||||
* @param string $column_name The column name
|
||||
* @return mixed The column value
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function column_default( $item, $column_name ) {
|
||||
return $item[ $column_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sortable columns
|
||||
*
|
||||
* Define which columns can be sorted
|
||||
*
|
||||
* @return array The sortable columns
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_sortable_columns() {
|
||||
return array(
|
||||
'col_status' => array( 'status', false ),
|
||||
|
|
@ -64,6 +120,13 @@ class Licenses_Table extends WP_List_Table {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare table items
|
||||
*
|
||||
* Query the database and set up the items for display
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function prepare_items() {
|
||||
global $wpdb;
|
||||
|
||||
|
|
@ -174,6 +237,13 @@ class Licenses_Table extends WP_List_Table {
|
|||
$this->items = $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display table rows
|
||||
*
|
||||
* Output the HTML for each row in the table
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display_rows() {
|
||||
$records = $this->items;
|
||||
$table = $this;
|
||||
|
|
@ -186,7 +256,11 @@ class Licenses_Table extends WP_List_Table {
|
|||
$page = ! empty( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
foreach ( $records as $record_key => $record ) {
|
||||
$bulk_value = wp_json_encode( $record );
|
||||
$bulk_value = wp_json_encode(
|
||||
$record,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES |
|
||||
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
|
||||
);
|
||||
$record['status_label'] = Utils::get_status_string( $record['status'] );
|
||||
|
||||
upserv_get_admin_template(
|
||||
|
|
@ -210,6 +284,14 @@ class Licenses_Table extends WP_List_Table {
|
|||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set table rows
|
||||
*
|
||||
* Set the row data for the table
|
||||
*
|
||||
* @param array $rows The rows data
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function set_rows( $rows ) {
|
||||
$this->rows = $rows;
|
||||
}
|
||||
|
|
@ -220,6 +302,16 @@ class Licenses_Table extends WP_List_Table {
|
|||
|
||||
// Overrides ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generate row actions
|
||||
*
|
||||
* Create action links for each row
|
||||
*
|
||||
* @param array $actions The actions array
|
||||
* @param bool $always_visible Whether actions should be always visible
|
||||
* @return string HTML for the row actions
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function row_actions( $actions, $always_visible = false ) {
|
||||
$action_count = count( $actions );
|
||||
$i = 0;
|
||||
|
|
@ -244,6 +336,14 @@ class Licenses_Table extends WP_List_Table {
|
|||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display extra tablenav
|
||||
*
|
||||
* Add additional controls above or below the table
|
||||
*
|
||||
* @param string $which The location ('top' or 'bottom')
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function extra_tablenav( $which ) {
|
||||
|
||||
if ( 'bottom' === $which ) {
|
||||
|
|
@ -251,6 +351,14 @@ class Licenses_Table extends WP_List_Table {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bulk actions
|
||||
*
|
||||
* Define available bulk actions for the table
|
||||
*
|
||||
* @return array The available bulk actions
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_bulk_actions() {
|
||||
$actions = array(
|
||||
'pending' => __( 'Set to Pending', 'updatepulse-server' ),
|
||||
|
|
@ -264,6 +372,14 @@ class Licenses_Table extends WP_List_Table {
|
|||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table classes
|
||||
*
|
||||
* Define CSS classes for the table
|
||||
*
|
||||
* @return array The table CSS classes
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_table_classes() {
|
||||
$mode = get_user_setting( 'posts_list_mode', 'list' );
|
||||
|
||||
|
|
|
|||
|
|
@ -9,14 +9,51 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
use WP_List_Table;
|
||||
use DateTimeZone;
|
||||
|
||||
/**
|
||||
* Packages Table class
|
||||
*
|
||||
* Manages the display of packages in the admin area
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Packages_Table extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* Bulk action error message
|
||||
*
|
||||
* @var string|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $bulk_action_error;
|
||||
/**
|
||||
* Nonce action name
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $nonce_action;
|
||||
|
||||
/**
|
||||
* Table rows data
|
||||
*
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $rows;
|
||||
/**
|
||||
* Package manager instance
|
||||
*
|
||||
* @var object
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $package_manager;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object $package_manager The package manager instance
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct( $package_manager ) {
|
||||
parent::__construct(
|
||||
array(
|
||||
|
|
@ -36,7 +73,22 @@ class Packages_Table extends WP_List_Table {
|
|||
|
||||
// Overrides ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get table columns
|
||||
*
|
||||
* Define the columns shown in the packages table.
|
||||
*
|
||||
* @return array Table columns
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_columns() {
|
||||
/**
|
||||
* Filter the columns shown in the packages table.
|
||||
*
|
||||
* @param array $columns The default columns for the packages table
|
||||
* @return array The filtered columns
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$columns = apply_filters(
|
||||
'upserv_packages_table_columns',
|
||||
array(
|
||||
|
|
@ -46,7 +98,7 @@ class Packages_Table extends WP_List_Table {
|
|||
'col_type' => __( 'Type', 'updatepulse-server' ),
|
||||
'col_file_name' => __( 'File Name', 'updatepulse-server' ),
|
||||
'col_file_size' => __( 'Size', 'updatepulse-server' ),
|
||||
'col_file_last_modified' => __( 'Last Modified ', 'updatepulse-server' ),
|
||||
'col_file_last_modified' => __( 'File Modified ', 'updatepulse-server' ),
|
||||
'col_origin' => __( 'Origin', 'updatepulse-server' ),
|
||||
)
|
||||
);
|
||||
|
|
@ -54,11 +106,36 @@ class Packages_Table extends WP_List_Table {
|
|||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default column renderer
|
||||
*
|
||||
* Default handler for columns without specific renderers.
|
||||
*
|
||||
* @param array $item The current row item
|
||||
* @param string $column_name The current column name
|
||||
* @return mixed Column content
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function column_default( $item, $column_name ) {
|
||||
return $item[ $column_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sortable columns
|
||||
*
|
||||
* Define which columns can be sorted in the table.
|
||||
*
|
||||
* @return array Sortable columns configuration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function get_sortable_columns() {
|
||||
/**
|
||||
* Filter the sortable columns in the packages table.
|
||||
*
|
||||
* @param array $columns The default sortable columns
|
||||
* @return array The filtered sortable columns
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$columns = apply_filters(
|
||||
'upserv_packages_table_sortable_columns',
|
||||
array(
|
||||
|
|
@ -75,6 +152,13 @@ class Packages_Table extends WP_List_Table {
|
|||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare table items
|
||||
*
|
||||
* Process data for table display including pagination.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function prepare_items() {
|
||||
$total_items = count( $this->rows );
|
||||
$offset = 0;
|
||||
|
|
@ -111,7 +195,13 @@ class Packages_Table extends WP_List_Table {
|
|||
uasort( $this->items, array( &$this, 'uasort_reorder' ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display table rows
|
||||
*
|
||||
* Render the rows of the packages table.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display_rows() {
|
||||
$records = $this->items;
|
||||
$table = $this;
|
||||
|
|
@ -148,6 +238,19 @@ class Packages_Table extends WP_List_Table {
|
|||
$record['metadata']['origin'] = 'unknown';
|
||||
}
|
||||
|
||||
$record['update_url'] = add_query_arg(
|
||||
array(
|
||||
'action' => 'get_metadata',
|
||||
'package_id' => $record['slug'],
|
||||
'installed_version' => $record['version'],
|
||||
'php' => PHP_VERSION,
|
||||
'locale' => get_locale(),
|
||||
'checking_for_updates' => 1,
|
||||
'update_type' => ucfirst( $record['type'] ),
|
||||
),
|
||||
home_url( '/updatepulse-server-update-api/' )
|
||||
);
|
||||
|
||||
$info = $record;
|
||||
$unset_metadata = array( 'previous', 'branch', 'vcs_key', 'vcs', 'whitelist' );
|
||||
|
||||
|
|
@ -166,7 +269,8 @@ class Packages_Table extends WP_List_Table {
|
|||
$search = ! empty( $_REQUEST['s'] ) ? trim( sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$record['info'] = wp_json_encode(
|
||||
$info,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES |
|
||||
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
|
||||
);
|
||||
|
||||
upserv_get_admin_template(
|
||||
|
|
@ -192,10 +296,28 @@ class Packages_Table extends WP_List_Table {
|
|||
|
||||
// Misc. -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set table rows
|
||||
*
|
||||
* Set the rows data for the table.
|
||||
*
|
||||
* @param array $rows Table rows data
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function set_rows( $rows ) {
|
||||
$this->rows = $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom sorting function
|
||||
*
|
||||
* Sort table items based on request parameters.
|
||||
*
|
||||
* @param array $a First item to compare
|
||||
* @param array $b Second item to compare
|
||||
* @return int Comparison result
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function uasort_reorder( $a, $b ) {
|
||||
$order_by = ! empty( $_REQUEST['orderby'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) : 'name'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$order = ! empty( $_REQUEST['order'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) : 'asc'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
|
@ -228,6 +350,14 @@ class Packages_Table extends WP_List_Table {
|
|||
|
||||
// Overrides ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* Display extra table navigation
|
||||
*
|
||||
* Add additional controls above or below the table.
|
||||
*
|
||||
* @param string $which Position ('top' or 'bottom')
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function extra_tablenav( $which ) {
|
||||
|
||||
if ( 'top' === $which ) {
|
||||
|
|
@ -244,15 +374,37 @@ class Packages_Table extends WP_List_Table {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table CSS classes
|
||||
*
|
||||
* Define the CSS classes for the table.
|
||||
*
|
||||
* @return array Table CSS classes
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_table_classes() {
|
||||
$mode = get_user_setting( 'posts_list_mode', 'list' );
|
||||
|
||||
$mode_class = esc_attr( 'table-view-' . $mode );
|
||||
|
||||
return array( 'widefat', 'striped', $mode_class, $this->_args['plural'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bulk actions
|
||||
*
|
||||
* Define available bulk actions for the table.
|
||||
*
|
||||
* @return array Bulk actions
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_bulk_actions() {
|
||||
/**
|
||||
* Filter the bulk actions available in the packages table.
|
||||
*
|
||||
* @param array $actions The default bulk actions
|
||||
* @return array The filtered bulk actions
|
||||
* @since 1.0.0
|
||||
*/
|
||||
$actions = apply_filters(
|
||||
'upserv_packages_table_bulk_actions',
|
||||
array(
|
||||
|
|
@ -264,6 +416,15 @@ class Packages_Table extends WP_List_Table {
|
|||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VCS icon class
|
||||
*
|
||||
* Get the appropriate icon class for a VCS provider.
|
||||
*
|
||||
* @param array $vcs_config VCS configuration
|
||||
* @return string CSS class for the VCS icon
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function get_vcs_class( $vcs_config ) {
|
||||
|
||||
switch ( $vcs_config['type'] ) {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
printf(
|
||||
// translators: %s is <code>upserv_download_remote_package( string $package_slug, string $type );</code>
|
||||
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' ),
|
||||
'<code>upserv_download_remote_package( string $package_slug, string $type, string $vcs_url = false, string branch = \'main\');</code>'
|
||||
'<code>upserv_download_remote_package( string $package_slug, string $type, string $vcs_url = false, string branch = \'main\' );</code>'
|
||||
);
|
||||
?>
|
||||
</li>
|
||||
|
|
@ -285,7 +285,7 @@ Licensed With: another-plugin-or-theme-slug</pre><br>
|
|||
// 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' ),
|
||||
'<a target="_blank" href="https://github.com/anyape/updatepulse-server/issues">' . esc_html__( 'open an issue on Github', 'updatepulse-server' ) . '</a>',
|
||||
'<a href="mailto:updatepulse@anyape.come">updatepulse@anyape.com</a>',
|
||||
'<a href="mailto:updatepulse@anyape.com">updatepulse@anyape.com</a>',
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,16 @@
|
|||
<td>
|
||||
<input class="vcs-setting regular-text" type="text" id="upserv_vcs_branch" data-prop="branch" value="">
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'The branch to download when getting remote packages from the Version Control System.', 'updatepulse-server' ); ?>
|
||||
<?php
|
||||
printf(
|
||||
// translators: %1$s line break, %2$s is <code>PUC_FORCE_BRANCH</code>, %3$s is <code>true</code>, %4$s is <code>wp-config.php</code>
|
||||
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' ),
|
||||
'<br/>',
|
||||
'<code>PUC_FORCE_BRANCH</code>',
|
||||
'<code>true</code>',
|
||||
'<code>wp-config.php</code>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -295,7 +304,16 @@
|
|||
<td>
|
||||
<input class="regular-text" type="text" id="upserv_add_vcs_branch" data-prop="branch" value="">
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'The branch to download when getting remote packages from the Version Control System.', 'updatepulse-server' ); ?>
|
||||
<?php
|
||||
printf(
|
||||
// translators: %1$s line break, %2$s is <code>PUC_FORCE_BRANCH</code>, %3$s is <code>true</code>, %4$s is <code>wp-config.php</code>
|
||||
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' ),
|
||||
'<br/>',
|
||||
'<code>PUC_FORCE_BRANCH</code>',
|
||||
'<code>true</code>',
|
||||
'<code>wp-config.php</code>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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 {
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
// 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'] );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ) ) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches ) ) {
|
||||
$this->user_name = $matches['username'];
|
||||
$this->repository_name = $matches['repository'];
|
||||
} elseif ( ( 'gitlab.com' === $this->repository_host ) ) {
|
||||
//This is probably a repository in a sub_group, e.g. "/organization/category/repo".
|
||||
// Handle repositories in sub-groups, e.g., "/organization/category/repo".
|
||||
$parts = explode( '/', trim( $path, '/' ) );
|
||||
|
||||
if ( count( $parts ) < 3 ) {
|
||||
|
|
@ -62,16 +77,16 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
$this->repository_name = $last_part;
|
||||
} else {
|
||||
|
||||
//There could be sub_groups in the URL: gitlab.domain.com/group/sub_group/sub_group2/repository
|
||||
// Handle URLs with sub-groups: gitlab.domain.com/group/sub_group/sub_group2/repository.
|
||||
if ( null === $sub_group ) {
|
||||
$path = str_replace( trailingslashit( $sub_group ), '', $path );
|
||||
}
|
||||
|
||||
//This is not a traditional url, it could be gitlab is in a deeper subdirectory.
|
||||
//Get the path segments.
|
||||
// Handle non-traditional URLs where GitLab is in a deeper subdirectory.
|
||||
// Extract the path segments.
|
||||
$segments = explode( '/', untrailingslashit( ltrim( $path, '/' ) ) );
|
||||
|
||||
//We need at least /user-name/repository-name/
|
||||
// Ensure there are at least /user-name/repository-name/ segments.
|
||||
if ( count( $segments ) < 2 ) {
|
||||
throw new InvalidArgumentException(
|
||||
esc_html(
|
||||
|
|
@ -80,17 +95,17 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
);
|
||||
}
|
||||
|
||||
//Get the username and repository name.
|
||||
// Extract the username and repository name.
|
||||
$username_repo = array_splice( $segments, -2, 2 );
|
||||
$this->user_name = $username_repo[0];
|
||||
$this->repository_name = $username_repo[1];
|
||||
|
||||
//Append the remaining segments to the host if there are segments left.
|
||||
// Append remaining segments to the host if any segments are left.
|
||||
if ( count( $segments ) > 0 ) {
|
||||
$this->repository_host = trailingslashit( $this->repository_host ) . implode( '/', $segments );
|
||||
}
|
||||
|
||||
//Add sub_groups to username.
|
||||
// Add sub-groups to the username if provided.
|
||||
if ( null !== $sub_group ) {
|
||||
$this->user_name = $username_repo[0] . '/' . untrailingslashit( $sub_group );
|
||||
}
|
||||
|
|
@ -102,7 +117,9 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
/**
|
||||
* Check if the VCS is accessible.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
* @param string $url The URL to check.
|
||||
* @param string $access_token The authentication token for GitLab.
|
||||
* @return bool|WP_Error True if accessible, WP_Error otherwise.
|
||||
*/
|
||||
public static function test( $url, $access_token = null ) {
|
||||
$instance = new self( $url . 'bogus/', $access_token );
|
||||
|
|
@ -118,13 +135,15 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
return $response;
|
||||
}
|
||||
|
||||
return $response && isset( $response->path ) && $instance->user_name === $response->path;
|
||||
return $response &&
|
||||
isset( $response->path ) &&
|
||||
trailingslashit( $instance->user_name ) === trailingslashit( $response->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest release from GitLab.
|
||||
* Retrieve the latest release from GitLab.
|
||||
*
|
||||
* @return Reference|null
|
||||
* @return Reference|null The latest release or null if not found.
|
||||
*/
|
||||
public function get_latest_release() {
|
||||
$releases = $this->api( '/:id/releases', array( 'per_page' => $this->release_filter_max_releases ) );
|
||||
|
|
@ -136,10 +155,10 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
foreach ( $releases as $release ) {
|
||||
|
||||
if (
|
||||
//Skip invalid/unsupported releases.
|
||||
// Skip invalid or unsupported releases.
|
||||
! is_object( $release )
|
||||
|| ! isset( $release->tag_name )
|
||||
//Skip upcoming releases.
|
||||
// Skip upcoming releases.
|
||||
|| (
|
||||
! empty( $release->upcoming_release )
|
||||
&& $this->should_skip_pre_releases()
|
||||
|
|
@ -148,9 +167,9 @@ if ( ! class_exists( GitLabApi::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".
|
||||
|
||||
//Apply custom filters.
|
||||
// Apply custom filters.
|
||||
if ( ! $this->matches_custom_release_filter( $version_number, $release ) ) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -158,7 +177,7 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
$download_url = $this->find_release_download_url( $release );
|
||||
|
||||
if ( empty( $download_url ) ) {
|
||||
//The latest release doesn't have valid download URL.
|
||||
// The latest release doesn't have a valid download URL.
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -177,8 +196,10 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
}
|
||||
|
||||
/**
|
||||
* @param object $release
|
||||
* @return string|null
|
||||
* Locate the download URL for a release asset.
|
||||
*
|
||||
* @param object $release The release object.
|
||||
* @return string|null The download URL or null if not found.
|
||||
*/
|
||||
protected function find_release_download_url( $release ) {
|
||||
|
||||
|
|
@ -186,7 +207,7 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
|
||||
if ( isset( $release->assets, $release->assets->links ) ) {
|
||||
|
||||
//Use the first asset link where the URL matches the filter.
|
||||
// Use the first asset link that matches the filter.
|
||||
foreach ( $release->assets->links as $link ) {
|
||||
|
||||
if ( $this->matches_asset_filter( $link ) ) {
|
||||
|
|
@ -196,12 +217,12 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
}
|
||||
|
||||
if ( Api::REQUIRE_RELEASE_ASSETS === $this->release_asset_preference ) {
|
||||
//Falling back to source archives is not allowed, so give up.
|
||||
// Do not fall back to source archives, so return null.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Use the first source code archive that's in ZIP format.
|
||||
// Use the first source code archive in ZIP format.
|
||||
foreach ( $release->assets->sources as $source ) {
|
||||
|
||||
if ( isset( $source->format ) && ( 'zip' === $source->format ) ) {
|
||||
|
|
@ -213,9 +234,9 @@ if ( ! class_exists( GitLabApi::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 latest tag or null if not found.
|
||||
*/
|
||||
public function get_latest_tag() {
|
||||
$tags = $this->api( '/:id/repository/tags' );
|
||||
|
|
@ -243,10 +264,10 @@ if ( ! class_exists( GitLabApi::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( '/:id/repository/branches/' . $branch_name );
|
||||
|
|
@ -271,10 +292,10 @@ if ( ! class_exists( GitLabApi::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 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 ) {
|
||||
$commits = $this->api( '/:id/repository/commits/', array( 'ref_name' => $ref ) );
|
||||
|
|
@ -287,11 +308,11 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
}
|
||||
|
||||
/**
|
||||
* Perform a GitLab API request.
|
||||
* Execute a GitLab API request.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $query_params
|
||||
* @return mixed|WP_Error
|
||||
* @param string $url The API endpoint URL.
|
||||
* @param array $query_params The query parameters for the request.
|
||||
* @return mixed|WP_Error The API response or WP_Error on failure.
|
||||
*/
|
||||
protected function api( $url, $query_params = array(), $override_url = false ) {
|
||||
|
||||
|
|
@ -338,11 +359,11 @@ if ( ! class_exists( GitLabApi::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 The query parameters for the request.
|
||||
* @return string The fully qualified URL.
|
||||
*/
|
||||
protected function build_api_url( $url, $query_params ) {
|
||||
$variables = array(
|
||||
|
|
@ -366,11 +387,11 @@ if ( ! class_exists( GitLabApi::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 name.
|
||||
* @param string $ref The reference name (e.g., branch or tag).
|
||||
* @return null|string The file contents or null if the file doesn't exist or there's an error.
|
||||
*/
|
||||
public function get_remote_file( $path, $ref = 'main' ) {
|
||||
$response = $this->api( '/:id/repository/files/' . $path, array( 'ref' => $ref ) );
|
||||
|
|
@ -385,8 +406,8 @@ if ( ! class_exists( GitLabApi::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(
|
||||
|
|
@ -401,19 +422,28 @@ if ( ! class_exists( GitLabApi::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
|
||||
*/
|
||||
public function get_tag( $tag_name ) {
|
||||
throw new LogicException( 'The ' . __METHOD__ . ' method is not implemented and should not be used.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the strategies for detecting updates.
|
||||
*
|
||||
* @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 ) ) {
|
||||
if (
|
||||
( 'main' === $config_branch ) || ( 'master' === $config_branch ) &&
|
||||
( ! defined( 'PUC_FORCE_BRANCH' ) || ! (bool) ( constant( 'PUC_FORCE_BRANCH' ) ) )
|
||||
) {
|
||||
$strategies[ self::STRATEGY_LATEST_RELEASE ] = array( $this, 'get_latest_release' );
|
||||
$strategies[ self::STRATEGY_LATEST_TAG ] = array( $this, 'get_latest_tag' );
|
||||
}
|
||||
|
|
@ -425,12 +455,24 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
return $strategies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authentication credentials.
|
||||
*
|
||||
* @param string $credentials The authentication credentials.
|
||||
* @return void
|
||||
*/
|
||||
public function set_authentication( $credentials ) {
|
||||
parent::set_authentication( $credentials );
|
||||
|
||||
$this->access_token = is_string( $credentials ) ? $credentials : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the filterable asset name.
|
||||
*
|
||||
* @param object $release_asset The release asset object.
|
||||
* @return string|null The asset name or null if not found.
|
||||
*/
|
||||
protected function get_filterable_asset_name( $release_asset ) {
|
||||
|
||||
if ( isset( $release_asset->url ) ) {
|
||||
|
|
@ -443,7 +485,7 @@ if ( ! class_exists( GitLabApi::class, false ) ) :
|
|||
/**
|
||||
* Generate the value of the "Authorization" header.
|
||||
*
|
||||
* @return string
|
||||
* @return string The authorization header value.
|
||||
*/
|
||||
public function get_authorization_headers() {
|
||||
return array( 'PRIVATE-TOKEN' => $this->access_token );
|
||||
|
|
|
|||
|
|
@ -5,43 +5,55 @@ namespace Anyape\PackageUpdateChecker\Vcs;
|
|||
if ( ! class_exists( Reference::class, false ) ) :
|
||||
|
||||
/**
|
||||
* This class represents a VCS branch or tag. It's intended as a read only, short-lived container
|
||||
* that only exists to provide a limited degree of type checking.
|
||||
* Class Reference
|
||||
*
|
||||
* This class represents a VCS branch or tag. It serves as a read-only, temporary container
|
||||
* that provides a limited degree of type checking.
|
||||
*
|
||||
* @property string $name
|
||||
* @property string|null version
|
||||
* @property string|null $version
|
||||
* @property string $download_url
|
||||
* @property string $updated
|
||||
*
|
||||
* @property int|null $downloadCount
|
||||
*/
|
||||
class Reference {
|
||||
|
||||
private $properties = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $properties The properties to initialize the reference with.
|
||||
*/
|
||||
public function __construct( $properties = array() ) {
|
||||
$this->properties = $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed|null
|
||||
* Magic getter method.
|
||||
*
|
||||
* @param string $name The property name.
|
||||
* @return mixed|null The property value or null if it doesn't exist.
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
return array_key_exists( $name, $this->properties ) ? $this->properties[ $name ] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* Magic setter method.
|
||||
*
|
||||
* @param string $name The property name.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function __set( $name, $value ) {
|
||||
$this->properties[ $name ] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
* Magic isset method.
|
||||
*
|
||||
* @param string $name The property name.
|
||||
* @return bool True if the property is set, false otherwise.
|
||||
*/
|
||||
public function __isset( $name ) {
|
||||
return isset( $this->properties[ $name ] );
|
||||
|
|
|
|||
|
|
@ -4,21 +4,33 @@ namespace Anyape\PackageUpdateChecker\Vcs;
|
|||
|
||||
if ( ! trait_exists( ReleaseAssetSupport::class, false ) ) :
|
||||
|
||||
/**
|
||||
* Trait ReleaseAssetSupport
|
||||
*
|
||||
* Provides functionality for handling release assets in version control systems.
|
||||
* Implements methods for enabling, disabling, and filtering release assets
|
||||
* during the update process.
|
||||
*/
|
||||
trait ReleaseAssetSupport {
|
||||
|
||||
/**
|
||||
* @var bool Whether to download release assets instead of the auto-generated
|
||||
* source code archives.
|
||||
* Whether to download release assets instead of the auto-generated source code archives.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $release_assets_enabled = false;
|
||||
/**
|
||||
* @var string|null Regular expression that's used to filter release assets
|
||||
* by file name or URL. Optional.
|
||||
* Regular expression that's used to filter release assets by file name or URL.
|
||||
*
|
||||
* @var string|null Optional regular expression for asset filtering.
|
||||
*/
|
||||
protected $asset_filter_regex = null;
|
||||
/**
|
||||
* How to handle releases that don't have any matching release assets.
|
||||
*
|
||||
* Controls the behavior when no matching assets are found in a release.
|
||||
* Uses Api::PREFER_RELEASE_ASSETS constant as default.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $release_asset_preference = Api::PREFER_RELEASE_ASSETS;
|
||||
|
|
@ -29,10 +41,11 @@ if ( ! trait_exists( ReleaseAssetSupport::class, false ) ) :
|
|||
* If the latest release contains no usable assets, the update checker
|
||||
* will fall back to using the automatically generated ZIP archive.
|
||||
*
|
||||
* @param string|null $nameRegex Optional. Use only those assets where
|
||||
* @param string|null $name_regex Optional. Use only those assets where
|
||||
* the file name or URL matches this regex.
|
||||
* @param int $preference Optional. How to handle releases that don't have
|
||||
* any matching release assets.
|
||||
* @return void
|
||||
*/
|
||||
public function enable_release_assets( $name_regex = null, $preference = Api::PREFER_RELEASE_ASSETS ) {
|
||||
$this->release_assets_enabled = true;
|
||||
|
|
@ -43,6 +56,8 @@ if ( ! trait_exists( ReleaseAssetSupport::class, false ) ) :
|
|||
/**
|
||||
* Disable release assets.
|
||||
*
|
||||
* Disables the use of release assets and clears any existing asset filters.
|
||||
*
|
||||
* @return void
|
||||
* @noinspection PhpUnused -- Public API
|
||||
*/
|
||||
|
|
@ -54,8 +69,11 @@ if ( ! trait_exists( ReleaseAssetSupport::class, false ) ) :
|
|||
/**
|
||||
* Does the specified asset match the name regex?
|
||||
*
|
||||
* @param mixed $releaseAsset Data type and structure depend on the host/API.
|
||||
* @return bool
|
||||
* Checks if a given release asset matches the configured name regex filter.
|
||||
* If no filter is set, accepts all assets by default.
|
||||
*
|
||||
* @param mixed $release_asset Data type and structure depend on the host/API.
|
||||
* @return bool True if the asset matches the filter or if no filter is set.
|
||||
*/
|
||||
protected function matches_asset_filter( $release_asset ) {
|
||||
|
||||
|
|
@ -76,8 +94,8 @@ if ( ! trait_exists( ReleaseAssetSupport::class, false ) ) :
|
|||
/**
|
||||
* Get the part of asset data that will be checked against the filter regex.
|
||||
*
|
||||
* @param mixed $releaseAsset
|
||||
* @return string|null
|
||||
* @param mixed $release_asset The release asset object to extract the name from.
|
||||
* @return string|null The filterable name of the asset or null if not available.
|
||||
*/
|
||||
abstract protected function get_filterable_asset_name( $release_asset );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,30 @@ namespace Anyape\PackageUpdateChecker\Vcs;
|
|||
|
||||
if ( ! trait_exists( ReleaseFilteringFeature::class, false ) ) :
|
||||
|
||||
/**
|
||||
* Trait ReleaseFilteringFeature
|
||||
*
|
||||
* Provides functionality for filtering VCS releases based on version numbers,
|
||||
* custom callbacks, and release types. Allows for flexible release selection
|
||||
* through customizable filtering mechanisms.
|
||||
*/
|
||||
trait ReleaseFilteringFeature {
|
||||
|
||||
/**
|
||||
* Callback function for custom release filtering.
|
||||
*
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $release_filter_callback = null;
|
||||
/**
|
||||
* Maximum number of releases to check during filtering.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $release_filter_max_releases = 1;
|
||||
/**
|
||||
* Release filtering type setting.
|
||||
*
|
||||
* @var string One of the Api::RELEASE_FILTER_* constants.
|
||||
*/
|
||||
protected $release_filter_by_type = Api::RELEASE_FILTER_SKIP_PRERELEASE;
|
||||
|
|
@ -26,10 +39,11 @@ if ( ! trait_exists( ReleaseFilteringFeature::class, false ) ) :
|
|||
*
|
||||
* @param callable $callback A callback that accepts a version number and a release
|
||||
* object, and returns a boolean.
|
||||
* @param int $releaseTypes One of the Api::RELEASE_FILTER_* constants.
|
||||
* @param int $maxReleases Optional. The maximum number of recent releases to examine
|
||||
* @param int $release_types One of the Api::RELEASE_FILTER_* constants.
|
||||
* @param int $max_releases Optional. The maximum number of recent releases to examine
|
||||
* when trying to find a release that matches the filter. 1 to 100.
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException When max_releases is not between 1 and 100.
|
||||
*/
|
||||
public function set_release_filter(
|
||||
$callback,
|
||||
|
|
@ -61,11 +75,11 @@ if ( ! trait_exists( ReleaseFilteringFeature::class, false ) ) :
|
|||
}
|
||||
|
||||
/**
|
||||
* Filter releases by their version number.
|
||||
* Filter releases by their version number using a regular expression.
|
||||
*
|
||||
* @param string $regex A regular expression. The release version number must match this regex.
|
||||
* @param int $releaseTypes
|
||||
* @param int $maxReleasesToExamine
|
||||
* @param string $regex A regular expression pattern to match version numbers.
|
||||
* @param int $release_types Type of releases to filter (Api::RELEASE_FILTER_*).
|
||||
* @param int $max_releases_to_examine Maximum number of releases to check.
|
||||
* @return $this
|
||||
* @noinspection PhpUnused -- Public API
|
||||
*/
|
||||
|
|
@ -84,9 +98,11 @@ if ( ! trait_exists( ReleaseFilteringFeature::class, false ) ) :
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $versionNumber The detected release version number.
|
||||
* @param object $releaseObject Varies depending on the host/API.
|
||||
* @return bool
|
||||
* Checks if a specific version number and release object match the custom filter criteria.
|
||||
*
|
||||
* @param string $version_number The detected release version number.
|
||||
* @param object $release_object Release information object from the API.
|
||||
* @return bool True if the release matches the filter criteria, false otherwise.
|
||||
*/
|
||||
protected function matches_custom_release_filter( $version_number, $release_object ) {
|
||||
|
||||
|
|
@ -98,7 +114,9 @@ if ( ! trait_exists( ReleaseFilteringFeature::class, false ) ) :
|
|||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* Determines if pre-release versions should be excluded from updates.
|
||||
*
|
||||
* @return bool True if pre-releases should be skipped, false otherwise.
|
||||
*/
|
||||
protected function should_skip_pre_releases() {
|
||||
//Maybe this could be a bitfield in the future, if we need to support more release types.
|
||||
|
|
@ -106,7 +124,9 @@ if ( ! trait_exists( ReleaseFilteringFeature::class, false ) ) :
|
|||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* Checks if a custom release filter has been set and is callable.
|
||||
*
|
||||
* @return bool True if a custom filter is set and callable, false otherwise.
|
||||
*/
|
||||
protected function has_custom_release_filter() {
|
||||
return isset( $this->release_filter_callback ) && is_callable( $this->release_filter_callback );
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ namespace Anyape\PackageUpdateChecker\Vcs;
|
|||
|
||||
if ( ! trait_exists( VcsCheckerMethods::class, false ) ) :
|
||||
|
||||
/**
|
||||
* Trait VcsCheckerMethods
|
||||
*
|
||||
* Provides common functionality for version control system (VCS) update checking.
|
||||
* Implements methods for branch management, authentication, and configuration display.
|
||||
*/
|
||||
trait VcsCheckerMethods {
|
||||
|
||||
/**
|
||||
|
|
@ -15,6 +21,12 @@ if ( ! trait_exists( VcsCheckerMethods::class, false ) ) :
|
|||
*/
|
||||
protected $api = null;
|
||||
|
||||
/**
|
||||
* Sets the branch to check for updates.
|
||||
*
|
||||
* @param string $branch The branch name to set.
|
||||
* @return $this For method chaining.
|
||||
*/
|
||||
public function set_branch( $branch ) {
|
||||
$this->branch = $branch;
|
||||
|
||||
|
|
@ -24,8 +36,8 @@ if ( ! trait_exists( VcsCheckerMethods::class, false ) ) :
|
|||
/**
|
||||
* Set authentication credentials.
|
||||
*
|
||||
* @param array|string $credentials
|
||||
* @return $this
|
||||
* @param array|string $credentials Authentication credentials for the VCS API.
|
||||
* @return $this For method chaining.
|
||||
*/
|
||||
public function set_authentication( $credentials ) {
|
||||
$this->api->set_authentication( $credentials );
|
||||
|
|
@ -34,12 +46,20 @@ if ( ! trait_exists( VcsCheckerMethods::class, false ) ) :
|
|||
}
|
||||
|
||||
/**
|
||||
* @return Api
|
||||
* Gets the VCS API client instance.
|
||||
*
|
||||
* @return Api The VCS API client instance.
|
||||
*/
|
||||
public function get_vcs_api() {
|
||||
return $this->api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the configuration information in the given panel.
|
||||
*
|
||||
* @param object $panel The panel object used to display configuration.
|
||||
* @return void
|
||||
*/
|
||||
public function on_display_configuration( $panel ) {
|
||||
parent::on_display_configuration( $panel );
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
* WP Update Migrate
|
||||
* WordPress plugins and themes update path library.
|
||||
*
|
||||
* @author Alexandre Froger
|
||||
* @version 1.5
|
||||
* @package UpdatePulseServer
|
||||
* @subpackage WP_Update_Migrate
|
||||
* @version 1.5.0
|
||||
*/
|
||||
|
||||
/*================================================================================================ */
|
||||
|
|
@ -73,23 +74,81 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
|
||||
if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
||||
|
||||
/**
|
||||
* WP_Update_Migrate class
|
||||
*
|
||||
* Handles the migration and update process for WordPress plugins and themes.
|
||||
*/
|
||||
class WP_Update_Migrate {
|
||||
|
||||
/**
|
||||
* The current version of the WP_Update_Migrate class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '1.5.0';
|
||||
|
||||
/**
|
||||
* @var string Information about the failed update.
|
||||
*/
|
||||
protected $failed_update_info;
|
||||
|
||||
/**
|
||||
* @var string Information about the successful update.
|
||||
*/
|
||||
protected $success_update_info;
|
||||
|
||||
/**
|
||||
* @var string The name of the package.
|
||||
*/
|
||||
protected $package_name;
|
||||
|
||||
/**
|
||||
* @var string The prefix of the package.
|
||||
*/
|
||||
protected $package_prefix;
|
||||
|
||||
/**
|
||||
* @var string The directory of the package.
|
||||
*/
|
||||
protected $package_dir;
|
||||
|
||||
/**
|
||||
* @var string The version to update to.
|
||||
*/
|
||||
protected $to_version;
|
||||
|
||||
/**
|
||||
* @var string The version to update from.
|
||||
*/
|
||||
protected $from_version;
|
||||
|
||||
/**
|
||||
* @var bool The result of the update.
|
||||
*/
|
||||
protected $update_result;
|
||||
|
||||
/**
|
||||
* @var string The type of the package (plugin or theme).
|
||||
*/
|
||||
protected $package_type;
|
||||
|
||||
/**
|
||||
* @var string The handle of the package.
|
||||
*/
|
||||
protected $package_handle;
|
||||
|
||||
/**
|
||||
* @var WP_Update_Migrate The instance of the class.
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $package_handle The handle of the package.
|
||||
* @param string $package_prefix The prefix of the package.
|
||||
*/
|
||||
private function __construct( $package_handle, $package_prefix ) {
|
||||
|
||||
if ( ! wp_doing_ajax() ) {
|
||||
|
|
@ -105,6 +164,13 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
|||
wp_cache_set( $package_prefix, $this, 'wp-update-migrate' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*
|
||||
* @param string $package_handle The handle of the package.
|
||||
* @param string $package_prefix The prefix of the package.
|
||||
* @return WP_Update_Migrate The instance of the class.
|
||||
*/
|
||||
public static function get_instance( $package_handle, $package_prefix ) {
|
||||
wp_cache_add_non_persistent_groups( 'wp-update-migrate' );
|
||||
|
||||
|
|
@ -117,10 +183,18 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
|||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get result
|
||||
*
|
||||
* @return bool The result of the update.
|
||||
*/
|
||||
public function get_result() {
|
||||
return $this->update_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the package
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
if ( 'plugin' === $this->package_type ) {
|
||||
|
|
@ -161,24 +235,35 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display update failed notice
|
||||
*/
|
||||
public function update_failed_notice() {
|
||||
$class = 'notice notice-error is-dismissible';
|
||||
$message = '<p>' . $this->failed_update_info . '</p>';
|
||||
// translators: %1$s is the package type
|
||||
// Translators: %1$s is the package type
|
||||
$message .= '<p>' . sprintf( esc_html__( 'The %1$s may not have any effect until the issues are resolved.', 'wp-update-migrate' ), $this->package_type ) . '</p>';
|
||||
|
||||
echo wp_kses_post( sprintf( '<div class="%1$s">%2$s</div>', $class, $message ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display update success notice
|
||||
*/
|
||||
public function update_success_notice() {
|
||||
$class = 'notice notice-success is-dismissible';
|
||||
// translators: %1$s is the package version to update to
|
||||
// Translators: %1$s is the package version to update to
|
||||
$title = $this->package_name . ' - ' . sprintf( __( 'Success updating to version %1$s', 'wp-update-migrate' ), $this->to_version );
|
||||
$message = '<h3>' . $title . '</h3><p>' . $this->success_update_info . '</p>';
|
||||
|
||||
echo wp_kses_post( sprintf( '<div class="%1$s">%2$s</div>', $class, $message ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content directory
|
||||
*
|
||||
* @return string The content directory path.
|
||||
*/
|
||||
protected static function get_content_dir() {
|
||||
WP_Filesystem();
|
||||
|
||||
|
|
@ -191,6 +276,11 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
|||
return $wp_filesystem->wp_content_dir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build update path
|
||||
*
|
||||
* @return array The update path.
|
||||
*/
|
||||
protected function build_update_path() {
|
||||
$file_list = glob( $this->package_dir . 'updates' . DIRECTORY_SEPARATOR . '*.php' );
|
||||
$update_path = array();
|
||||
|
|
@ -207,6 +297,11 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
|||
return $update_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the package
|
||||
*
|
||||
* @return mixed The result of the update.
|
||||
*/
|
||||
protected function update() {
|
||||
$update_path = $this->build_update_path();
|
||||
$result = true;
|
||||
|
|
@ -259,6 +354,12 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the update for a specific version
|
||||
*
|
||||
* @param string $version The version to update to.
|
||||
* @return mixed The result of the update.
|
||||
*/
|
||||
protected function do_update( $version ) {
|
||||
$error = false;
|
||||
$result = false;
|
||||
|
|
@ -278,7 +379,7 @@ if ( ! class_exists( 'WP_Update_Migrate' ) ) {
|
|||
$error = new WP_Error(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
// translators: %1$s is the missing function name, %2$s is the package type, %3$s is the path to WP_CONTENT_DIR
|
||||
// Translators: %1$s is the missing function name, %2$s is the package type, %3$s is the path to WP_CONTENT_DIR
|
||||
__( '<br/>The update failed: function <code>%1$s</code> not found.<br/>Please restore the previously used version of the %2$s, or delete the %2$s and its files in the <code>%2$s</code> 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 <code>%1$s_%2$s_version</code> 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.<br/>Please restore the previously used version of the %2$s, or delete the %2$s and its files in the <code>%1$s</code> 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 . '<br/>';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
|||
115
readme.txt
115
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.2
|
||||
Stable tag: 1.0.10
|
||||
Requires PHP: 8.0
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
|
@ -37,7 +38,7 @@ 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!
|
||||
|
|
@ -46,6 +47,78 @@ This plugin adds the following major features to WordPress:
|
|||
* **API:** UpdatePulse Server provides APIs to manage packages and licenses. The APIs keys are secured with a system of tokens: the API keys are never shared over the network, acquiring a token requires signed payloads, and the tokens have a limited lifetime. For more details about tokens and security, see [the Nonce API documentation](https://github.com/anyape/updatepulse-server/blob/main/docs/misc.md#nonce-api).
|
||||
|
||||
To connect their plugins or themes and UpdatePulse Server, developers can find integration examples in the [UpdatePulse Server Integration Examples](https://github.com/Anyape/updatepulse-server-integration) repository - theme and plugin examples rely heavily on the popular [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker) by [Yahnis Elsts](https://github.com/YahnisElsts).
|
||||
== Companion Plugins ==
|
||||
|
||||
The following plugins are compatible with UpdatePulse Server and can be used to extend its functionality:
|
||||
* [Updatepulse Blocks](https://store.anyape.com/product/updatepulse-blocks/?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 ==
|
||||
|
||||
|
|
@ -65,6 +138,44 @@ 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
|
||||
|
|
|
|||
81
tests.php
81
tests.php
|
|
@ -1,13 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* UpdatePulse Server Testing Utilities
|
||||
*
|
||||
* This file provides testing and debugging functionality for the UpdatePulse Server plugin.
|
||||
* It includes tools for performance measurement, logging, and monitoring resource usage.
|
||||
* Only loaded when the UPSERV_ENABLE_TEST constant is defined and true.
|
||||
*
|
||||
* @package UPServ
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Ensure required dependencies are available
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
|
||||
// Global variables for testing configuration
|
||||
global $upserv_functions_to_test_params, $upserv_functions_to_test, $upserv_actions_to_test, $upserv_filters_to_test, $upserv_output_log, $upserv_tests_show_queries_details, $upserv_tests_show_scripts_details;
|
||||
|
||||
// Configure logging preferences - last value overrides previous
|
||||
$upserv_output_log = 'filelog';
|
||||
$upserv_output_log = 'serverlog';
|
||||
|
||||
// Configure detail level for test output
|
||||
$upserv_tests_show_queries_details = true;
|
||||
$upserv_tests_show_scripts_details = true;
|
||||
|
||||
/**
|
||||
* Log messages or data to a file or server log
|
||||
*
|
||||
* Writes log entries with timestamps to either the server error log
|
||||
* or a dedicated test log file in the plugin's logs directory.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $message The message to log
|
||||
* @param mixed $array_or_object Optional data structure to log
|
||||
* @return void
|
||||
*/
|
||||
function upserv_tests_log( $message, $array_or_object = null ) {
|
||||
global $upserv_output_log;
|
||||
|
||||
|
|
@ -45,6 +72,16 @@ function upserv_tests_log( $message, $array_or_object = null ) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format memory usage for human readability
|
||||
*
|
||||
* Converts raw byte values to a human-readable format with appropriate units (B, K, M, G, T).
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $bytes The number of bytes to format
|
||||
* @param int $precision The number of decimal places to display
|
||||
* @return string Formatted memory usage with units
|
||||
*/
|
||||
function upserv_get_formatted_memory( $bytes, $precision = 2 ) {
|
||||
$units = array( 'B', 'K', 'M', 'G', 'T' );
|
||||
$bytes = max( $bytes, 0 );
|
||||
|
|
@ -55,22 +92,47 @@ function upserv_get_formatted_memory( $bytes, $precision = 2 ) {
|
|||
return round( $bytes, $precision ) . ' ' . $units[ $pow ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Log performance statistics for the current request
|
||||
*
|
||||
* Collects and logs detailed performance metrics including:
|
||||
* - Memory usage (total and plugin-specific)
|
||||
* - Execution time
|
||||
* - Database query counts and details
|
||||
* - Included script files
|
||||
*
|
||||
* This function runs at the end of the request during the shutdown action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @return void
|
||||
*/
|
||||
function upserv_performance_stats_log() {
|
||||
global $wpdb, $upserv_mem_before, $upserv_scripts_before, $upserv_queries_before, $upserv_tests_show_queries_details, $upserv_tests_show_scripts_details;
|
||||
|
||||
// Collect performance metrics
|
||||
$mem_after = memory_get_peak_usage();
|
||||
$query_list = array();
|
||||
$scripts_after = get_included_files();
|
||||
$scripts = array_diff( $scripts_after, $upserv_scripts_before );
|
||||
$query_stats = 'Number of queries executed by the plugin: ' . ( count( $wpdb->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' );
|
||||
|
|
|
|||
|
|
@ -1,16 +1,42 @@
|
|||
<?php
|
||||
/**
|
||||
* UpdatePulse Server Uninstall Script
|
||||
*
|
||||
* This file runs when the plugin is uninstalled through the WordPress admin.
|
||||
* It cleans up all data, files, cron jobs, and database tables created by the plugin.
|
||||
*
|
||||
* @package UPServ
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access and ensure this is a proper WordPress uninstall request
|
||||
if ( ! defined( 'ABSPATH' ) || ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
||||
exit; // Exit if accessed directly}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main uninstallation function for the UpdatePulse Server plugin.
|
||||
*
|
||||
* Removes all plugin data including:
|
||||
* - Scheduled cron jobs
|
||||
* - Must-use plugin optimizer files
|
||||
* - Upload directory files and folders
|
||||
* - Action Scheduler entries
|
||||
* - Database options
|
||||
* - Custom database tables
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @return void
|
||||
*/
|
||||
function upserv_uninstall() {
|
||||
global $wpdb;
|
||||
|
||||
// Initialize WordPress filesystem
|
||||
WP_Filesystem();
|
||||
|
||||
global $wp_filesystem;
|
||||
|
||||
// Remove all scheduled cron jobs belonging to the plugin
|
||||
$cron = get_option( 'cron' );
|
||||
|
||||
foreach ( $cron as $job ) {
|
||||
|
|
@ -27,6 +53,7 @@ function upserv_uninstall() {
|
|||
}
|
||||
}
|
||||
|
||||
// Delete any must-use plugin optimizer files
|
||||
$mu_plugin_path = trailingslashit( wp_normalize_path( WPMU_PLUGIN_DIR ) );
|
||||
$mu_plugins = $wp_filesystem->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();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: UpdatePulse Server
|
||||
* Plugin URI: https://github.com/anyape/updatepulse-server/
|
||||
* Description: Run your own update server.
|
||||
* Version: 1.0.2
|
||||
* Version: 1.0.10
|
||||
* Author: Alexandre Froger
|
||||
* Author URI: https://froger.me/
|
||||
* License: GPLv2 or later
|
||||
|
|
@ -11,12 +11,19 @@
|
|||
* Domain Path: /languages
|
||||
*
|
||||
* @package UPServ
|
||||
*
|
||||
* UpdatePulse Server is a WordPress plugin that enables users to host their own
|
||||
* update server for WordPress themes and plugins, or generic packages.
|
||||
* It handles license verification, package distribution, and update management
|
||||
* through a set of APIs and managers.
|
||||
*/
|
||||
|
||||
// Exit if accessed directly to prevent unauthorized access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
// Skip processing during WordPress heartbeat requests for performance optimization
|
||||
if (
|
||||
defined( 'DOING_AJAX' ) &&
|
||||
DOING_AJAX &&
|
||||
|
|
@ -25,6 +32,7 @@ if (
|
|||
return;
|
||||
}
|
||||
|
||||
// Store performance metrics when debugging is enabled
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
|
||||
global $wpdb, $upserv_mem_before, $upserv_scripts_before, $upserv_queries_before;
|
||||
|
||||
|
|
@ -33,6 +41,7 @@ if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'SAVEQUERIES' ) && SAVEQUERIE
|
|||
$upserv_queries_before = $wpdb->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;
|
||||
|
|
@ -47,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__ ) );
|
||||
}
|
||||
|
|
@ -63,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 ) {
|
||||
|
|
@ -72,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();
|
||||
|
||||
|
|
@ -85,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()
|
||||
|
|
@ -120,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' ) ) {
|
||||
|
|
@ -136,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 );
|
||||
}
|
||||
|
|
@ -153,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() ||
|
||||
|
|
@ -180,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' ) ) {
|
||||
|
|
@ -217,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' );
|
||||
|
||||
|
|
@ -243,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() ) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue