PluginProbe
Parse.ly / 3.8.4
Parse.ly v3.8.4
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
wp-parsely / src / UI / class-recommended-widget.php

class-recommended-widget.php in Parse.ly 3.8.4, at src/UI/class-recommended-widget.php

387 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * UI: Recommended Widget class
4 *
5 * Provides a widget with Parse.ly recommended articles.
6 *
7 * @package Parsely
8 */
9
10 declare(strict_types=1);
11
12 namespace Parsely\UI;
13
14 use Parsely\Parsely;
15 use WP_Widget;
16
17 use function Parsely\Utils\get_asset_info;
18
19 use const Parsely\PARSELY_FILE;
20
21 /**
22 * Provides a widget with Parse.ly recommended articles.
23 *
24 * @phpstan-type Widget_Settings array{
25 * title: string,
26 * return_limit: int,
27 * display_direction: string,
28 * published_within: int,
29 * sort: string,
30 * boost: string,
31 * personalize_results: bool,
32 * img_src: string,
33 * display_author: bool,
34 * }
35 */
36 final class Recommended_Widget extends WP_Widget {
37 /**
38 * Instance of Parsely class.
39 *
40 * @var Parsely
41 */
42 private $parsely;
43
44 /**
45 * Default values of widget settings
46 *
47 * @var Widget_Settings
48 */
49 private static $default_widget_settings = array(
50 'title' => '',
51 'return_limit' => 5,
52 'display_direction' => 'vertical',
53 'published_within' => 0,
54 'sort' => 'score',
55 'boost' => 'views',
56 'personalize_results' => false,
57 'img_src' => 'parsely_thumb',
58 'display_author' => false,
59 );
60
61 /**
62 * Constructor.
63 *
64 * @param Parsely $parsely Instance of Parsely class.
65 */
66 public function __construct( Parsely $parsely ) {
67 parent::__construct(
68 'Parsely_Recommended_Widget',
69 __( 'Parse.ly Recommended Widget', 'wp-parsely' ),
70 array(
71 'classname' => 'Recommended_Widget parsely-recommended-widget-hidden',
72 'description' => __( 'Display a list of post recommendations, personalized for a visitor or the current post.', 'wp-parsely' ),
73 )
74 );
75
76 $this->parsely = $parsely;
77 }
78
79 /**
80 * Gets the URL for the Recommendations API (GET /related).
81 *
82 * @see https://www.parse.ly/help/api/recommendations#get-related
83 *
84 * @internal While this is a public method now, this should be moved to a new class.
85 *
86 * @since 2.5.0
87 *
88 * @param string $site_id Publisher Site ID.
89 * @param int|null $published_within Publication filter start date; see https://www.parse.ly/help/api/time for
90 * formatting details. No restriction by default.
91 * @param string|null $sort What to sort the results by. There are currently 2 valid options: `score`,
92 * which will sort articles by overall relevance and `pub_date` which will sort
93 * results by their publication date. The default is `score`.
94 * @param string|null $boost Available for sort=score only. Sub-sort value to re-rank relevant posts that
95 * received high e.g. views; default is undefined.
96 * @param int $return_limit Number of records to retrieve; defaults to "10".
97 * @return string API URL.
98 */
99 private function get_api_url( string $site_id, ?int $published_within, ?string $sort, ?string $boost, int $return_limit ): string {
100 $related_api_endpoint = Parsely::PUBLIC_API_BASE_URL . '/related';
101
102 $query_args = array(
103 'apikey' => $site_id,
104 'sort' => $sort,
105 'limit' => $return_limit,
106 );
107
108 if ( 'score' === $sort && 'no-boost' !== $boost ) {
109 $query_args['boost'] = $boost;
110 }
111
112 if ( null !== $published_within && 0 !== $published_within ) {
113 $query_args['pub_date_start'] = $published_within . 'd';
114 }
115
116 return add_query_arg( $query_args, $related_api_endpoint );
117 }
118
119 /**
120 * This is the widget function.
121 *
122 * @param array<string, string> $args Widget Arguments.
123 * @param array<mixed> $widget_settings Values saved to the db.
124 */
125 public function widget( $args, $widget_settings ): void /* @phpstan-ignore-line */ {
126 if ( ! $this->site_id_and_secret_are_populated() ) {
127 return;
128 }
129
130 $instance = $this->get_widget_settings( $widget_settings );
131 $removed_title_esc = remove_filter( 'widget_title', 'esc_html' );
132
133 /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
134 $title = apply_filters( 'widget_title', $instance['title'] );
135
136 if ( $removed_title_esc ) {
137 add_filter( 'widget_title', 'esc_html' );
138 }
139
140 $title_html = $args['before_widget'] . $args['before_title'] . $title . $args['after_title'];
141 echo wp_kses_post( $title_html );
142
143 // Set up the variables.
144 $api_url = $this->get_api_url(
145 $this->parsely->get_site_id(),
146 (int) $instance['published_within'], // @phpstan-ignore-line
147 $instance['sort'],
148 $instance['boost'],
149 (int) $instance['return_limit'] // @phpstan-ignore-line
150 );
151
152 ?>
153
154 <div class="parsely-recommended-widget"
155 data-parsely-widget-display-author="<?php echo esc_attr( (string) wp_json_encode( $instance['display_author'] ) ); ?>"
156 data-parsely-widget-display-direction="<?php echo esc_attr( $instance['display_direction'] ); ?>"
157 data-parsely-widget-api-url="<?php echo esc_url( $api_url ); ?>"
158 data-parsely-widget-img-display="<?php echo esc_attr( $instance['img_src'] ); ?>"
159 data-parsely-widget-permalink="<?php echo esc_url( (string) get_permalink() ); ?>"
160 data-parsely-widget-personalized="<?php echo esc_attr( (string) wp_json_encode( $instance['personalize_results'] ) ); ?>"
161 data-parsely-widget-id="<?php echo esc_attr( (string) $this->id ); ?>"
162 ></div>
163
164 <?php
165
166 $recommended_widget_script_asset = get_asset_info( 'build/recommended-widget.asset.php' );
167
168 wp_register_script(
169 'wp-parsely-recommended-widget',
170 plugin_dir_url( PARSELY_FILE ) . 'build/recommended-widget.js',
171 $recommended_widget_script_asset['dependencies'],
172 $recommended_widget_script_asset['version'],
173 true
174 );
175
176 wp_register_style(
177 'wp-parsely-recommended-widget',
178 plugin_dir_url( PARSELY_FILE ) . 'build/recommended-widget.css',
179 array(),
180 $recommended_widget_script_asset['version']
181 );
182
183 wp_enqueue_script( 'wp-parsely-recommended-widget' );
184 wp_enqueue_style( 'wp-parsely-recommended-widget' );
185
186 echo wp_kses_post( $args['after_widget'] );
187 }
188
189 /**
190 * This is the form function.
191 *
192 * @param array<mixed> $current_settings Values saved to the db.
193 */
194 public function form( $current_settings ): string {
195 if ( ! $this->site_id_and_secret_are_populated() ) {
196 $settings_page_url = add_query_arg( 'page', 'parsely', get_admin_url() . 'options-general.php' );
197
198 $message = sprintf(
199 /* translators: %s: Plugin settings page URL */
200 __( 'The <i>Parse.ly Site ID</i> and <i>Parse.ly API Secret</i> fields need to be populated on the <a href="%s">Parse.ly settings page</a> for this widget to work.', 'wp-parsely' ),
201 esc_url( $settings_page_url )
202 );
203
204 echo '<p>', wp_kses_post( $message ), '</p>';
205
206 return '';
207 }
208
209 $instance = $this->get_widget_settings( $current_settings );
210
211 // editable fields: title.
212 $title = $instance['title'];
213 $return_limit = $instance['return_limit'];
214 $display_direction = $instance['display_direction'];
215 $published_within = $instance['published_within'];
216 $sort = $instance['sort'];
217 $boost = $instance['boost'];
218 $personalize_results = $instance['personalize_results'];
219 $img_src = $instance['img_src'];
220 $display_author = $instance['display_author'];
221
222 $instance['return_limit'] = $return_limit;
223 $instance['display_direction'] = $display_direction;
224 $instance['published_within'] = $published_within;
225 $instance['sort'] = $sort;
226 $instance['boost'] = $boost;
227 $instance['personalize_results'] = $personalize_results;
228 $instance['img_src'] = $img_src;
229 $instance['display_author'] = $display_author;
230
231 $boost_params = $this->get_boost_params();
232 ?>
233 <p>
234 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'wp-parsely' ); ?></label>
235 <br>
236 <input type="text" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo esc_attr( $title ); ?>" class="widefat" />
237 </p>
238 <p>
239 <label for="<?php echo esc_attr( $this->get_field_id( 'published_within' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'published_within_label' ) ); ?>"><?php esc_html_e( 'Published within', 'wp-parsely' ); ?></label>
240 <input type="number" id="<?php echo esc_attr( $this->get_field_id( 'published_within' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'published_within' ) ); ?>" value="<?php echo esc_attr( (string) $instance['published_within'] ); ?>" min="0" max="30"
241 class="tiny-text" aria-labelledby="<?php echo esc_attr( $this->get_field_id( 'published_within_label' ) ); ?> <?php echo esc_attr( $this->get_field_id( 'published_within' ) ); ?> <?php echo esc_attr( $this->get_field_id( 'published_within_unit' ) ); ?>" />
242 <span id="<?php echo esc_attr( $this->get_field_id( 'published_within_unit' ) ); ?>"> <?php esc_html_e( 'days (0 for no limit).', 'wp-parsely' ); ?></span>
243 </p>
244 <p>
245 <label for="<?php echo esc_attr( $this->get_field_id( 'return_limit' ) ); ?>"><?php esc_html_e( 'Number of posts to show (max 20):', 'wp-parsely' ); ?></label>
246 <input type="number" id="<?php echo esc_attr( $this->get_field_id( 'return_limit' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'return_limit' ) ); ?>" value="<?php echo esc_attr( (string) $instance['return_limit'] ); ?>" min="1" max="20" class="tiny-text" />
247 </p>
248 <p>
249 <fieldset>
250 <legend><?php esc_html_e( 'Display entries:', 'wp-parsely' ); ?></legend>
251 <p>
252 <input type="radio" id="<?php echo esc_attr( $this->get_field_id( 'display_direction_horizontal' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'display_direction' ) ); ?>"<?php checked( $instance['display_direction'], 'horizontal' ); ?> value="horizontal" />
253 <label for="<?php echo esc_attr( $this->get_field_id( 'display_direction_horizontal' ) ); ?>"><?php esc_html_e( 'Horizontally', 'wp-parsely' ); ?></label>
254 <br />
255 <input type="radio" id="<?php echo esc_attr( $this->get_field_id( 'display_direction_vertical' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'display_direction' ) ); ?>"<?php checked( $instance['display_direction'], 'vertical' ); ?> value="vertical" />
256 <label for="<?php echo esc_attr( $this->get_field_id( 'display_direction_vertical' ) ); ?>"><?php esc_html_e( 'Vertically', 'wp-parsely' ); ?></label>
257 </p>
258 </fieldset>
259 </p>
260 <p>
261 <label for="<?php echo esc_attr( $this->get_field_id( 'sort' ) ); ?>"><?php esc_html_e( 'Sort by:', 'wp-parsely' ); ?></label>
262 <br>
263 <select id="<?php echo esc_attr( $this->get_field_id( 'sort' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>" class="widefat">
264 <option<?php selected( $instance['sort'], 'score' ); ?> value="score"><?php esc_html_e( 'Score (relevancy, boostable)', 'wp-parsely' ); ?></option>
265 <option<?php selected( $instance['sort'], 'pub_date' ); ?> value="pub_date"><?php esc_html_e( 'Publish date (not boostable)', 'wp-parsely' ); ?></option>
266 </select>
267 </p>
268 <p>
269 <label for="<?php echo esc_attr( $this->get_field_id( 'boost' ) ); ?>"><?php esc_html_e( 'Boost by:', 'wp-parsely' ); ?></label>
270 <br>
271 <select id="<?php echo esc_attr( $this->get_field_id( 'boost' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'boost' ) ); ?>" class="widefat">
272 <?php foreach ( $boost_params as $boost_param => $description ) { ?>
273 <option<?php selected( $instance['boost'], $boost_param ); ?> value="<?php echo esc_attr( $boost_param ); ?>"><?php echo esc_html( $description ); ?></option>
274 <?php } ?>
275 </select>
276
277 </p>
278 <p>
279 <label for="<?php echo esc_attr( $this->get_field_id( 'img_src' ) ); ?>"><?php esc_html_e( 'Image source:', 'wp-parsely' ); ?></label>
280 <br>
281 <select id="<?php echo esc_attr( $this->get_field_id( 'img_src' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'img_src' ) ); ?>" class="widefat">
282 <option<?php selected( $instance['img_src'], 'parsely_thumb' ); ?> value="parsely_thumb"><?php esc_html_e( 'Parse.ly generated thumbnail (85x85px)', 'wp-parsely' ); ?></option>
283 <option<?php selected( $instance['img_src'], 'original' ); ?> value="original"><?php esc_html_e( 'Original image', 'wp-parsely' ); ?></option>
284 <option<?php selected( $instance['img_src'], 'none' ); ?> value="none"><?php esc_html_e( 'No image', 'wp-parsely' ); ?></option>
285 </select>
286 </p>
287 <p>
288 <input type="checkbox" id="<?php echo esc_attr( $this->get_field_id( 'display_author' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'display_author' ) ); ?>" value="display_author"<?php checked( $instance['display_author'], 'display_author' ); ?> />
289 <label for="<?php echo esc_attr( $this->get_field_id( 'display_author' ) ); ?>"><?php esc_html_e( 'Display author', 'wp-parsely' ); ?></label>
290 <br />
291 <input type="checkbox" id="<?php echo esc_attr( $this->get_field_id( 'personalize_results' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'personalize_results' ) ); ?>" value="personalize_results"<?php checked( $instance['personalize_results'], 'personalize_results' ); ?> />
292 <label for="<?php echo esc_attr( $this->get_field_id( 'personalize_results' ) ); ?>"><?php esc_html_e( 'Personalize recommended results', 'wp-parsely' ); ?></label>
293 </p>
294 <?php
295
296 return '';
297 }
298
299 /**
300 * This is the update function.
301 *
302 * @param Widget_Settings $new_instance The new values for the db.
303 * @param Widget_Settings $old_instance Values saved to the db.
304 *
305 * @return Widget_Settings
306 */
307 public function update( $new_instance, $old_instance ) /* @phpstan-ignore-line */ {
308 $instance = $old_instance;
309 $instance['title'] = trim( wp_kses_post( $new_instance['title'] ) );
310 $instance['published_within'] = $new_instance['published_within'];
311 $instance['return_limit'] = $new_instance['return_limit'] <= 20 ? $new_instance['return_limit'] : 20;
312 $instance['display_direction'] = trim( $new_instance['display_direction'] );
313 $instance['sort'] = trim( $new_instance['sort'] );
314 $instance['boost'] = trim( $new_instance['boost'] );
315 $instance['display_author'] = $new_instance['display_author'];
316 $instance['personalize_results'] = $new_instance['personalize_results'];
317 $instance['img_src'] = trim( $new_instance['img_src'] );
318
319 return $instance;
320 }
321
322 /**
323 * Returns the list of boost parameters, values and labels.
324 *
325 * @since 2.5.0
326 *
327 * @return array<string, string> Boost parameters values and labels.
328 */
329 private function get_boost_params(): array {
330 return array(
331 'no-boost' => __( 'No boost', 'wp-parsely' ),
332 'views' => __( 'Page views', 'wp-parsely' ),
333 'mobile_views' => __( 'Page views on mobile devices', 'wp-parsely' ),
334 'tablet_views' => __( 'Page views on tablet devices', 'wp-parsely' ),
335 'desktop_views' => __( 'Page views on desktop devices', 'wp-parsely' ),
336 'visitors' => __( 'Unique page visitors, total', 'wp-parsely' ),
337 'visitors_new' => __( 'New visitors', 'wp-parsely' ),
338 'visitors_returning' => __( 'Returning visitors', 'wp-parsely' ),
339 'engaged_minutes' => __( 'Total engagement time in minutes', 'wp-parsely' ),
340 'avg_engaged' => __( 'Engaged minutes spent by total visitors', 'wp-parsely' ),
341 'avg_engaged_new' => __( 'Average engaged minutes spent by new visitors', 'wp-parsely' ),
342 'avg_engaged_returning' => __( 'Average engaged minutes spent by returning visitors', 'wp-parsely' ),
343 'social_interactions' => __( 'Total social interactions', 'wp-parsely' ),
344 'fb_interactions' => __( 'Count of Facebook shares, likes, and comments', 'wp-parsely' ),
345 'tw_interactions' => __( 'Count of Twitter tweets and retweets', 'wp-parsely' ),
346 'pi_interactions' => __( 'Count of Pinterest pins', 'wp-parsely' ),
347 'social_referrals' => __( 'Page views where the referrer was any social network', 'wp-parsely' ),
348 'fb_referrals' => __( 'Page views where the referrer was facebook.com', 'wp-parsely' ),
349 'tw_referrals' => __( 'Page views where the referrer was twitter.com', 'wp-parsely' ),
350 'pi_referrals' => __( 'Page views where the referrer was pinterest.com', 'wp-parsely' ),
351 );
352 }
353
354 /**
355 * Checks if both the Site ID and API secret settings are populated with
356 * non-empty values.
357 *
358 * @since 2.5.0
359 *
360 * @return bool True if Site ID and API Secret settings are set.
361 * False otherwise.
362 */
363 private function site_id_and_secret_are_populated(): bool {
364 return $this->parsely->site_id_is_set() && $this->parsely->api_secret_is_set();
365 }
366
367 /**
368 * Returns all widget settings by assigning defaults if a setting isn't present
369 *
370 * @since 3.7.0
371 *
372 * @param array<string, mixed> $settings Widget Options.
373 *
374 * @return Widget_Settings
375 */
376 public function get_widget_settings( array $settings ) {
377 /**
378 * Variable.
379 *
380 * @var Widget_Settings
381 */
382 $widget_settings = $settings;
383
384 return array_merge( self::$default_widget_settings, $widget_settings );
385 }
386 }
387