first commit
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Actions\Wincher;
|
||||
|
||||
use Exception;
|
||||
use WP_Post;
|
||||
use WPSEO_Utils;
|
||||
use Yoast\WP\SEO\Config\Wincher_Client;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Class Wincher_Keyphrases_Action
|
||||
*/
|
||||
class Wincher_Keyphrases_Action {
|
||||
|
||||
/**
|
||||
* The Wincher keyphrase URL for bulk addition.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KEYPHRASES_ADD_URL = 'https://api.wincher.com/beta/websites/%s/keywords/bulk';
|
||||
|
||||
/**
|
||||
* The Wincher tracked keyphrase retrieval URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KEYPHRASES_URL = 'https://api.wincher.com/beta/yoast/%s';
|
||||
|
||||
/**
|
||||
* The Wincher delete tracked keyphrase URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KEYPHRASE_DELETE_URL = 'https://api.wincher.com/beta/websites/%s/keywords/%s';
|
||||
|
||||
/**
|
||||
* The Wincher_Client instance.
|
||||
*
|
||||
* @var Wincher_Client
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* The Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
protected $options_helper;
|
||||
|
||||
/**
|
||||
* The Indexable_Repository instance.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
protected $indexable_repository;
|
||||
|
||||
/**
|
||||
* Wincher_Keyphrases_Action constructor.
|
||||
*
|
||||
* @param Wincher_Client $client The API client.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Indexable_Repository $indexable_repository The indexables repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Wincher_Client $client,
|
||||
Options_Helper $options_helper,
|
||||
Indexable_Repository $indexable_repository
|
||||
) {
|
||||
$this->client = $client;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the tracking API request for one or more keyphrases.
|
||||
*
|
||||
* @param string|array $keyphrases One or more keyphrases that should be tracked.
|
||||
* @param Object $limits The limits API call response data.
|
||||
*
|
||||
* @return Object The reponse object.
|
||||
*/
|
||||
public function track_keyphrases( $keyphrases, $limits ) {
|
||||
try {
|
||||
$endpoint = \sprintf(
|
||||
self::KEYPHRASES_ADD_URL,
|
||||
$this->options_helper->get( 'wincher_website_id' )
|
||||
);
|
||||
|
||||
// Enforce arrrays to ensure a consistent way of preparing the request.
|
||||
if ( ! \is_array( $keyphrases ) ) {
|
||||
$keyphrases = [ $keyphrases ];
|
||||
}
|
||||
|
||||
// Calculate if the user would exceed their limit.
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- To ensure JS code style, this can be ignored.
|
||||
if ( ! $limits->canTrack || $this->would_exceed_limits( $keyphrases, $limits ) ) {
|
||||
$response = [
|
||||
'limit' => $limits->limit,
|
||||
'error' => 'Account limit exceeded',
|
||||
'status' => 400,
|
||||
];
|
||||
|
||||
return $this->to_result_object( $response );
|
||||
}
|
||||
|
||||
$formatted_keyphrases = \array_values(
|
||||
\array_map(
|
||||
static function ( $keyphrase ) {
|
||||
return [
|
||||
'keyword' => $keyphrase,
|
||||
'groups' => [],
|
||||
];
|
||||
},
|
||||
$keyphrases
|
||||
)
|
||||
);
|
||||
|
||||
$results = $this->client->post( $endpoint, WPSEO_Utils::format_json_encode( $formatted_keyphrases ) );
|
||||
|
||||
if ( ! \array_key_exists( 'data', $results ) ) {
|
||||
return $this->to_result_object( $results );
|
||||
}
|
||||
|
||||
// The endpoint returns a lot of stuff that we don't want/need.
|
||||
$results['data'] = \array_map(
|
||||
static function( $keyphrase ) {
|
||||
return [
|
||||
'id' => $keyphrase['id'],
|
||||
'keyword' => $keyphrase['keyword'],
|
||||
];
|
||||
},
|
||||
$results['data']
|
||||
);
|
||||
|
||||
$results['data'] = \array_combine(
|
||||
\array_column( $results['data'], 'keyword' ),
|
||||
\array_values( $results['data'] )
|
||||
);
|
||||
|
||||
return $this->to_result_object( $results );
|
||||
} catch ( Exception $e ) {
|
||||
return (object) [
|
||||
'error' => $e->getMessage(),
|
||||
'status' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an untrack request for the passed keyword ID.
|
||||
*
|
||||
* @param int $keyphrase_id The ID of the keyphrase to untrack.
|
||||
*
|
||||
* @return object The response object.
|
||||
*/
|
||||
public function untrack_keyphrase( $keyphrase_id ) {
|
||||
try {
|
||||
$endpoint = \sprintf(
|
||||
self::KEYPHRASE_DELETE_URL,
|
||||
$this->options_helper->get( 'wincher_website_id' ),
|
||||
$keyphrase_id
|
||||
);
|
||||
|
||||
$this->client->delete( $endpoint );
|
||||
|
||||
return (object) [
|
||||
'status' => 200,
|
||||
];
|
||||
} catch ( Exception $e ) {
|
||||
return (object) [
|
||||
'error' => $e->getMessage(),
|
||||
'status' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keyphrase data for the passed keyphrases.
|
||||
* Retrieves all available data if no keyphrases are provided.
|
||||
*
|
||||
* @param array|null $used_keyphrases The currently used keyphrases. Optional.
|
||||
* @param string|null $permalink The current permalink. Optional.
|
||||
*
|
||||
* @return object The keyphrase chart data.
|
||||
*/
|
||||
public function get_tracked_keyphrases( $used_keyphrases = null, $permalink = null ) {
|
||||
try {
|
||||
if ( $used_keyphrases === null ) {
|
||||
$used_keyphrases = $this->collect_all_keyphrases();
|
||||
}
|
||||
|
||||
// If we still have no keyphrases the API will return an error, so
|
||||
// don't even bother sending a request.
|
||||
if ( empty( $used_keyphrases ) ) {
|
||||
return $this->to_result_object(
|
||||
[
|
||||
'data' => [],
|
||||
'status' => 200,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$endpoint = \sprintf(
|
||||
self::KEYPHRASES_URL,
|
||||
$this->options_helper->get( 'wincher_website_id' )
|
||||
);
|
||||
|
||||
$results = $this->client->post(
|
||||
$endpoint,
|
||||
WPSEO_Utils::format_json_encode(
|
||||
[
|
||||
'keywords' => $used_keyphrases,
|
||||
'url' => $permalink,
|
||||
]
|
||||
),
|
||||
[
|
||||
'timeout' => 60,
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! \array_key_exists( 'data', $results ) ) {
|
||||
return $this->to_result_object( $results );
|
||||
}
|
||||
|
||||
$results['data'] = $this->filter_results_by_used_keyphrases( $results['data'], $used_keyphrases );
|
||||
|
||||
// Extract the positional data and assign it to the keyphrase.
|
||||
$results['data'] = \array_combine(
|
||||
\array_column( $results['data'], 'keyword' ),
|
||||
\array_values( $results['data'] )
|
||||
);
|
||||
|
||||
return $this->to_result_object( $results );
|
||||
} catch ( Exception $e ) {
|
||||
return (object) [
|
||||
'error' => $e->getMessage(),
|
||||
'status' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the keyphrases associated with the post.
|
||||
*
|
||||
* @param WP_Post $post The post object.
|
||||
*
|
||||
* @return array The keyphrases.
|
||||
*/
|
||||
public function collect_keyphrases_from_post( $post ) {
|
||||
$keyphrases = [];
|
||||
$primary_keyphrase = $this->indexable_repository
|
||||
->query()
|
||||
->select( 'primary_focus_keyword' )
|
||||
->where( 'object_id', $post->ID )
|
||||
->find_one();
|
||||
|
||||
if ( $primary_keyphrase ) {
|
||||
$keyphrases[] = $primary_keyphrase->primary_focus_keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the keyphrases collected by the Wincher integration from the post.
|
||||
*
|
||||
* @param array $keyphrases The keyphrases array.
|
||||
* @param int $post_id The ID of the post.
|
||||
*/
|
||||
return \apply_filters( 'wpseo_wincher_keyphrases_from_post', $keyphrases, $post->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all keyphrases known to Yoast.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function collect_all_keyphrases() {
|
||||
global $wpdb;
|
||||
|
||||
// Collect primary keyphrases first.
|
||||
$keyphrases = \array_column(
|
||||
$this->indexable_repository
|
||||
->query()
|
||||
->select( 'primary_focus_keyword' )
|
||||
->where_not_null( 'primary_focus_keyword' )
|
||||
->where( 'object_type', 'post' )
|
||||
->where_not_equal( 'post_status', 'trash' )
|
||||
->distinct()
|
||||
->find_array(),
|
||||
'primary_focus_keyword'
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the keyphrases collected by the Wincher integration from all the posts.
|
||||
*
|
||||
* @param array $keyphrases The keyphrases array.
|
||||
*/
|
||||
$keyphrases = \apply_filters( 'wpseo_wincher_all_keyphrases', $keyphrases );
|
||||
|
||||
// Filter out empty entries.
|
||||
return \array_filter( $keyphrases );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the results based on the passed keyphrases.
|
||||
*
|
||||
* @param array $results The results to filter.
|
||||
* @param array $used_keyphrases The used keyphrases.
|
||||
*
|
||||
* @return array The filtered results.
|
||||
*/
|
||||
protected function filter_results_by_used_keyphrases( $results, $used_keyphrases ) {
|
||||
return \array_filter(
|
||||
$results,
|
||||
static function( $result ) use ( $used_keyphrases ) {
|
||||
return \in_array( $result['keyword'], \array_map( 'strtolower', $used_keyphrases ), true );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the amount of keyphrases would mean the user exceeds their account limits.
|
||||
*
|
||||
* @param string|array $keyphrases The keyphrases to be added.
|
||||
* @param object $limits The current account limits.
|
||||
*
|
||||
* @return bool Whether the limit is exceeded.
|
||||
*/
|
||||
protected function would_exceed_limits( $keyphrases, $limits ) {
|
||||
if ( ! \is_array( $keyphrases ) ) {
|
||||
$keyphrases = [ $keyphrases ];
|
||||
}
|
||||
|
||||
if ( \is_null( $limits->limit ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ( \count( $keyphrases ) + $limits->usage ) > $limits->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the passed dataset to an object.
|
||||
*
|
||||
* @param array $result The result dataset to convert to an object.
|
||||
*
|
||||
* @return object The result object.
|
||||
*/
|
||||
protected function to_result_object( $result ) {
|
||||
if ( \array_key_exists( 'data', $result ) ) {
|
||||
$result['results'] = $result['data'];
|
||||
|
||||
unset( $result['data'] );
|
||||
}
|
||||
|
||||
if ( \array_key_exists( 'message', $result ) ) {
|
||||
$result['error'] = $result['message'];
|
||||
|
||||
unset( $result['message'] );
|
||||
}
|
||||
|
||||
return (object) $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user