| @@ -1,13 +1,11 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Recommended Widget file | |
| 3 | + * UI: Recommended Widget class | |
| 4 | 4 | * |
| 5 | - * This provides a widget to put on a page, will have parsely recommended articles | |
| 5 | + * Provides a widget with Parse.ly recommended articles. | |
| 6 | 6 | * |
| 7 | - * @category Components | |
| 8 | - * @package WordPress | |
| 9 | - * @subpackage Parse.ly | |
| 7 | + * @package Parsely | |
| 10 | 8 | */ |
| 11 | 9 | |
| 12 | 10 | declare(strict_types=1); |
| 13 | 11 | |
| @@ -12,20 +10,61 @@ | ||
| 12 | 10 | declare(strict_types=1); |
| 13 | 11 | |
| 14 | 12 | namespace Parsely\UI; |
| 15 | 13 | |
| 14 | +use Parsely\Parsely; | |
| 16 | 15 | use WP_Widget; |
| 17 | 16 | |
| 17 | +use function Parsely\Utils\get_asset_info; | |
| 18 | + | |
| 18 | 19 | use const Parsely\PARSELY_FILE; |
| 19 | 20 | |
| 20 | 21 | /** |
| 21 | - * This is the class for the recommended widget. | |
| 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 | + * } | |
| 22 | 35 | */ |
| 23 | 36 | final class Recommended_Widget extends WP_Widget { |
| 24 | 37 | /** |
| 25 | - * This is the constructor function. | |
| 38 | + * Instance of Parsely class. | |
| 39 | + * | |
| 40 | + * @var Parsely | |
| 26 | 41 | */ |
| 27 | - public function __construct() { | |
| 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 ) { | |
| 28 | 67 | parent::__construct( |
| 29 | 68 | 'Parsely_Recommended_Widget', |
| 30 | 69 | __( 'Parse.ly Recommended Widget', 'wp-parsely' ), |
| 31 | 70 | array( |
| @@ -32,12 +71,14 @@ | ||
| 32 | 71 | 'classname' => 'Recommended_Widget parsely-recommended-widget-hidden', |
| 33 | 72 | 'description' => __( 'Display a list of post recommendations, personalized for a visitor or the current post.', 'wp-parsely' ), |
| 34 | 73 | ) |
| 35 | 74 | ); |
| 75 | + | |
| 76 | + $this->parsely = $parsely; | |
| 36 | 77 | } |
| 37 | 78 | |
| 38 | 79 | /** |
| 39 | - * Get the URL for the Recommendations API (GET /related). | |
| 80 | + * Gets the URL for the Recommendations API (GET /related). | |
| 40 | 81 | * |
| 41 | 82 | * @see https://www.parse.ly/help/api/recommendations#get-related |
| 42 | 83 | * |
| 43 | 84 | * @internal While this is a public method now, this should be moved to a new class. |
| @@ -43,24 +84,24 @@ | ||
| 43 | 84 | * @internal While this is a public method now, this should be moved to a new class. |
| 44 | 85 | * |
| 45 | 86 | * @since 2.5.0 |
| 46 | 87 | * |
| 47 | - * @param string $api_key Publisher Site ID (API key). | |
| 88 | + * @param string $site_id Publisher Site ID. | |
| 48 | 89 | * @param int|null $published_within Publication filter start date; see https://www.parse.ly/help/api/time for |
| 49 | - * formatting details. No restriction by default. | |
| 50 | - * @param string|null $sort What to sort the results by. There are currently 2 valid options: `score`, which | |
| 51 | - * will sort articles by overall relevance and `pub_date` which will sort results by | |
| 52 | - * their publication date. The default is `score`. | |
| 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`. | |
| 53 | 94 | * @param string|null $boost Available for sort=score only. Sub-sort value to re-rank relevant posts that |
| 54 | - * received high e.g. views; default is undefined. | |
| 95 | + * received high e.g. views; default is undefined. | |
| 55 | 96 | * @param int $return_limit Number of records to retrieve; defaults to "10". |
| 56 | 97 | * @return string API URL. |
| 57 | 98 | */ |
| 58 | - private function get_api_url( string $api_key, ?int $published_within, ?string $sort, ?string $boost, int $return_limit ): string { | |
| 59 | - $related_api_endpoint = 'https://api.parsely.com/v2/related'; | |
| 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'; | |
| 60 | 101 | |
| 61 | 102 | $query_args = array( |
| 62 | - 'apikey' => $api_key, | |
| 103 | + 'apikey' => $site_id, | |
| 63 | 104 | 'sort' => $sort, |
| 64 | 105 | 'limit' => $return_limit, |
| 65 | 106 | ); |
| 66 | 107 | |
| @@ -75,19 +116,19 @@ | ||
| 75 | 116 | return add_query_arg( $query_args, $related_api_endpoint ); |
| 76 | 117 | } |
| 77 | 118 | |
| 78 | 119 | /** |
| 79 | - * This is the widget function | |
| 120 | + * This is the widget function. | |
| 80 | 121 | * |
| 81 | - * @param array $args Widget Arguments. | |
| 82 | - * @param array $instance Values saved to the db. | |
| 83 | - * @return void | |
| 122 | + * @param array<string, string> $args Widget Arguments. | |
| 123 | + * @param array<mixed> $widget_settings Values saved to the db. | |
| 84 | 124 | */ |
| 85 | - public function widget( $args, $instance ): void { | |
| 86 | - if ( ! $this->api_key_and_secret_are_populated() ) { | |
| 125 | + public function widget( $args, $widget_settings ): void /* @phpstan-ignore-line */ { | |
| 126 | + if ( ! $this->site_id_and_secret_are_populated() ) { | |
| 87 | 127 | return; |
| 88 | 128 | } |
| 89 | 129 | |
| 130 | + $instance = $this->get_widget_settings( $widget_settings ); | |
| 90 | 131 | $removed_title_esc = remove_filter( 'widget_title', 'esc_html' ); |
| 91 | 132 | |
| 92 | 133 | /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ |
| 93 | 134 | $title = apply_filters( 'widget_title', $instance['title'] ); |
| @@ -99,33 +140,32 @@ | ||
| 99 | 140 | $title_html = $args['before_widget'] . $args['before_title'] . $title . $args['after_title']; |
| 100 | 141 | echo wp_kses_post( $title_html ); |
| 101 | 142 | |
| 102 | 143 | // Set up the variables. |
| 103 | - $options = get_option( 'parsely' ); | |
| 104 | 144 | $api_url = $this->get_api_url( |
| 105 | - $options['apikey'], | |
| 106 | - $instance['published_within'], | |
| 145 | + $this->parsely->get_site_id(), | |
| 146 | + (int) $instance['published_within'], // @phpstan-ignore-line | |
| 107 | 147 | $instance['sort'], |
| 108 | 148 | $instance['boost'], |
| 109 | - (int) $instance['return_limit'] | |
| 149 | + (int) $instance['return_limit'] // @phpstan-ignore-line | |
| 110 | 150 | ); |
| 111 | 151 | |
| 112 | - $recommended_widget_script_asset = require plugin_dir_path( PARSELY_FILE ) . 'build/recommended-widget.asset.php'; | |
| 113 | - | |
| 114 | 152 | ?> |
| 115 | 153 | |
| 116 | 154 | <div class="parsely-recommended-widget" |
| 117 | - data-parsely-widget-display-author="<?php echo esc_attr( wp_json_encode( isset( $instance['display_author'] ) && $instance['display_author'] ) ); ?>" | |
| 118 | - data-parsely-widget-display-direction="<?php echo esc_attr( $instance['display_direction'] ?? '' ); ?>" | |
| 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'] ); ?>" | |
| 119 | 157 | data-parsely-widget-api-url="<?php echo esc_url( $api_url ); ?>" |
| 120 | - data-parsely-widget-img-display="<?php echo esc_attr( $instance['img_src'] ?? '' ); ?>" | |
| 121 | - data-parsely-widget-permalink="<?php echo esc_url( get_permalink() ); ?>" | |
| 122 | - data-parsely-widget-personalized="<?php echo esc_attr( wp_json_encode( isset( $instance['personalize_results'] ) && $instance['personalize_results'] ) ); ?>" | |
| 123 | - data-parsely-widget-id="<?php echo esc_attr( $this->id ); ?>" | |
| 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 ); ?>" | |
| 124 | 162 | ></div> |
| 125 | 163 | |
| 126 | 164 | <?php |
| 127 | 165 | |
| 166 | + $recommended_widget_script_asset = get_asset_info( 'build/recommended-widget.asset.php' ); | |
| 167 | + | |
| 128 | 168 | wp_register_script( |
| 129 | 169 | 'wp-parsely-recommended-widget', |
| 130 | 170 | plugin_dir_url( PARSELY_FILE ) . 'build/recommended-widget.js', |
| 131 | 171 | $recommended_widget_script_asset['dependencies'], |
| @@ -146,14 +186,14 @@ | ||
| 146 | 186 | echo wp_kses_post( $args['after_widget'] ); |
| 147 | 187 | } |
| 148 | 188 | |
| 149 | 189 | /** |
| 150 | - * This is the form function | |
| 190 | + * This is the form function. | |
| 151 | 191 | * |
| 152 | - * @param array $instance Values saved to the db. | |
| 192 | + * @param array<mixed> $current_settings Values saved to the db. | |
| 153 | 193 | */ |
| 154 | - public function form( $instance ): void { | |
| 155 | - if ( ! $this->api_key_and_secret_are_populated() ) { | |
| 194 | + public function form( $current_settings ): string { | |
| 195 | + if ( ! $this->site_id_and_secret_are_populated() ) { | |
| 156 | 196 | $settings_page_url = add_query_arg( 'page', 'parsely', get_admin_url() . 'options-general.php' ); |
| 157 | 197 | |
| 158 | 198 | $message = sprintf( |
| 159 | 199 | /* translators: %s: Plugin settings page URL */ |
| @@ -162,21 +202,23 @@ | ||
| 162 | 202 | ); |
| 163 | 203 | |
| 164 | 204 | echo '<p>', wp_kses_post( $message ), '</p>'; |
| 165 | 205 | |
| 166 | - return; | |
| 206 | + return ''; | |
| 167 | 207 | } |
| 168 | 208 | |
| 209 | + $instance = $this->get_widget_settings( $current_settings ); | |
| 210 | + | |
| 169 | 211 | // editable fields: title. |
| 170 | - $title = ! empty( $instance['title'] ) ? $instance['title'] : ''; | |
| 171 | - $return_limit = ! empty( $instance['return_limit'] ) ? (int) $instance['return_limit'] : 5; | |
| 172 | - $display_direction = ! empty( $instance['display_direction'] ) ? $instance['display_direction'] : 'vertical'; | |
| 173 | - $published_within = ! empty( $instance['published_within'] ) ? $instance['published_within'] : 0; | |
| 174 | - $sort = ! empty( $instance['sort'] ) ? $instance['sort'] : 'score'; | |
| 175 | - $boost = ! empty( $instance['boost'] ) ? $instance['boost'] : 'views'; | |
| 176 | - $personalize_results = ! empty( $instance['personalize_results'] ) ? $instance['personalize_results'] : false; | |
| 177 | - $img_src = ! empty( $instance['img_src'] ) ? $instance['img_src'] : 'parsely_thumb'; | |
| 178 | - $display_author = ! empty( $instance['display_author'] ) ? $instance['display_author'] : false; | |
| 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']; | |
| 179 | 221 | |
| 180 | 222 | $instance['return_limit'] = $return_limit; |
| 181 | 223 | $instance['display_direction'] = $display_direction; |
| 182 | 224 | $instance['published_within'] = $published_within; |
| @@ -200,9 +242,9 @@ | ||
| 200 | 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> |
| 201 | 243 | </p> |
| 202 | 244 | <p> |
| 203 | 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> |
| 204 | - <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" /> | |
| 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" /> | |
| 205 | 247 | </p> |
| 206 | 248 | <p> |
| 207 | 249 | <fieldset> |
| 208 | 250 | <legend><?php esc_html_e( 'Display entries:', 'wp-parsely' ); ?></legend> |
| @@ -249,22 +291,25 @@ | ||
| 249 | 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' ); ?> /> |
| 250 | 292 | <label for="<?php echo esc_attr( $this->get_field_id( 'personalize_results' ) ); ?>"><?php esc_html_e( 'Personalize recommended results', 'wp-parsely' ); ?></label> |
| 251 | 293 | </p> |
| 252 | 294 | <?php |
| 295 | + | |
| 296 | + return ''; | |
| 253 | 297 | } |
| 254 | 298 | |
| 255 | 299 | /** |
| 256 | - * This is the update function | |
| 300 | + * This is the update function. | |
| 257 | 301 | * |
| 258 | - * @param array $new_instance The new values for the db. | |
| 259 | - * @param array $old_instance Values saved to the db. | |
| 260 | - * @return array | |
| 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 | |
| 261 | 306 | */ |
| 262 | - public function update( $new_instance, $old_instance ): array { | |
| 307 | + public function update( $new_instance, $old_instance ) /* @phpstan-ignore-line */ { | |
| 263 | 308 | $instance = $old_instance; |
| 264 | 309 | $instance['title'] = trim( wp_kses_post( $new_instance['title'] ) ); |
| 265 | - $instance['published_within'] = is_int( $new_instance['published_within'] ) ? $new_instance['published_within'] : (int) trim( $new_instance['published_within'] ); | |
| 266 | - $instance['return_limit'] = (int) $new_instance['return_limit'] <= 20 ? (int) $new_instance['return_limit'] : 20; | |
| 310 | + $instance['published_within'] = $new_instance['published_within']; | |
| 311 | + $instance['return_limit'] = $new_instance['return_limit'] <= 20 ? $new_instance['return_limit'] : 20; | |
| 267 | 312 | $instance['display_direction'] = trim( $new_instance['display_direction'] ); |
| 268 | 313 | $instance['sort'] = trim( $new_instance['sort'] ); |
| 269 | 314 | $instance['boost'] = trim( $new_instance['boost'] ); |
| 270 | 315 | $instance['display_author'] = $new_instance['display_author']; |
| @@ -269,13 +314,14 @@ | ||
| 269 | 314 | $instance['boost'] = trim( $new_instance['boost'] ); |
| 270 | 315 | $instance['display_author'] = $new_instance['display_author']; |
| 271 | 316 | $instance['personalize_results'] = $new_instance['personalize_results']; |
| 272 | 317 | $instance['img_src'] = trim( $new_instance['img_src'] ); |
| 318 | + | |
| 273 | 319 | return $instance; |
| 274 | 320 | } |
| 275 | 321 | |
| 276 | 322 | /** |
| 277 | - * Return the list of boost parameters, values and labels. | |
| 323 | + * Returns the list of boost parameters, values and labels. | |
| 278 | 324 | * |
| 279 | 325 | * @since 2.5.0 |
| 280 | 326 | * |
| 281 | 327 | * @return array<string, string> Boost parameters values and labels. |
| @@ -293,9 +339,9 @@ | ||
| 293 | 339 | 'engaged_minutes' => __( 'Total engagement time in minutes', 'wp-parsely' ), |
| 294 | 340 | 'avg_engaged' => __( 'Engaged minutes spent by total visitors', 'wp-parsely' ), |
| 295 | 341 | 'avg_engaged_new' => __( 'Average engaged minutes spent by new visitors', 'wp-parsely' ), |
| 296 | 342 | 'avg_engaged_returning' => __( 'Average engaged minutes spent by returning visitors', 'wp-parsely' ), |
| 297 | - 'social_interactions' => __( 'Total for Facebook, Twitter, LinkedIn, and Pinterest', 'wp-parsely' ), | |
| 343 | + 'social_interactions' => __( 'Total social interactions', 'wp-parsely' ), | |
| 298 | 344 | 'fb_interactions' => __( 'Count of Facebook shares, likes, and comments', 'wp-parsely' ), |
| 299 | 345 | 'tw_interactions' => __( 'Count of Twitter tweets and retweets', 'wp-parsely' ), |
| 300 | 346 | 'pi_interactions' => __( 'Count of Pinterest pins', 'wp-parsely' ), |
| 301 | 347 | 'social_referrals' => __( 'Page views where the referrer was any social network', 'wp-parsely' ), |
| @@ -305,31 +351,36 @@ | ||
| 305 | 351 | ); |
| 306 | 352 | } |
| 307 | 353 | |
| 308 | 354 | /** |
| 309 | - * Check if both the API key and API secret settings are populated with non-empty values. | |
| 355 | + * Checks if both the Site ID and API secret settings are populated with | |
| 356 | + * non-empty values. | |
| 310 | 357 | * |
| 311 | 358 | * @since 2.5.0 |
| 312 | 359 | * |
| 313 | - * @return bool True if apikey and api_secret settings are not empty strings. False otherwise. | |
| 360 | + * @return bool True if Site ID and API Secret settings are set. | |
| 361 | + * False otherwise. | |
| 314 | 362 | */ |
| 315 | - private function api_key_and_secret_are_populated(): bool { | |
| 316 | - $options = get_option( 'parsely' ); | |
| 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 | + } | |
| 317 | 366 | |
| 318 | - // No options are saved, so API key is not available. | |
| 319 | - if ( ! is_array( $options ) ) { | |
| 320 | - return false; | |
| 321 | - } | |
| 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; | |
| 322 | 383 | |
| 323 | - // Parse.ly Site ID settings field is not populated. | |
| 324 | - if ( ! array_key_exists( 'apikey', $options ) || '' === $options['apikey'] ) { | |
| 325 | - return false; | |
| 326 | - } | |
| 327 | - | |
| 328 | - // Parse.ly API Secret settings field is not populated. | |
| 329 | - if ( ! array_key_exists( 'api_secret', $options ) || '' === $options['api_secret'] ) { | |
| 330 | - return false; | |
| 331 | - } | |
| 332 | - | |
| 333 | - return true; | |
| 384 | + return array_merge( self::$default_widget_settings, $widget_settings ); | |
| 334 | 385 | } |
| 335 | 386 | } |