Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error handling and logic #58

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 41 additions & 21 deletions src/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Junaidbhura\Composer\WPProPlugins;

use RuntimeException;

/**
* Http class.
*/
Expand All @@ -15,47 +17,65 @@ class Http {
/**
* POST request.
*
* @param string $url Url to POST.
* @param array $args Arguments to POST.
* @return mixed
* @param string $url URL to POST.
* @param array<string, mixed> $args Arguments to POST.
* @return string
*/
public function post( $url = '', $args = array() ) {
public function post( $url, $args = array() ) {
$curl_handle = curl_init();
curl_setopt( $curl_handle, CURLOPT_URL, $url );
curl_setopt( $curl_handle, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $curl_handle, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $curl_handle, CURLOPT_SSL_VERIFYPEER, 0 );
curl_setopt( $curl_handle, CURLOPT_SSL_VERIFYHOST, 0 );
curl_setopt( $curl_handle, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $curl_handle, CURLOPT_SSL_VERIFYHOST, false );
curl_setopt( $curl_handle, CURLOPT_CUSTOMREQUEST, 'POST' );
if ( ! empty( $args ) ) {
curl_setopt( $curl_handle, CURLOPT_POSTFIELDS, http_build_query( $args, '', '&' ) );
}
$response = curl_exec( $curl_handle );
curl_close( $curl_handle );

return $response;
return $this->request( $curl_handle );
}

/**
* GET request.
*
* @param string $url Base URL for requeset (without params)
* @param array $args Arguments to add to request
* @return mixed
* @param string $url URL to GET (without params).
* @param array<string, mixed> $args Arguments to add to request.
* @return string
*/
public function get( $url = '', $args = array() ) {
$query_string = '';
public function get( $url, $args = array() ) {
if ( ! empty( $args ) ) {
$url .= '?' . http_build_query( $args, '', '&' );
}

$curl_handle = curl_init();
curl_setopt( $curl_handle, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $curl_handle, CURLOPT_URL, $url );

return $this->request( $curl_handle );
}

/**
* @param \CurlHandle|resource $curl_handle The cURL handler.
* @throws RuntimeException If the request failed or the response is invalid.
* @return string The response body.
*/
protected function request( $curl_handle ) {
curl_setopt( $curl_handle, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $curl_handle, CURLOPT_FOLLOWLOCATION, true );
if ( ! empty( $args ) ) {
$query_string = http_build_query( $args, '', '&' );
}
curl_setopt( $curl_handle, CURLOPT_URL, $url . '?' . $query_string );
curl_setopt( $curl_handle, CURLOPT_FAILONERROR, true );

$response = curl_exec( $curl_handle );

$curl_errno = curl_errno( $curl_handle );
$curl_error = curl_error( $curl_handle );
curl_close( $curl_handle );

if ( false === $response ) {
throw new RuntimeException( sprintf(
'cURL error (%d): %s',
$curl_errno,
$curl_error
), $curl_errno );
}

return $response;
}

Expand Down
99 changes: 86 additions & 13 deletions src/Plugins/AbstractEddPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,117 @@
namespace Junaidbhura\Composer\WPProPlugins\Plugins;

use Composer\Semver\Semver;
use Exception;
use UnexpectedValueException;

/**
* AbstractEddPlugin class.
*/
abstract class AbstractEddPlugin extends AbstractPlugin {

/**
* Get the download URL for this plugin from its API.
*
* @return string
*/
abstract protected function getDownloadUrlFromApi();

/**
* Get the download URL for this plugin.
*
* @param array<string, mixed> $response The EDD API response.
* @throws UnexpectedValueException If the response is invalid.
* @return string
*/
protected function extractDownloadUrl( array $response ) {
if ( empty( $response['download_link'] ) || ! is_string( $response['download_link'] ) ) {
public function getDownloadUrl() {
try {
$response = $this->getDownloadUrlFromApi();
} catch ( Exception $e ) {
$details = [];

$error = $e->getMessage();
if ( $error ) {
$details[] = 'HTTP Error: ' . $error;
}

$message = sprintf(
'Could not query API for package %s. Please try again later.',
$this->getPackageName()
);

if ( $details ) {
$message .= PHP_EOL . PHP_EOL . implode( PHP_EOL . PHP_EOL, $details );
}

throw new UnexpectedValueException( $message );
}

try {
/**
* @todo When the Composer plugin drops support for PHP 5,
* use the `json_decode()` function's `JSON_THROW_ON_ERROR` flag,
* introduced in PHP 7.3, to simplify error handling.
*/
$data = json_decode( $response, true );

if ( json_last_error() !== JSON_ERROR_NONE ) {
throw new Exception(
json_last_error_msg(),
json_last_error()
);
}

if ( ! is_array( $data ) ) {
throw new UnexpectedValueException(
'Expected a data structure'
);
}
} catch ( Exception $e ) {
$details = [
'json_decode(): ' . $e->getMessage(),
];

$response_length = mb_strlen( $response );
if ( $response_length > 0 ) {
$details[] = ' ' . mb_substr( $response, 0, 100 ) . ( $response_length > 100 ? '...' : '' );
}

$message = sprintf(
'Expected a data structure from API for package %s. Please try again later.',
$this->getPackageName()
);

if ( $details ) {
$message .= PHP_EOL . PHP_EOL . implode( PHP_EOL . PHP_EOL, $details );
}

throw new UnexpectedValueException( $message );
}

if ( empty( $data['download_link'] ) || ! is_string( $data['download_link'] ) ) {
throw new UnexpectedValueException( sprintf(
'Expected a valid download URL for package %s',
'junaidbhura/' . $this->slug
'Expected a valid download URL from API for package %s',
$this->getPackageName()
) );
}

if ( empty( $response['new_version'] ) || ! is_scalar( $response['new_version'] ) ) {
if ( empty( $data['new_version'] ) || ! is_scalar( $data['new_version'] ) ) {
throw new UnexpectedValueException( sprintf(
'Expected a valid download version number for package %s',
'junaidbhura/' . $this->slug
'Expected a valid download version number from API for package %s',
$this->getPackageName()
) );
}

if ( ! Semver::satisfies( $response['new_version'], $this->version ) ) {
// If no version is specified, we are fetching the latest version.
if ( $this->version && ! Semver::satisfies( $data['new_version'], $this->version ) ) {
throw new UnexpectedValueException( sprintf(
'Expected download version (%s) to match installed version (%s) of package %s',
$response['new_version'],
'Expected download version from API (%s) to match installed version (%s) of package %s',
$data['new_version'],
$this->version,
'junaidbhura/' . $this->slug
$this->getPackageName()
) );
}

return $response['download_link'];
return $data['download_link'];
}

}
16 changes: 13 additions & 3 deletions src/Plugins/AbstractPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@ abstract class AbstractPlugin {
/**
* The version number of the plugin to download.
*
* @var string Version number.
* @var string
*/
protected $version = '';

/**
* The slug of which plugin to download.
* The name of the plugin to download.
*
* @var string Plugin slug.
* @var string
*/
protected $slug = '';

/**
* AbstractPlugin constructor.
*
* @param string $version
* @param string $slug
*/
public function __construct( $version = '', $slug = '' ) {
$this->version = $version;
Expand All @@ -43,4 +44,13 @@ public function __construct( $version = '', $slug = '' ) {
*/
abstract public function getDownloadUrl();

/**
* Get the plugin's Composer package name with vendor.
*
* @return string
*/
protected function getPackageName() {
return 'junaidbhura/' . $this->slug;
}

}
21 changes: 14 additions & 7 deletions src/Plugins/AcfExtendedPro.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,28 @@
class AcfExtendedPro extends AbstractEddPlugin {

/**
* Get the download URL for this plugin.
* Get the download URL for this plugin from its API.
*
* @return string
*/
public function getDownloadUrl() {
$http = new Http();
$response = json_decode( $http->post( 'https://acf-extended.com', array(
protected function getDownloadUrlFromApi() {
$http = new Http();

$api_query = array(
'edd_action' => 'get_version',
'license' => getenv( 'ACFE_PRO_KEY' ),
'item_name' => 'ACF Extended Pro',
'url' => getenv( 'ACFE_PRO_URL' ),
'version' => $this->version,
) ), true );
);

// If no version is specified, we are fetching the latest version.
if ( $this->version ) {
$api_query['version'] = $this->version;
}

$api_url = 'https://acf-extended.com';

return $this->extractDownloadUrl( $response );
return $http->get( $api_url, $api_query );
}

}
15 changes: 14 additions & 1 deletion src/Plugins/AcfPro.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,20 @@ class AcfPro extends AbstractPlugin {
* @return string
*/
public function getDownloadUrl() {
return 'https://connect.advancedcustomfields.com/index.php?p=pro&a=download&k=' . getenv( 'ACF_PRO_KEY' ) . '&t=' . $this->version;
$api_query = array(
'p' => 'pro',
'a' => 'download',
'k' => getenv( 'ACF_PRO_KEY' ),
);

// If no version is specified, we are fetching the latest version.
if ( $this->version ) {
$api_query['t'] = $this->version;
}

$api_url = 'https://connect.advancedcustomfields.com/index.php';

return $api_url . '?' . http_build_query( $api_query, '', '&' );
}

}
Loading