Нет описания

abstract-wc-widget.php 12KB

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