Nav apraksta

class-wp-widget.php 18KB

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