abstract-wc-widget.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. /**
  3. * Abstract widget class
  4. *
  5. * @class WC_Widget
  6. * @package WooCommerce/Abstracts
  7. */
  8. if ( ! defined( 'ABSPATH' ) ) {
  9. exit;
  10. }
  11. /**
  12. * WC_Widget
  13. *
  14. * @package WooCommerce/Abstracts
  15. * @version 2.5.0
  16. * @extends WP_Widget
  17. */
  18. abstract class WC_Widget extends WP_Widget {
  19. /**
  20. * CSS class.
  21. *
  22. * @var string
  23. */
  24. public $widget_cssclass;
  25. /**
  26. * Widget description.
  27. *
  28. * @var string
  29. */
  30. public $widget_description;
  31. /**
  32. * Widget ID.
  33. *
  34. * @var string
  35. */
  36. public $widget_id;
  37. /**
  38. * Widget name.
  39. *
  40. * @var string
  41. */
  42. public $widget_name;
  43. /**
  44. * Settings.
  45. *
  46. * @var array
  47. */
  48. public $settings;
  49. /**
  50. * Constructor.
  51. */
  52. public function __construct() {
  53. $widget_ops = array(
  54. 'classname' => $this->widget_cssclass,
  55. 'description' => $this->widget_description,
  56. 'customize_selective_refresh' => true,
  57. );
  58. parent::__construct( $this->widget_id, $this->widget_name, $widget_ops );
  59. add_action( 'save_post', array( $this, 'flush_widget_cache' ) );
  60. add_action( 'deleted_post', array( $this, 'flush_widget_cache' ) );
  61. add_action( 'switch_theme', array( $this, 'flush_widget_cache' ) );
  62. }
  63. /**
  64. * Get cached widget.
  65. *
  66. * @param array $args Arguments.
  67. * @return bool true if the widget is cached otherwise false
  68. */
  69. public function get_cached_widget( $args ) {
  70. $cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' );
  71. if ( ! is_array( $cache ) ) {
  72. $cache = array();
  73. }
  74. if ( isset( $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] ) ) {
  75. echo $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ]; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
  76. return true;
  77. }
  78. return false;
  79. }
  80. /**
  81. * Cache the widget.
  82. *
  83. * @param array $args Arguments.
  84. * @param string $content Content.
  85. * @return string the content that was cached
  86. */
  87. public function cache_widget( $args, $content ) {
  88. $cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' );
  89. if ( ! is_array( $cache ) ) {
  90. $cache = array();
  91. }
  92. $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] = $content;
  93. wp_cache_set( $this->get_widget_id_for_cache( $this->widget_id ), $cache, 'widget' );
  94. return $content;
  95. }
  96. /**
  97. * Flush the cache.
  98. */
  99. public function flush_widget_cache() {
  100. foreach ( array( 'https', 'http' ) as $scheme ) {
  101. wp_cache_delete( $this->get_widget_id_for_cache( $this->widget_id, $scheme ), 'widget' );
  102. }
  103. }
  104. /**
  105. * Output the html at the start of a widget.
  106. *
  107. * @param array $args Arguments.
  108. * @param array $instance Instance.
  109. */
  110. public function widget_start( $args, $instance ) {
  111. echo $args['before_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
  112. if ( $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base ) ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found
  113. echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
  114. }
  115. }
  116. /**
  117. * Output the html at the end of a widget.
  118. *
  119. * @param array $args Arguments.
  120. */
  121. public function widget_end( $args ) {
  122. echo $args['after_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
  123. }
  124. /**
  125. * Updates a particular instance of a widget.
  126. *
  127. * @see WP_Widget->update
  128. * @param array $new_instance New instance.
  129. * @param array $old_instance Old instance.
  130. * @return array
  131. */
  132. public function update( $new_instance, $old_instance ) {
  133. $instance = $old_instance;
  134. if ( empty( $this->settings ) ) {
  135. return $instance;
  136. }
  137. // Loop settings and get values to save.
  138. foreach ( $this->settings as $key => $setting ) {
  139. if ( ! isset( $setting['type'] ) ) {
  140. continue;
  141. }
  142. // Format the value based on settings type.
  143. switch ( $setting['type'] ) {
  144. case 'number':
  145. $instance[ $key ] = absint( $new_instance[ $key ] );
  146. if ( isset( $setting['min'] ) && '' !== $setting['min'] ) {
  147. $instance[ $key ] = max( $instance[ $key ], $setting['min'] );
  148. }
  149. if ( isset( $setting['max'] ) && '' !== $setting['max'] ) {
  150. $instance[ $key ] = min( $instance[ $key ], $setting['max'] );
  151. }
  152. break;
  153. case 'textarea':
  154. $instance[ $key ] = wp_kses( trim( wp_unslash( $new_instance[ $key ] ) ), wp_kses_allowed_html( 'post' ) );
  155. break;
  156. case 'checkbox':
  157. $instance[ $key ] = empty( $new_instance[ $key ] ) ? 0 : 1;
  158. break;
  159. default:
  160. $instance[ $key ] = isset( $new_instance[ $key ] ) ? sanitize_text_field( $new_instance[ $key ] ) : $setting['std'];
  161. break;
  162. }
  163. /**
  164. * Sanitize the value of a setting.
  165. */
  166. $instance[ $key ] = apply_filters( 'woocommerce_widget_settings_sanitize_option', $instance[ $key ], $new_instance, $key, $setting );
  167. }
  168. $this->flush_widget_cache();
  169. return $instance;
  170. }
  171. /**
  172. * Outputs the settings update form.
  173. *
  174. * @see WP_Widget->form
  175. *
  176. * @param array $instance Instance.
  177. */
  178. public function form( $instance ) {
  179. if ( empty( $this->settings ) ) {
  180. return;
  181. }
  182. foreach ( $this->settings as $key => $setting ) {
  183. $class = isset( $setting['class'] ) ? $setting['class'] : '';
  184. $value = isset( $instance[ $key ] ) ? $instance[ $key ] : $setting['std'];
  185. switch ( $setting['type'] ) {
  186. case 'text':
  187. ?>
  188. <p>
  189. <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; ?></label><?php // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
  190. <input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="text" value="<?php echo esc_attr( $value ); ?>" />
  191. </p>
  192. <?php
  193. break;
  194. case 'number':
  195. ?>
  196. <p>
  197. <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
  198. <input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="number" step="<?php echo esc_attr( $setting['step'] ); ?>" min="<?php echo esc_attr( $setting['min'] ); ?>" max="<?php echo esc_attr( $setting['max'] ); ?>" value="<?php echo esc_attr( $value ); ?>" />
  199. </p>
  200. <?php
  201. break;
  202. case 'select':
  203. ?>
  204. <p>
  205. <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
  206. <select class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>">
  207. <?php foreach ( $setting['options'] as $option_key => $option_value ) : ?>
  208. <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $option_key, $value ); ?>><?php echo esc_html( $option_value ); ?></option>
  209. <?php endforeach; ?>
  210. </select>
  211. </p>
  212. <?php
  213. break;
  214. case 'textarea':
  215. ?>
  216. <p>
  217. <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
  218. <textarea class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" cols="20" rows="3"><?php echo esc_textarea( $value ); ?></textarea>
  219. <?php if ( isset( $setting['desc'] ) ) : ?>
  220. <small><?php echo esc_html( $setting['desc'] ); ?></small>
  221. <?php endif; ?>
  222. </p>
  223. <?php
  224. break;
  225. case 'checkbox':
  226. ?>
  227. <p>
  228. <input class="checkbox <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="checkbox" value="1" <?php checked( $value, 1 ); ?> />
  229. <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
  230. </p>
  231. <?php
  232. break;
  233. // Default: run an action.
  234. default:
  235. do_action( 'woocommerce_widget_field_' . $setting['type'], $key, $value, $setting, $instance );
  236. break;
  237. }
  238. }
  239. }
  240. /**
  241. * Get current page URL with various filtering props supported by WC.
  242. *
  243. * @return string
  244. * @since 3.3.0
  245. */
  246. protected function get_current_page_url() {
  247. if ( defined( 'SHOP_IS_ON_FRONT' ) ) {
  248. $link = home_url();
  249. } elseif ( is_shop() ) {
  250. $link = get_permalink( wc_get_page_id( 'shop' ) );
  251. } elseif ( is_product_category() ) {
  252. $link = get_term_link( get_query_var( 'product_cat' ), 'product_cat' );
  253. } elseif ( is_product_tag() ) {
  254. $link = get_term_link( get_query_var( 'product_tag' ), 'product_tag' );
  255. } else {
  256. $queried_object = get_queried_object();
  257. $link = get_term_link( $queried_object->slug, $queried_object->taxonomy );
  258. }
  259. // Min/Max.
  260. if ( isset( $_GET['min_price'] ) ) {
  261. $link = add_query_arg( 'min_price', wc_clean( wp_unslash( $_GET['min_price'] ) ), $link );
  262. }
  263. if ( isset( $_GET['max_price'] ) ) {
  264. $link = add_query_arg( 'max_price', wc_clean( wp_unslash( $_GET['max_price'] ) ), $link );
  265. }
  266. // Order by.
  267. if ( isset( $_GET['orderby'] ) ) {
  268. $link = add_query_arg( 'orderby', wc_clean( wp_unslash( $_GET['orderby'] ) ), $link );
  269. }
  270. /**
  271. * Search Arg.
  272. * To support quote characters, first they are decoded from &quot; entities, then URL encoded.
  273. */
  274. if ( get_search_query() ) {
  275. $link = add_query_arg( 's', rawurlencode( htmlspecialchars_decode( get_search_query() ) ), $link );
  276. }
  277. // Post Type Arg.
  278. if ( isset( $_GET['post_type'] ) ) {
  279. $link = add_query_arg( 'post_type', wc_clean( wp_unslash( $_GET['post_type'] ) ), $link );
  280. }
  281. // Min Rating Arg.
  282. if ( isset( $_GET['rating_filter'] ) ) {
  283. $link = add_query_arg( 'rating_filter', wc_clean( wp_unslash( $_GET['rating_filter'] ) ), $link );
  284. }
  285. // All current filters.
  286. if ( $_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes() ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found
  287. foreach ( $_chosen_attributes as $name => $data ) {
  288. $filter_name = sanitize_title( str_replace( 'pa_', '', $name ) );
  289. if ( ! empty( $data['terms'] ) ) {
  290. $link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link );
  291. }
  292. if ( 'or' === $data['query_type'] ) {
  293. $link = add_query_arg( 'query_type_' . $filter_name, 'or', $link );
  294. }
  295. }
  296. }
  297. return $link;
  298. }
  299. /**
  300. * Get widget id plus scheme/protocol to prevent serving mixed content from (persistently) cached widgets.
  301. *
  302. * @since 3.4.0
  303. * @param string $widget_id Id of the cached widget.
  304. * @param string $scheme Scheme for the widget id.
  305. * @return string Widget id including scheme/protocol.
  306. */
  307. protected function get_widget_id_for_cache( $widget_id, $scheme = '' ) {
  308. if ( $scheme ) {
  309. $widget_id_for_cache = $widget_id . '-' . $scheme;
  310. } else {
  311. $widget_id_for_cache = $widget_id . '-' . ( is_ssl() ? 'https' : 'http' );
  312. }
  313. return apply_filters( 'woocommerce_cached_widget_id', $widget_id_for_cache );
  314. }
  315. }