class-wp-widget.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. <?php
  2. /**
  3. * Widget API: WP_Widget base class
  4. *
  5. * @package WordPress
  6. * @subpackage Widgets
  7. * @since 4.4.0
  8. */
  9. /**
  10. * Core base class extended to register widgets.
  11. *
  12. * This class must be extended for each widget, and WP_Widget::widget() must be overridden.
  13. *
  14. * If adding widget options, WP_Widget::update() and WP_Widget::form() should also be overridden.
  15. *
  16. * @since 2.8.0
  17. * @since 4.4.0 Moved to its own file from wp-includes/widgets.php
  18. */
  19. class WP_Widget {
  20. /**
  21. * Root ID for all widgets of this type.
  22. *
  23. * @since 2.8.0
  24. * @var mixed|string
  25. */
  26. public $id_base;
  27. /**
  28. * Name for this widget type.
  29. *
  30. * @since 2.8.0
  31. * @var string
  32. */
  33. public $name;
  34. /**
  35. * Option name for this widget type.
  36. *
  37. * @since 2.8.0
  38. * @var string
  39. */
  40. public $option_name;
  41. /**
  42. * Alt option name for this widget type.
  43. *
  44. * @since 2.8.0
  45. * @var string
  46. */
  47. public $alt_option_name;
  48. /**
  49. * Option array passed to wp_register_sidebar_widget().
  50. *
  51. * @since 2.8.0
  52. * @var array
  53. */
  54. public $widget_options;
  55. /**
  56. * Option array passed to wp_register_widget_control().
  57. *
  58. * @since 2.8.0
  59. * @var array
  60. */
  61. public $control_options;
  62. /**
  63. * Unique ID number of the current instance.
  64. *
  65. * @since 2.8.0
  66. * @var bool|int
  67. */
  68. public $number = false;
  69. /**
  70. * Unique ID string of the current instance (id_base-number).
  71. *
  72. * @since 2.8.0
  73. * @var bool|string
  74. */
  75. public $id = false;
  76. /**
  77. * Whether the widget data has been updated.
  78. *
  79. * Set to true when the data is updated after a POST submit - ensures it does
  80. * not happen twice.
  81. *
  82. * @since 2.8.0
  83. * @var bool
  84. */
  85. public $updated = false;
  86. //
  87. // Member functions that must be overridden by subclasses.
  88. //
  89. /**
  90. * Echoes the widget content.
  91. *
  92. * Sub-classes should over-ride this function to generate their widget code.
  93. *
  94. * @since 2.8.0
  95. *
  96. * @param array $args Display arguments including 'before_title', 'after_title',
  97. * 'before_widget', and 'after_widget'.
  98. * @param array $instance The settings for the particular instance of the widget.
  99. */
  100. public function widget( $args, $instance ) {
  101. die( 'function WP_Widget::widget() must be over-ridden in a sub-class.' );
  102. }
  103. /**
  104. * Updates a particular instance of a widget.
  105. *
  106. * This function should check that `$new_instance` is set correctly. The newly-calculated
  107. * value of `$instance` should be returned. If false is returned, the instance won't be
  108. * saved/updated.
  109. *
  110. * @since 2.8.0
  111. *
  112. * @param array $new_instance New settings for this instance as input by the user via
  113. * WP_Widget::form().
  114. * @param array $old_instance Old settings for this instance.
  115. * @return array Settings to save or bool false to cancel saving.
  116. */
  117. public function update( $new_instance, $old_instance ) {
  118. return $new_instance;
  119. }
  120. /**
  121. * Outputs the settings update form.
  122. *
  123. * @since 2.8.0
  124. *
  125. * @param array $instance Current settings.
  126. * @return string Default return is 'noform'.
  127. */
  128. public function form( $instance ) {
  129. echo '<p class="no-options-widget">' . __( 'There are no options for this widget.' ) . '</p>';
  130. return 'noform';
  131. }
  132. // Functions you'll need to call.
  133. /**
  134. * PHP5 constructor.
  135. *
  136. * @since 2.8.0
  137. *
  138. * @param string $id_base Optional Base ID for the widget, lowercase and unique. If left empty,
  139. * a portion of the widget's class name will be used Has to be unique.
  140. * @param string $name Name for the widget displayed on the configuration page.
  141. * @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for information
  142. * on accepted arguments. Default empty array.
  143. * @param array $control_options Optional. Widget control options. See wp_register_widget_control() for
  144. * information on accepted arguments. Default empty array.
  145. */
  146. public function __construct( $id_base, $name, $widget_options = array(), $control_options = array() ) {
  147. $this->id_base = empty( $id_base ) ? preg_replace( '/(wp_)?widget_/', '', strtolower( get_class( $this ) ) ) : strtolower( $id_base );
  148. $this->name = $name;
  149. $this->option_name = 'widget_' . $this->id_base;
  150. $this->widget_options = wp_parse_args(
  151. $widget_options,
  152. array(
  153. 'classname' => $this->option_name,
  154. 'customize_selective_refresh' => false,
  155. )
  156. );
  157. $this->control_options = wp_parse_args( $control_options, array( 'id_base' => $this->id_base ) );
  158. }
  159. /**
  160. * PHP4 constructor.
  161. *
  162. * @since 2.8.0
  163. *
  164. * @see __construct()
  165. *
  166. * @param string $id_base Optional Base ID for the widget, lowercase and unique. If left empty,
  167. * a portion of the widget's class name will be used Has to be unique.
  168. * @param string $name Name for the widget displayed on the configuration page.
  169. * @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for information
  170. * on accepted arguments. Default empty array.
  171. * @param array $control_options Optional. Widget control options. See wp_register_widget_control() for
  172. * information on accepted arguments. Default empty array.
  173. */
  174. public function WP_Widget( $id_base, $name, $widget_options = array(), $control_options = array() ) {
  175. _deprecated_constructor( 'WP_Widget', '4.3.0', get_class( $this ) );
  176. WP_Widget::__construct( $id_base, $name, $widget_options, $control_options );
  177. }
  178. /**
  179. * Constructs name attributes for use in form() fields
  180. *
  181. * This function should be used in form() methods to create name attributes for fields
  182. * to be saved by update()
  183. *
  184. * @since 2.8.0
  185. * @since 4.4.0 Array format field names are now accepted.
  186. *
  187. * @param string $field_name Field name
  188. * @return string Name attribute for $field_name
  189. */
  190. public function get_field_name( $field_name ) {
  191. $pos = strpos( $field_name, '[' );
  192. if ( false === $pos ) {
  193. return 'widget-' . $this->id_base . '[' . $this->number . '][' . $field_name . ']';
  194. } else {
  195. return 'widget-' . $this->id_base . '[' . $this->number . '][' . substr_replace( $field_name, '][', $pos, strlen( '[' ) );
  196. }
  197. }
  198. /**
  199. * Constructs id attributes for use in WP_Widget::form() fields.
  200. *
  201. * This function should be used in form() methods to create id attributes
  202. * for fields to be saved by WP_Widget::update().
  203. *
  204. * @since 2.8.0
  205. * @since 4.4.0 Array format field IDs are now accepted.
  206. *
  207. * @param string $field_name Field name.
  208. * @return string ID attribute for `$field_name`.
  209. */
  210. public function get_field_id( $field_name ) {
  211. return 'widget-' . $this->id_base . '-' . $this->number . '-' . trim( str_replace( array( '[]', '[', ']' ), array( '', '-', '' ), $field_name ), '-' );
  212. }
  213. /**
  214. * Register all widget instances of this widget class.
  215. *
  216. * @since 2.8.0
  217. */
  218. public function _register() {
  219. $settings = $this->get_settings();
  220. $empty = true;
  221. // When $settings is an array-like object, get an intrinsic array for use with array_keys().
  222. if ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) {
  223. $settings = $settings->getArrayCopy();
  224. }
  225. if ( is_array( $settings ) ) {
  226. foreach ( array_keys( $settings ) as $number ) {
  227. if ( is_numeric( $number ) ) {
  228. $this->_set( $number );
  229. $this->_register_one( $number );
  230. $empty = false;
  231. }
  232. }
  233. }
  234. if ( $empty ) {
  235. // If there are none, we register the widget's existence with a generic template.
  236. $this->_set( 1 );
  237. $this->_register_one();
  238. }
  239. }
  240. /**
  241. * Sets the internal order number for the widget instance.
  242. *
  243. * @since 2.8.0
  244. *
  245. * @param int $number The unique order number of this widget instance compared to other
  246. * instances of the same class.
  247. */
  248. public function _set( $number ) {
  249. $this->number = $number;
  250. $this->id = $this->id_base . '-' . $number;
  251. }
  252. /**
  253. * Retrieves the widget display callback.
  254. *
  255. * @since 2.8.0
  256. *
  257. * @return callable Display callback.
  258. */
  259. public function _get_display_callback() {
  260. return array( $this, 'display_callback' );
  261. }
  262. /**
  263. * Retrieves the widget update callback.
  264. *
  265. * @since 2.8.0
  266. *
  267. * @return callable Update callback.
  268. */
  269. public function _get_update_callback() {
  270. return array( $this, 'update_callback' );
  271. }
  272. /**
  273. * Retrieves the form callback.
  274. *
  275. * @since 2.8.0
  276. *
  277. * @return callable Form callback.
  278. */
  279. public function _get_form_callback() {
  280. return array( $this, 'form_callback' );
  281. }
  282. /**
  283. * Determines whether the current request is inside the Customizer preview.
  284. *
  285. * If true -- the current request is inside the Customizer preview, then
  286. * the object cache gets suspended and widgets should check this to decide
  287. * whether they should store anything persistently to the object cache,
  288. * to transients, or anywhere else.
  289. *
  290. * @since 3.9.0
  291. *
  292. * @global WP_Customize_Manager $wp_customize
  293. *
  294. * @return bool True if within the Customizer preview, false if not.
  295. */
  296. public function is_preview() {
  297. global $wp_customize;
  298. return ( isset( $wp_customize ) && $wp_customize->is_preview() );
  299. }
  300. /**
  301. * Generates the actual widget content (Do NOT override).
  302. *
  303. * Finds the instance and calls WP_Widget::widget().
  304. *
  305. * @since 2.8.0
  306. *
  307. * @param array $args Display arguments. See WP_Widget::widget() for information
  308. * on accepted arguments.
  309. * @param int|array $widget_args {
  310. * Optional. Internal order number of the widget instance, or array of multi-widget arguments.
  311. * Default 1.
  312. *
  313. * @type int $number Number increment used for multiples of the same widget.
  314. * }
  315. */
  316. public function display_callback( $args, $widget_args = 1 ) {
  317. if ( is_numeric( $widget_args ) ) {
  318. $widget_args = array( 'number' => $widget_args );
  319. }
  320. $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
  321. $this->_set( $widget_args['number'] );
  322. $instances = $this->get_settings();
  323. if ( array_key_exists( $this->number, $instances ) ) {
  324. $instance = $instances[ $this->number ];
  325. /**
  326. * Filters the settings for a particular widget instance.
  327. *
  328. * Returning false will effectively short-circuit display of the widget.
  329. *
  330. * @since 2.8.0
  331. *
  332. * @param array $instance The current widget instance's settings.
  333. * @param WP_Widget $this The current widget instance.
  334. * @param array $args An array of default widget arguments.
  335. */
  336. $instance = apply_filters( 'widget_display_callback', $instance, $this, $args );
  337. if ( false === $instance ) {
  338. return;
  339. }
  340. $was_cache_addition_suspended = wp_suspend_cache_addition();
  341. if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
  342. wp_suspend_cache_addition( true );
  343. }
  344. $this->widget( $args, $instance );
  345. if ( $this->is_preview() ) {
  346. wp_suspend_cache_addition( $was_cache_addition_suspended );
  347. }
  348. }
  349. }
  350. /**
  351. * Handles changed settings (Do NOT override).
  352. *
  353. * @since 2.8.0
  354. *
  355. * @global array $wp_registered_widgets
  356. *
  357. * @param int $deprecated Not used.
  358. */
  359. public function update_callback( $deprecated = 1 ) {
  360. global $wp_registered_widgets;
  361. $all_instances = $this->get_settings();
  362. // We need to update the data
  363. if ( $this->updated ) {
  364. return;
  365. }
  366. if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
  367. // Delete the settings for this instance of the widget
  368. if ( isset( $_POST['the-widget-id'] ) ) {
  369. $del_id = $_POST['the-widget-id'];
  370. } else {
  371. return;
  372. }
  373. if ( isset( $wp_registered_widgets[ $del_id ]['params'][0]['number'] ) ) {
  374. $number = $wp_registered_widgets[ $del_id ]['params'][0]['number'];
  375. if ( $this->id_base . '-' . $number == $del_id ) {
  376. unset( $all_instances[ $number ] );
  377. }
  378. }
  379. } else {
  380. if ( isset( $_POST[ 'widget-' . $this->id_base ] ) && is_array( $_POST[ 'widget-' . $this->id_base ] ) ) {
  381. $settings = $_POST[ 'widget-' . $this->id_base ];
  382. } elseif ( isset( $_POST['id_base'] ) && $_POST['id_base'] == $this->id_base ) {
  383. $num = $_POST['multi_number'] ? (int) $_POST['multi_number'] : (int) $_POST['widget_number'];
  384. $settings = array( $num => array() );
  385. } else {
  386. return;
  387. }
  388. foreach ( $settings as $number => $new_instance ) {
  389. $new_instance = stripslashes_deep( $new_instance );
  390. $this->_set( $number );
  391. $old_instance = isset( $all_instances[ $number ] ) ? $all_instances[ $number ] : array();
  392. $was_cache_addition_suspended = wp_suspend_cache_addition();
  393. if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
  394. wp_suspend_cache_addition( true );
  395. }
  396. $instance = $this->update( $new_instance, $old_instance );
  397. if ( $this->is_preview() ) {
  398. wp_suspend_cache_addition( $was_cache_addition_suspended );
  399. }
  400. /**
  401. * Filters a widget's settings before saving.
  402. *
  403. * Returning false will effectively short-circuit the widget's ability
  404. * to update settings.
  405. *
  406. * @since 2.8.0
  407. *
  408. * @param array $instance The current widget instance's settings.
  409. * @param array $new_instance Array of new widget settings.
  410. * @param array $old_instance Array of old widget settings.
  411. * @param WP_Widget $this The current widget instance.
  412. */
  413. $instance = apply_filters( 'widget_update_callback', $instance, $new_instance, $old_instance, $this );
  414. if ( false !== $instance ) {
  415. $all_instances[ $number ] = $instance;
  416. }
  417. break; // run only once
  418. }
  419. }
  420. $this->save_settings( $all_instances );
  421. $this->updated = true;
  422. }
  423. /**
  424. * Generates the widget control form (Do NOT override).
  425. *
  426. * @since 2.8.0
  427. *
  428. * @param int|array $widget_args {
  429. * Optional. Internal order number of the widget instance, or array of multi-widget arguments.
  430. * Default 1.
  431. *
  432. * @type int $number Number increment used for multiples of the same widget.
  433. * }
  434. * @return string|null
  435. */
  436. public function form_callback( $widget_args = 1 ) {
  437. if ( is_numeric( $widget_args ) ) {
  438. $widget_args = array( 'number' => $widget_args );
  439. }
  440. $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
  441. $all_instances = $this->get_settings();
  442. if ( -1 == $widget_args['number'] ) {
  443. // We echo out a form where 'number' can be set later
  444. $this->_set( '__i__' );
  445. $instance = array();
  446. } else {
  447. $this->_set( $widget_args['number'] );
  448. $instance = $all_instances[ $widget_args['number'] ];
  449. }
  450. /**
  451. * Filters the widget instance's settings before displaying the control form.
  452. *
  453. * Returning false effectively short-circuits display of the control form.
  454. *
  455. * @since 2.8.0
  456. *
  457. * @param array $instance The current widget instance's settings.
  458. * @param WP_Widget $this The current widget instance.
  459. */
  460. $instance = apply_filters( 'widget_form_callback', $instance, $this );
  461. $return = null;
  462. if ( false !== $instance ) {
  463. $return = $this->form( $instance );
  464. /**
  465. * Fires at the end of the widget control form.
  466. *
  467. * Use this hook to add extra fields to the widget form. The hook
  468. * is only fired if the value passed to the 'widget_form_callback'
  469. * hook is not false.
  470. *
  471. * Note: If the widget has no form, the text echoed from the default
  472. * form method can be hidden using CSS.
  473. *
  474. * @since 2.8.0
  475. *
  476. * @param WP_Widget $this The widget instance (passed by reference).
  477. * @param null $return Return null if new fields are added.
  478. * @param array $instance An array of the widget's settings.
  479. */
  480. do_action_ref_array( 'in_widget_form', array( &$this, &$return, $instance ) );
  481. }
  482. return $return;
  483. }
  484. /**
  485. * Registers an instance of the widget class.
  486. *
  487. * @since 2.8.0
  488. *
  489. * @param integer $number Optional. The unique order number of this widget instance
  490. * compared to other instances of the same class. Default -1.
  491. */
  492. public function _register_one( $number = -1 ) {
  493. wp_register_sidebar_widget( $this->id, $this->name, $this->_get_display_callback(), $this->widget_options, array( 'number' => $number ) );
  494. _register_widget_update_callback( $this->id_base, $this->_get_update_callback(), $this->control_options, array( 'number' => -1 ) );
  495. _register_widget_form_callback( $this->id, $this->name, $this->_get_form_callback(), $this->control_options, array( 'number' => $number ) );
  496. }
  497. /**
  498. * Saves the settings for all instances of the widget class.
  499. *
  500. * @since 2.8.0
  501. *
  502. * @param array $settings Multi-dimensional array of widget instance settings.
  503. */
  504. public function save_settings( $settings ) {
  505. $settings['_multiwidget'] = 1;
  506. update_option( $this->option_name, $settings );
  507. }
  508. /**
  509. * Retrieves the settings for all instances of the widget class.
  510. *
  511. * @since 2.8.0
  512. *
  513. * @return array Multi-dimensional array of widget instance settings.
  514. */
  515. public function get_settings() {
  516. $settings = get_option( $this->option_name );
  517. if ( false === $settings ) {
  518. if ( isset( $this->alt_option_name ) ) {
  519. $settings = get_option( $this->alt_option_name );
  520. } else {
  521. // Save an option so it can be autoloaded next time.
  522. $this->save_settings( array() );
  523. }
  524. }
  525. if ( ! is_array( $settings ) && ! ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) ) {
  526. $settings = array();
  527. }
  528. if ( ! empty( $settings ) && ! isset( $settings['_multiwidget'] ) ) {
  529. // Old format, convert if single widget.
  530. $settings = wp_convert_widget_settings( $this->id_base, $this->option_name, $settings );
  531. }
  532. unset( $settings['_multiwidget'], $settings['__i__'] );
  533. return $settings;
  534. }
  535. }