Skip to content

Commit

Permalink
Merge pull request #1364 from Parsely/release/3.6.2
Browse files Browse the repository at this point in the history
  • Loading branch information
acicovic authored Feb 13, 2023
2 parents 9824fa6 + 9a64c7c commit b59a2c6
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 79 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.6.2](https://github.com/Parsely/wp-parsely/compare/3.6.1...3.6.2) - 2023-02-13

### Fixed

- Fix PHP 8 Incompatibilities ([#1362](https://github.com/Parsely/wp-parsely/pull/1362))
- Improve checks on proxy endpoints
- Fix referral distribution in Performance Details panel ([#1382](https://github.com/Parsely/wp-parsely/pull/1382))

## [3.6.1](https://github.com/Parsely/wp-parsely/compare/3.6.0...3.6.1) - 2022-12-20

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Parse.ly

Stable tag: 3.6.1
Stable tag: 3.6.2
Requires at least: 5.0
Tested up to: 6.1
Requires PHP: 7.1
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wp-parsely",
"version": "3.6.1",
"version": "3.6.2",
"private": true,
"description": "The Parse.ly plugin facilitates real-time and historical analytics to your content through a platform designed and built for digital publishing.",
"author": "parsely, hbbtstar, jblz, mikeyarce, GaryJ, parsely_mike, pauarge",
Expand Down
14 changes: 2 additions & 12 deletions src/Endpoints/class-analytics-post-detail-api-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Analytics_Post_Detail_API_Proxy extends Base_API_Proxy {
* Registers the endpoint's WP REST route.
*/
public function run(): void {
$this->register_endpoint( '/stats/post/detail' );
$this->register_endpoint( '/stats/post/detail', 'publish_posts' );
}

/**
Expand Down Expand Up @@ -74,7 +74,7 @@ static function( stdClass $item ) use ( $stats_base_url ) {
* @param float $time The time as a float number.
* @return string The resulting formatted time duration.
*/
private function get_duration( float $time ): string {
private static function get_duration( float $time ): string {
$minutes = absint( $time );
$seconds = absint( round( fmod( $time, 1 ) * 60 ) );

Expand All @@ -85,14 +85,4 @@ private function get_duration( float $time ): string {

return sprintf( '%2d:%02d', $minutes, $seconds );
}

/**
* Determines if there are enough permissions to call the endpoint.
*
* @return bool
*/
public function permission_callback(): bool {
// Unauthenticated.
return true;
}
}
12 changes: 1 addition & 11 deletions src/Endpoints/class-analytics-posts-api-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class Analytics_Posts_API_Proxy extends Base_API_Proxy {
* Registers the endpoint's WP REST route.
*/
public function run(): void {
$this->register_endpoint( '/stats/posts' );
$this->register_endpoint( '/stats/posts', 'publish_posts' );
}

/**
Expand Down Expand Up @@ -64,14 +64,4 @@ static function( stdClass $item ) use ( $date_format, $stats_base_url ) {

return $result;
}

/**
* Determines if there are enough permissions to call the endpoint.
*
* @return bool
*/
public function permission_callback(): bool {
// Unauthenticated.
return true;
}
}
32 changes: 29 additions & 3 deletions src/Endpoints/class-base-api-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ abstract class Base_API_Proxy {
*/
protected $parsely;

/**
* Capability of the user based on which we should allow access to endpoint.
*
* `null` should be used for all public endpoints.
*
* @var string|null
*/
protected $user_capability;

/**
* Proxy object which does the actual calls to the Parse.ly API.
*
Expand Down Expand Up @@ -62,7 +71,19 @@ abstract public function get_items( WP_REST_Request $request );
*
* @return bool
*/
abstract public function permission_callback(): bool;
public function permission_callback(): bool {
// This endpoint does not require any capability checks.
if ( is_null( $this->user_capability ) ) {
return true;
}

// The user has the required capability to access this endpoint.
if ( current_user_can( $this->user_capability ) ) {
return true;
}

return false;
}

/**
* Constructor.
Expand All @@ -79,9 +100,13 @@ public function __construct( Parsely $parsely, Proxy $proxy ) {
/**
* Registers the endpoint's WP REST route.
*
* @param string $endpoint The endpoint's route (e.g. /stats/posts).
* @param string $endpoint The endpoint's route (e.g. /stats/posts).
* @param string|null $user_capability Capability of the user based on which we should allow access to endpoint.
* @param bool $show_in_index Show endpoint in /wp-json view if TRUE.
*/
protected function register_endpoint( string $endpoint ): void {
protected function register_endpoint( string $endpoint, ?string $user_capability, $show_in_index = false ): void {
$this->user_capability = $user_capability;

$filter_key = trim( str_replace( '/', '_', $endpoint ), '_' );
if ( ! apply_filters( 'wp_parsely_enable_' . $filter_key . '_api_proxy', true ) ) {
return;
Expand All @@ -107,6 +132,7 @@ protected function register_endpoint( string $endpoint ): void {
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'permission_callback' ),
'args' => $get_items_args,
'show_in_index' => $show_in_index,
),
);

Expand Down
68 changes: 37 additions & 31 deletions src/Endpoints/class-referrers-post-detail-api-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final class Referrers_Post_Detail_API_Proxy extends Base_API_Proxy {
* @since 3.6.0
*/
public function run(): void {
$this->register_endpoint( '/referrers/post/detail' );
$this->register_endpoint( '/referrers/post/detail', 'publish_posts' );
}

/**
Expand Down Expand Up @@ -97,26 +97,37 @@ private function generate_referrer_types_data( array $response ): stdClass {
// Set referrer type order as it is displayed in the Parse.ly dashboard.
$referrer_type_keys = array( 'social', 'search', 'other', 'internal', 'direct' );
foreach ( $referrer_type_keys as $key ) {
$result->$key->views = 0;
$result->$key = (object) array( 'views' => 0 );
}

// Set views and views totals.
foreach ( $response as $referrer_data ) {
// Point by reference to the item to be processed, and set it to 0
// when needed in order to avoid potential PHP warnings.
$current_type_views =& $result->{ $referrer_data->type }->views;
if ( ! isset( $current_type_views ) ) {
$current_type_views = 0;
/**
* Variable.
*
* @var int
*/
$current_views = isset( $referrer_data->metrics->referrers_views ) ? $referrer_data->metrics->referrers_views : 0;
$total_referrer_views += $current_views;

/**
* Variable.
*
* @var string
*/
$current_key = isset( $referrer_data->type ) ? $referrer_data->type : '';
if ( '' !== $current_key ) {
if ( ! isset( $result->$current_key->views ) ) {
$result->$current_key = (object) array( 'views' => 0 );
}

$result->$current_key->views += $current_views;
}

// Set the values.
$current_type_views += $referrer_data->metrics->referrers_views;
$total_referrer_views += $referrer_data->metrics->referrers_views;
}

// Add direct and total views to the object.
$result->direct->views = $this->total_views - $total_referrer_views;
$result->totals->views = $this->total_views;
$result->totals = (object) array( 'views' => $this->total_views );

// Remove referrer types without views.
foreach ( $referrer_type_keys as $key ) {
Expand Down Expand Up @@ -167,11 +178,18 @@ private function generate_referrers_data(
// Set views and views totals.
$loop_count = $referrer_count > $limit ? $limit : $referrer_count;
for ( $i = 0; $i < $loop_count; $i++ ) {
$data = $response[ $i ];
$referrer_views = $data->metrics->referrers_views;

$temp_views[ $data->name ] = $referrer_views;
$totals += $referrer_views;
$data = $response[ $i ];

/**
* Variable.
*
* @var int
*/
$referrer_views = isset( $data->metrics->referrers_views ) ? $data->metrics->referrers_views : 0;
$totals += $referrer_views;
if ( isset( $data->name ) ) {
$temp_views[ $data->name ] = $referrer_views;
}
}

// If applicable, add the direct views.
Expand All @@ -187,9 +205,9 @@ private function generate_referrers_data(
// Convert temporary array to result object and add totals.
$result = new stdClass();
foreach ( $temp_views as $key => $value ) {
$result->$key->views = $value;
$result->$key = (object) array( 'views' => $value );
}
$result->totals->views = $totals;
$result->totals = (object) array( 'views' => $totals );

// Set percentages values and format numbers.
foreach ( $result as $key => $value ) {
Expand Down Expand Up @@ -227,16 +245,4 @@ private function get_i18n_percentage( int $number, int $total ) {

return number_format_i18n( $number / $total * 100, 2 );
}

/**
* Determines if there are enough permissions to call the endpoint.
*
* @since 3.6.0
*
* @return bool
*/
public function permission_callback(): bool {
// Unauthenticated.
return true;
}
}
12 changes: 1 addition & 11 deletions src/Endpoints/class-related-api-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Related_API_Proxy extends Base_API_Proxy {
* Registers the endpoint's WP REST route.
*/
public function run(): void {
$this->register_endpoint( '/related' );
$this->register_endpoint( '/related', null, true );
}

/**
Expand Down Expand Up @@ -57,14 +57,4 @@ static function( stdClass $item ) {

return $result;
}

/**
* Determines if there are enough permissions to call the endpoint.
*
* @return bool
*/
public function permission_callback(): bool {
// Unauthenticated.
return true;
}
}
11 changes: 11 additions & 0 deletions src/RemoteAPI/class-base-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ public function __construct( Parsely $parsely ) {
$this->parsely = $parsely;
}

/**
* Gets Parse.ly API endpoint.
*
* @since 3.6.2
*
* @return string
*/
public function get_endpoint(): string {
return static::ENDPOINT;
}

/**
* Gets the URL for a particular Parse.ly API endpoint.
*
Expand Down
9 changes: 7 additions & 2 deletions src/RemoteAPI/class-cached-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@ public function __construct( Proxy $proxy, Cache $cache ) {
* response is empty.
*/
public function get_items( array $query ) {
$cache_key = 'parsely_api_' . wp_hash( (string) wp_json_encode( $this->proxy ) ) . '_' . wp_hash( (string) wp_json_encode( $query ) );
$items = $this->cache->get( $cache_key, self::CACHE_GROUP );
$cache_key = (
'parsely_api_' .
wp_hash( $this->proxy->get_endpoint() ) . '_' .
wp_hash( (string) wp_json_encode( $query ) )
);

$items = $this->cache->get( $cache_key, self::CACHE_GROUP );

if ( false === $items ) {
$items = $this->proxy->get_items( $query );
Expand Down
19 changes: 19 additions & 0 deletions tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ public function test_do_not_register_route_when_proxy_is_disabled(): void {
parent::test_do_not_register_route_when_proxy_is_disabled();
}

/**
* Verifies forbidden error when current user doesn't have proper capabilities.
*
* @covers \Parsely\Endpoints\Base_API_Proxy::permission_callback
*
* @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint
* @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::register_endpoint
*/
public function test_access_of_analytics_posts_endpoint_is_forbidden(): void {
$response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', self::$route ) );
$error = $response->as_error();

self::assertSame( 401, $response->get_status() );
self::assertSame( 'rest_forbidden', $error->get_error_code() );
self::assertSame( 'Sorry, you are not allowed to do that.', $error->get_error_message() );
}

/**
* Verifies that calling `GET /wp-parsely/v1/stats/posts` returns an
* error and does not perform a remote call when the apikey is not populated
Expand All @@ -85,6 +102,7 @@ public function test_do_not_register_route_when_proxy_is_disabled(): void {
* @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint
*/
public function test_get_items_fails_without_apikey_set() {
$this->set_admin_user();
parent::test_get_items_fails_without_apikey_set();
}

Expand Down Expand Up @@ -112,6 +130,7 @@ public function test_get_items_fails_without_apikey_set() {
public function test_get_items() {
TestCase::set_options( array( 'apikey' => 'example.com' ) );
TestCase::set_options( array( 'api_secret' => 'test' ) );
$this->set_admin_user();

$dispatched = 0;
$date_format = get_option( 'date_format' );
Expand Down
6 changes: 4 additions & 2 deletions tests/Integration/RemoteAPITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ public function test_cached_proxy_returns_cached_value(): void {
// If this method is called, that means our cache did not hit as
// expected.
$proxy_mock->expects( self::never() )->method( 'get_items' );
$proxy_mock->method( 'get_endpoint' )->willReturn( self::$proxy->get_endpoint() ); // Passing call to non-mock method.

$cache_key = 'parsely_api_' . wp_hash( wp_json_encode( $proxy_mock ) ) . '_' . wp_hash( wp_json_encode( array() ) );
$cache_key = 'parsely_api_' . wp_hash( self::$proxy->get_endpoint() ) . '_' . wp_hash( wp_json_encode( array() ) );

$object_cache = $this->createMock( Cache::class );
$object_cache->method( 'get' )
Expand Down Expand Up @@ -121,8 +122,9 @@ public function test_caching_decorator_returns_uncached_value(): void {
// If this method is _NOT_ called, that means our cache did not miss as
// expected.
$proxy_mock->expects( self::once() )->method( 'get_items' );
$proxy_mock->method( 'get_endpoint' )->willReturn( self::$proxy->get_endpoint() ); // Passing call to non-mock method.

$cache_key = 'parsely_api_' . wp_hash( wp_json_encode( $proxy_mock ) ) . '_' . wp_hash( wp_json_encode( array() ) );
$cache_key = 'parsely_api_' . wp_hash( self::$proxy->get_endpoint() ) . '_' . wp_hash( wp_json_encode( array() ) );

$object_cache = $this->createMock( Cache::class );
$object_cache->method( 'get' )
Expand Down
Loading

0 comments on commit b59a2c6

Please sign in to comment.