/wp-includes/class-wp-widget.php

https://bitbucket.org/skyarch-iijima/wordpress · PHP · 588 lines · 196 code · 64 blank · 328 comment · 44 complexity · d8592b30a4f80781bce9046616720829 MD5 · raw file

  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( $widget_options, array( 'classname' => $this->option_name, 'customize_selective_refresh' => false ) );
  151. $this->control_options = wp_parse_args( $control_options, array( 'id_base' => $this->id_base ) );
  152. }
  153. /**
  154. * PHP4 constructor.
  155. *
  156. * @since 2.8.0
  157. *
  158. * @see __construct()
  159. *
  160. * @param string $id_base Optional Base ID for the widget, lowercase and unique. If left empty,
  161. * a portion of the widget's class name will be used Has to be unique.
  162. * @param string $name Name for the widget displayed on the configuration page.
  163. * @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for information
  164. * on accepted arguments. Default empty array.
  165. * @param array $control_options Optional. Widget control options. See wp_register_widget_control() for
  166. * information on accepted arguments. Default empty array.
  167. */
  168. public function WP_Widget( $id_base, $name, $widget_options = array(), $control_options = array() ) {
  169. _deprecated_constructor( 'WP_Widget', '4.3.0', get_class( $this ) );
  170. WP_Widget::__construct( $id_base, $name, $widget_options, $control_options );
  171. }
  172. /**
  173. * Constructs name attributes for use in form() fields
  174. *
  175. * This function should be used in form() methods to create name attributes for fields
  176. * to be saved by update()
  177. *
  178. * @since 2.8.0
  179. * @since 4.4.0 Array format field names are now accepted.
  180. *
  181. * @param string $field_name Field name
  182. * @return string Name attribute for $field_name
  183. */
  184. public function get_field_name($field_name) {
  185. if ( false === $pos = strpos( $field_name, '[' ) ) {
  186. return 'widget-' . $this->id_base . '[' . $this->number . '][' . $field_name . ']';
  187. } else {
  188. return 'widget-' . $this->id_base . '[' . $this->number . '][' . substr_replace( $field_name, '][', $pos, strlen( '[' ) );
  189. }
  190. }
  191. /**
  192. * Constructs id attributes for use in WP_Widget::form() fields.
  193. *
  194. * This function should be used in form() methods to create id attributes
  195. * for fields to be saved by WP_Widget::update().
  196. *
  197. * @since 2.8.0
  198. * @since 4.4.0 Array format field IDs are now accepted.
  199. *
  200. * @param string $field_name Field name.
  201. * @return string ID attribute for `$field_name`.
  202. */
  203. public function get_field_id( $field_name ) {
  204. return 'widget-' . $this->id_base . '-' . $this->number . '-' . trim( str_replace( array( '[]', '[', ']' ), array( '', '-', '' ), $field_name ), '-' );
  205. }
  206. /**
  207. * Register all widget instances of this widget class.
  208. *
  209. * @since 2.8.0
  210. */
  211. public function _register() {
  212. $settings = $this->get_settings();
  213. $empty = true;
  214. // When $settings is an array-like object, get an intrinsic array for use with array_keys().
  215. if ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) {
  216. $settings = $settings->getArrayCopy();
  217. }
  218. if ( is_array( $settings ) ) {
  219. foreach ( array_keys( $settings ) as $number ) {
  220. if ( is_numeric( $number ) ) {
  221. $this->_set( $number );
  222. $this->_register_one( $number );
  223. $empty = false;
  224. }
  225. }
  226. }
  227. if ( $empty ) {
  228. // If there are none, we register the widget's existence with a generic template.
  229. $this->_set( 1 );
  230. $this->_register_one();
  231. }
  232. }
  233. /**
  234. * Sets the internal order number for the widget instance.
  235. *
  236. * @since 2.8.0
  237. *
  238. * @param int $number The unique order number of this widget instance compared to other
  239. * instances of the same class.
  240. */
  241. public function _set($number) {
  242. $this->number = $number;
  243. $this->id = $this->id_base . '-' . $number;
  244. }
  245. /**
  246. * Retrieves the widget display callback.
  247. *
  248. * @since 2.8.0
  249. *
  250. * @return callable Display callback.
  251. */
  252. public function _get_display_callback() {
  253. return array($this, 'display_callback');
  254. }
  255. /**
  256. * Retrieves the widget update callback.
  257. *
  258. * @since 2.8.0
  259. *
  260. * @return callable Update callback.
  261. */
  262. public function _get_update_callback() {
  263. return array($this, 'update_callback');
  264. }
  265. /**
  266. * Retrieves the form callback.
  267. *
  268. * @since 2.8.0
  269. *
  270. * @return callable Form callback.
  271. */
  272. public function _get_form_callback() {
  273. return array($this, 'form_callback');
  274. }
  275. /**
  276. * Determines whether the current request is inside the Customizer preview.
  277. *
  278. * If true -- the current request is inside the Customizer preview, then
  279. * the object cache gets suspended and widgets should check this to decide
  280. * whether they should store anything persistently to the object cache,
  281. * to transients, or anywhere else.
  282. *
  283. * @since 3.9.0
  284. *
  285. * @global WP_Customize_Manager $wp_customize
  286. *
  287. * @return bool True if within the Customizer preview, false if not.
  288. */
  289. public function is_preview() {
  290. global $wp_customize;
  291. return ( isset( $wp_customize ) && $wp_customize->is_preview() ) ;
  292. }
  293. /**
  294. * Generates the actual widget content (Do NOT override).
  295. *
  296. * Finds the instance and calls WP_Widget::widget().
  297. *
  298. * @since 2.8.0
  299. *
  300. * @param array $args Display arguments. See WP_Widget::widget() for information
  301. * on accepted arguments.
  302. * @param int|array $widget_args {
  303. * Optional. Internal order number of the widget instance, or array of multi-widget arguments.
  304. * Default 1.
  305. *
  306. * @type int $number Number increment used for multiples of the same widget.
  307. * }
  308. */
  309. public function display_callback( $args, $widget_args = 1 ) {
  310. if ( is_numeric( $widget_args ) ) {
  311. $widget_args = array( 'number' => $widget_args );
  312. }
  313. $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
  314. $this->_set( $widget_args['number'] );
  315. $instances = $this->get_settings();
  316. if ( array_key_exists( $this->number, $instances ) ) {
  317. $instance = $instances[ $this->number ];
  318. /**
  319. * Filters the settings for a particular widget instance.
  320. *
  321. * Returning false will effectively short-circuit display of the widget.
  322. *
  323. * @since 2.8.0
  324. *
  325. * @param array $instance The current widget instance's settings.
  326. * @param WP_Widget $this The current widget instance.
  327. * @param array $args An array of default widget arguments.
  328. */
  329. $instance = apply_filters( 'widget_display_callback', $instance, $this, $args );
  330. if ( false === $instance ) {
  331. return;
  332. }
  333. $was_cache_addition_suspended = wp_suspend_cache_addition();
  334. if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
  335. wp_suspend_cache_addition( true );
  336. }
  337. $this->widget( $args, $instance );
  338. if ( $this->is_preview() ) {
  339. wp_suspend_cache_addition( $was_cache_addition_suspended );
  340. }
  341. }
  342. }
  343. /**
  344. * Handles changed settings (Do NOT override).
  345. *
  346. * @since 2.8.0
  347. *
  348. * @global array $wp_registered_widgets
  349. *
  350. * @param int $deprecated Not used.
  351. */
  352. public function update_callback( $deprecated = 1 ) {
  353. global $wp_registered_widgets;
  354. $all_instances = $this->get_settings();
  355. // We need to update the data
  356. if ( $this->updated )
  357. return;
  358. if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
  359. // Delete the settings for this instance of the widget
  360. if ( isset($_POST['the-widget-id']) )
  361. $del_id = $_POST['the-widget-id'];
  362. else
  363. return;
  364. if ( isset($wp_registered_widgets[$del_id]['params'][0]['number']) ) {
  365. $number = $wp_registered_widgets[$del_id]['params'][0]['number'];
  366. if ( $this->id_base . '-' . $number == $del_id )
  367. unset($all_instances[$number]);
  368. }
  369. } else {
  370. if ( isset($_POST['widget-' . $this->id_base]) && is_array($_POST['widget-' . $this->id_base]) ) {
  371. $settings = $_POST['widget-' . $this->id_base];
  372. } elseif ( isset($_POST['id_base']) && $_POST['id_base'] == $this->id_base ) {
  373. $num = $_POST['multi_number'] ? (int) $_POST['multi_number'] : (int) $_POST['widget_number'];
  374. $settings = array( $num => array() );
  375. } else {
  376. return;
  377. }
  378. foreach ( $settings as $number => $new_instance ) {
  379. $new_instance = stripslashes_deep($new_instance);
  380. $this->_set($number);
  381. $old_instance = isset($all_instances[$number]) ? $all_instances[$number] : array();
  382. $was_cache_addition_suspended = wp_suspend_cache_addition();
  383. if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
  384. wp_suspend_cache_addition( true );
  385. }
  386. $instance = $this->update( $new_instance, $old_instance );
  387. if ( $this->is_preview() ) {
  388. wp_suspend_cache_addition( $was_cache_addition_suspended );
  389. }
  390. /**
  391. * Filters a widget's settings before saving.
  392. *
  393. * Returning false will effectively short-circuit the widget's ability
  394. * to update settings.
  395. *
  396. * @since 2.8.0
  397. *
  398. * @param array $instance The current widget instance's settings.
  399. * @param array $new_instance Array of new widget settings.
  400. * @param array $old_instance Array of old widget settings.
  401. * @param WP_Widget $this The current widget instance.
  402. */
  403. $instance = apply_filters( 'widget_update_callback', $instance, $new_instance, $old_instance, $this );
  404. if ( false !== $instance ) {
  405. $all_instances[$number] = $instance;
  406. }
  407. break; // run only once
  408. }
  409. }
  410. $this->save_settings($all_instances);
  411. $this->updated = true;
  412. }
  413. /**
  414. * Generates the widget control form (Do NOT override).
  415. *
  416. * @since 2.8.0
  417. *
  418. * @param int|array $widget_args {
  419. * Optional. Internal order number of the widget instance, or array of multi-widget arguments.
  420. * Default 1.
  421. *
  422. * @type int $number Number increment used for multiples of the same widget.
  423. * }
  424. * @return string|null
  425. */
  426. public function form_callback( $widget_args = 1 ) {
  427. if ( is_numeric($widget_args) )
  428. $widget_args = array( 'number' => $widget_args );
  429. $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
  430. $all_instances = $this->get_settings();
  431. if ( -1 == $widget_args['number'] ) {
  432. // We echo out a form where 'number' can be set later
  433. $this->_set('__i__');
  434. $instance = array();
  435. } else {
  436. $this->_set($widget_args['number']);
  437. $instance = $all_instances[ $widget_args['number'] ];
  438. }
  439. /**
  440. * Filters the widget instance's settings before displaying the control form.
  441. *
  442. * Returning false effectively short-circuits display of the control form.
  443. *
  444. * @since 2.8.0
  445. *
  446. * @param array $instance The current widget instance's settings.
  447. * @param WP_Widget $this The current widget instance.
  448. */
  449. $instance = apply_filters( 'widget_form_callback', $instance, $this );
  450. $return = null;
  451. if ( false !== $instance ) {
  452. $return = $this->form($instance);
  453. /**
  454. * Fires at the end of the widget control form.
  455. *
  456. * Use this hook to add extra fields to the widget form. The hook
  457. * is only fired if the value passed to the 'widget_form_callback'
  458. * hook is not false.
  459. *
  460. * Note: If the widget has no form, the text echoed from the default
  461. * form method can be hidden using CSS.
  462. *
  463. * @since 2.8.0
  464. *
  465. * @param WP_Widget $this The widget instance (passed by reference).
  466. * @param null $return Return null if new fields are added.
  467. * @param array $instance An array of the widget's settings.
  468. */
  469. do_action_ref_array( 'in_widget_form', array( &$this, &$return, $instance ) );
  470. }
  471. return $return;
  472. }
  473. /**
  474. * Registers an instance of the widget class.
  475. *
  476. * @since 2.8.0
  477. *
  478. * @param integer $number Optional. The unique order number of this widget instance
  479. * compared to other instances of the same class. Default -1.
  480. */
  481. public function _register_one( $number = -1 ) {
  482. wp_register_sidebar_widget( $this->id, $this->name, $this->_get_display_callback(), $this->widget_options, array( 'number' => $number ) );
  483. _register_widget_update_callback( $this->id_base, $this->_get_update_callback(), $this->control_options, array( 'number' => -1 ) );
  484. _register_widget_form_callback( $this->id, $this->name, $this->_get_form_callback(), $this->control_options, array( 'number' => $number ) );
  485. }
  486. /**
  487. * Saves the settings for all instances of the widget class.
  488. *
  489. * @since 2.8.0
  490. *
  491. * @param array $settings Multi-dimensional array of widget instance settings.
  492. */
  493. public function save_settings( $settings ) {
  494. $settings['_multiwidget'] = 1;
  495. update_option( $this->option_name, $settings );
  496. }
  497. /**
  498. * Retrieves the settings for all instances of the widget class.
  499. *
  500. * @since 2.8.0
  501. *
  502. * @return array Multi-dimensional array of widget instance settings.
  503. */
  504. public function get_settings() {
  505. $settings = get_option( $this->option_name );
  506. if ( false === $settings ) {
  507. if ( isset( $this->alt_option_name ) ) {
  508. $settings = get_option( $this->alt_option_name );
  509. } else {
  510. // Save an option so it can be autoloaded next time.
  511. $this->save_settings( array() );
  512. }
  513. }
  514. if ( ! is_array( $settings ) && ! ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) ) {
  515. $settings = array();
  516. }
  517. if ( ! empty( $settings ) && ! isset( $settings['_multiwidget'] ) ) {
  518. // Old format, convert if single widget.
  519. $settings = wp_convert_widget_settings( $this->id_base, $this->option_name, $settings );
  520. }
  521. unset( $settings['_multiwidget'], $settings['__i__'] );
  522. return $settings;
  523. }
  524. }