wincher-keyphrases-action.php
9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
<?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() {
// 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'] = (object) $result['data'];
unset( $result['data'] );
}
if ( \array_key_exists( 'message', $result ) ) {
$result['error'] = $result['message'];
unset( $result['message'] );
}
return (object) $result;
}
}