PluginProbe
Parse.ly / 3.6.0
Parse.ly v3.6.0
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.6.0, at src/UI/class-recommended-widget.php

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