class-wp-widget.php 18 KB

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