Skip to content
This repository has been archived by the owner on Mar 20, 2024. It is now read-only.

Update/include required classes #5

Merged
merged 4 commits into from
May 30, 2023
Merged
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
2 changes: 1 addition & 1 deletion bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
use NewfoldLabs\WP\Module\CustomerBluehost\CustomerBluehost;
use NewfoldLabs\WP\Module\CustomerBluehost\SiteMeta;
use NewfoldLabs\WP\ModuleLoader\Container;
use Bluehost\SiteMeta; // From plugin

use function NewfoldLabs\WP\ModuleLoader\register as registerModule;

Expand Down
16 changes: 6 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,24 @@
"only": [
"newfold-labs/*"
]
},
{
"type": "composer",
"url": "https://bluehost.github.io/satis/",
"only": [
"bluehost/*"
]
}
],
"authors": [
{
"name": "Evan Mullins",
"email": "evan@bluehost.com"
"homepage": "https://evanmullins.com"
}
],
"autoload": {
"psr-4": {
"NewFoldLabs\\WP\\Module\\CustomerBluehost\\": "includes"
"NewfoldLabs\\WP\\Module\\CustomerBluehost\\": "includes"
},
"files": [
"bootstrap.php",
"includes/CustomerBluehost.php"
"includes/AccessToken.php",
"includes/CustomerBluehost.php",
"includes/ResponseUtilities.php",
"includes/SiteMeta.php"
]
},
"require": {
Expand Down
171 changes: 171 additions & 0 deletions includes/AccessToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php
/**
* Class for managing Bluehost access tokens.
*/

// phpcs:disable PHPCompatibility.FunctionUse.NewFunctions.random_bytesFound -- WordPress provides a fallback for this function.
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- We use this for security reasons.

namespace NewfoldLabs\WP\Module\CustomerBluehost;

use NewfoldLabs\WP\Module\CustomerBluehost\SiteMeta;
use NewfoldLabs\WP\Module\CustomerBluehost\ResponseUtilities;

/**
* Class AccessToken
*
* @uses ResponseUtilities, SiteMeta
*/
class AccessToken {

/**
* Check if an access token is stored.
*
* @return bool
*/
public static function has_token() {
$token = self::get_token();

return ! empty( $token );
}

/**
* Get the stored access token, if available.
*
* @return string Access token or an empty string if not available.
*/
public static function get_token() {
$token = '';
$expiration = get_option( 'bluehost_access_token_expiration', 0 );
if ( $expiration > time() ) {
$token = get_option( 'bluehost_access_token', '' );
}

return (string) $token;
}

/**
* Get the stored user_id, if available.
*
* @return string User_id or an empty string if not available.
*/
public static function get_user() {
$user = '';
$expiration = get_option( 'bluehost_access_token_expiration', 0 );
if ( $expiration > time() ) {
$user = get_option( 'bluehost_access_user', '' );
}

return (string) $user;
}

/**
* Save an access token.
*
* @param string $token Access token
* @param int $expiration Timestamp of expiration
* @param int $user_id WordPress user id
*/
public static function set_token( $token, $expiration, $user_id ) {
update_option( 'bluehost_access_token', $token, true );
update_option( 'bluehost_access_token_expiration', $expiration, true );
update_option( 'bluehost_access_user', $user_id, true );
}

/**
* Request an access token.
*
* @return array|\WP_Error
*
* @throws \Exception The random_bytes() function may throw an Exception in some cases.
*/
public static function request_token() {

$domain = SiteMeta::get_domain();
$site_id = SiteMeta::get_id();

$hashing_algorithm = 'sha256';
$transient_name = 'authorizations_' . base64_encode( random_bytes( 12 ) );
$salt = random_bytes( 24 );
$client_secret = random_bytes( 40 );
$hashed_client_secret = hash( $hashing_algorithm, $salt . $client_secret, true );
$transient_value = sprintf(
'%s:%s:%s',
$hashing_algorithm,
base64_encode( $salt ),
base64_encode( $hashed_client_secret )
);

set_transient( $transient_name, $transient_value, MINUTE_IN_SECONDS );

return wp_remote_post(
sprintf( 'https://my.bluehost.com/siteapi/sites/%s/%s/authorizations', $domain, $site_id ),
array(
'timeout' => 45,
'body' => array(
'transient_name' => $transient_name,
'client_secret' => base64_encode( $client_secret ),
'domain' => $domain,
'site_id' => $site_id,
'scopes' => array(),
),
)
);

}

/**
* Refresh the stored token.
*
* @throws \RuntimeException On error or unexpected payload shape.
*/
public static function refresh_token() {
try {
$response = self::request_token();
$data = ResponseUtilities::parse_json_response( $response, true );
if ( isset( $data['access_token'], $data['expires_in'], $data['user_id'] ) ) {
$token = $data['access_token'];
$expires_in = (int) $data['expires_in'];
$timestamp = ResponseUtilities::get_response_timestamp( $response );
$user_id = $data['user_id'];
self::set_token( $token, $timestamp + $expires_in, $user_id );
} else {
$data['_status_code'] = wp_remote_retrieve_response_code( $response );
$data['_response'] = wp_remote_retrieve_body( $response );
throw new \RuntimeException( wp_json_encode( $data ) );
}
} catch ( \Exception $e ) {
error_log( 'Bluehost WP Plugin: Failed to fetch token: ' . $e->getMessage() );
}
}

/**
* Check for proper conditions before refreshing the token.
*/
public static function maybe_refresh_token() {

// if no token, and should refresh token, go ahead and refresh the token
if ( ! self::has_token() && self::should_refresh_token() ) {
self::refresh_token();
}

}

/**
* Check if we should refresh the token
*
* @return bool
*/
public static function should_refresh_token() {

// never when doing ajax
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return false;
}

// require either a user with permissions or a cron event
return ( current_user_can( 'manage_options' ) || wp_doing_cron() );

}

}
4 changes: 2 additions & 2 deletions includes/CustomerBluehost.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
namespace NewfoldLabs\WP\Module\CustomerBluehost;

use Bluehost\AccessToken;
use Bluehost\SiteMeta;
use NewfoldLabs\WP\Module\CustomerBluehost\SiteMeta;
use NewfoldLabs\WP\Module\CustomerBluehost\AccessToken;
use NewfoldLabs\WP\Module\Data\Helpers\Transient;

/**
Expand Down
55 changes: 55 additions & 0 deletions includes/ResponseUtilities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/**
* Class containing utility functions for working with WordPress HTTP responses.
*/

namespace NewfoldLabs\WP\Module\CustomerBluehost;

/**
* Class ResponseUtilities
*/
class ResponseUtilities {

/**
* Parse a WordPress response for JSON data.
*
* @param array $response WordPress request response
* @param bool $assoc_array Whether or not to convert objects into associative arrays.
*
* @return array An array of data
*/
public static function parse_json_response( $response, $assoc_array = false ) {
$data = array();

$body = wp_remote_retrieve_body( $response );
if ( $body ) {
$payload = json_decode( $body, $assoc_array );
if ( $payload && is_array( $payload ) ) {
$data = (array) $payload;
}
}

return $data;
}

/**
* Get response timestamp.
*
* @param array $response WordPress request response
*
* @return int Returns the timestamp or 0 on error.
*/
public static function get_response_timestamp( $response ) {
$timestamp = 0;
try {
$date = wp_remote_retrieve_header( $response, 'date' );
$date_time = new \DateTime( $date );
$timestamp = (int) $date_time->format( 'U' );
} catch ( \Exception $e ) {
trigger_error( $e->getMessage() ); // phpcs:ignore
}

return $timestamp;
}

}
50 changes: 50 additions & 0 deletions includes/SiteMeta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
/**
* Class for fetching site information as used by Bluehost.
*/

namespace NewfoldLabs\WP\Module\CustomerBluehost;

/**
* Class SiteMeta
*/
class SiteMeta {

/**
* Get the domain for the WordPress installation.
*
* @return string
*/
public static function get_domain() {
return wp_parse_url( get_home_url(), PHP_URL_HOST );
}

/**
* Get the relative filesystem path to the root of the WordPress installation.
*
* @return string
*/
public static function get_path() {
return strstr( ABSPATH, '/public_html/' );
}

/**
* Get the (Bluehost) site ID given the full path to the site root.
*
* @return string Site ID is the path encoded as UTF-8 and converted to hexadecimal.
*/
public static function get_id() {
static $id = null;
if ( is_null( $id ) ) {
$path = self::get_path();
if ( function_exists( 'iconv' ) ) {
$id = bin2hex( iconv( mb_detect_encoding( $path, mb_detect_order(), true ), 'UTF-8', $path ) );
} else {
$id = bin2hex( $path );
}
}

return $id;
}

}