/bp-xprofile/classes/class-bp-xprofile-field-type.php

  1. <?php 
  2. /** 
  3. * BuddyPress XProfile Classes. 
  4. * 
  5. * @package BuddyPress 
  6. * @subpackage XProfileClasses 
  7. * @since 2.0.0 
  8. */ 
  9.  
  10. // Exit if accessed directly. 
  11. defined( 'ABSPATH' ) || exit; 
  12.  
  13. /** 
  14. * Represents a type of XProfile field and holds meta information about the type of value that it accepts. 
  15. * 
  16. * @since 2.0.0 
  17. */ 
  18. abstract class BP_XProfile_Field_Type { 
  19.  
  20. /** 
  21. * Validation regex rules for field type. 
  22. * 
  23. * @since 2.0.0 
  24. * @var array Field type validation regexes. 
  25. */ 
  26. protected $validation_regex = array(); 
  27.  
  28. /** 
  29. * Whitelisted values for field type. 
  30. * 
  31. * @since 2.0.0 
  32. * @var array Field type whitelisted values. 
  33. */ 
  34. protected $validation_whitelist = array(); 
  35.  
  36. /** 
  37. * Name for field type. 
  38. * 
  39. * @since 2.0.0 
  40. * @var string The name of this field type. 
  41. */ 
  42. public $name = ''; 
  43.  
  44. /** 
  45. * The name of the category that this field type should be grouped with. Used on the [Users > Profile Fields] screen in wp-admin. 
  46. * 
  47. * @since 2.0.0 
  48. * @var string 
  49. */ 
  50. public $category = ''; 
  51.  
  52. /** 
  53. * If allowed to store null/empty values. 
  54. * 
  55. * @since 2.0.0 
  56. * @var bool If this is set, allow BP to store null/empty values for this field type. 
  57. */ 
  58. public $accepts_null_value = false; 
  59.  
  60. /** 
  61. * If this is set, BP will set this field type's validation whitelist from the field's options (e.g checkbox, selectbox). 
  62. * 
  63. * @since 2.0.0 
  64. * @var bool Does this field support options? e.g. selectbox, radio buttons, etc. 
  65. */ 
  66. public $supports_options = false; 
  67.  
  68. /** 
  69. * If allowed to support multiple options as default. 
  70. * 
  71. * @since 2.0.0 
  72. * @var bool Does this field type support multiple options being set as default values? e.g. multiselectbox, checkbox. 
  73. */ 
  74. public $supports_multiple_defaults = false; 
  75.  
  76. /** 
  77. * If the field type supports rich text by default. 
  78. * 
  79. * @since 2.4.0 
  80. * @var bool 
  81. */ 
  82. public $supports_richtext = false; 
  83.  
  84. /** 
  85. * If the field type has a type-specific settings section on the Edit Field panel. 
  86. * 
  87. * @since 2.7.0 
  88. * @var bool|null Boolean if set explicitly by the type object, otherwise null. 
  89. */ 
  90. protected $do_settings_section = null; 
  91.  
  92. /** 
  93. * If object is created by an BP_XProfile_Field object. 
  94. * 
  95. * @since 2.0.0 
  96. * @var BP_XProfile_Field If this object is created by instantiating a {@link BP_XProfile_Field},  
  97. * this is a reference back to that object. 
  98. */ 
  99. public $field_obj = null; 
  100.  
  101. /** 
  102. * Constructor. 
  103. * 
  104. * @since 2.0.0 
  105. */ 
  106. public function __construct() { 
  107.  
  108. /** 
  109. * Fires inside __construct() method for BP_XProfile_Field_Type class. 
  110. * 
  111. * @since 2.0.0 
  112. * 
  113. * @param BP_XProfile_Field_Type $this Current instance of 
  114. * the field type class. 
  115. */ 
  116. do_action( 'bp_xprofile_field_type', $this ); 
  117.  
  118. /** 
  119. * Set a regex that profile data will be asserted against. 
  120. * 
  121. * You can call this method multiple times to set multiple formats. When validation is performed,  
  122. * it's successful as long as the new value matches any one of the registered formats. 
  123. * 
  124. * @since 2.0.0 
  125. * 
  126. * @param string $format Regex string. 
  127. * @param string $replace_format Optional; if 'replace', replaces the format instead of adding to it. 
  128. * Defaults to 'add'. 
  129. * @return BP_XProfile_Field_Type 
  130. */ 
  131. public function set_format( $format, $replace_format = 'add' ) { 
  132.  
  133. /** 
  134. * Filters the regex format for the field type. 
  135. * 
  136. * @since 2.0.0 
  137. * 
  138. * @param string $format Regex string. 
  139. * @param string $replace_format Optional replace format If "replace", replaces the 
  140. * format instead of adding to it. Defaults to "add". 
  141. * @param BP_XProfile_Field_Type $this Current instance of the BP_XProfile_Field_Type class. 
  142. */ 
  143. $format = apply_filters( 'bp_xprofile_field_type_set_format', $format, $replace_format, $this ); 
  144.  
  145. if ( 'add' === $replace_format ) { 
  146. $this->validation_regex[] = $format; 
  147. } elseif ( 'replace' === $replace_format ) { 
  148. $this->validation_regex = array( $format ); 
  149.  
  150. return $this; 
  151.  
  152. /** 
  153. * Add a value to this type's whitelist that profile data will be asserted against. 
  154. * 
  155. * You can call this method multiple times to set multiple formats. When validation is performed,  
  156. * it's successful as long as the new value matches any one of the registered formats. 
  157. * 
  158. * @since 2.0.0 
  159. * 
  160. * @param string|array $values Whitelisted values. 
  161. * @return BP_XProfile_Field_Type 
  162. */ 
  163. public function set_whitelist_values( $values ) { 
  164. foreach ( (array) $values as $value ) { 
  165.  
  166. /** 
  167. * Filters values for field type's whitelist that profile data will be asserted against. 
  168. * 
  169. * @since 2.0.0 
  170. * 
  171. * @param string $value Field value. 
  172. * @param array $values Original array of values. 
  173. * @param BP_XProfile_Field_Type $this Current instance of the BP_XProfile_Field_Type class. 
  174. */ 
  175. $this->validation_whitelist[] = apply_filters( 'bp_xprofile_field_type_set_whitelist_values', $value, $values, $this ); 
  176.  
  177. return $this; 
  178.  
  179. /** 
  180. * Check the given string against the registered formats for this field type. 
  181. * 
  182. * This method doesn't support chaining. 
  183. * 
  184. * @since 2.0.0 
  185. * 
  186. * @param string|array $values Value to check against the registered formats. 
  187. * @return bool True if the value validates 
  188. */ 
  189. public function is_valid( $values ) { 
  190. $validated = false; 
  191.  
  192. // Some types of field (e.g. multi-selectbox) may have multiple values to check. 
  193. foreach ( (array) $values as $value ) { 
  194.  
  195. // Validate the $value against the type's accepted format(s). 
  196. foreach ( $this->validation_regex as $format ) { 
  197. if ( 1 === preg_match( $format, $value ) ) { 
  198. $validated = true; 
  199. continue; 
  200.  
  201. } else { 
  202. $validated = false; 
  203.  
  204. // Handle field types with accepts_null_value set if $values is an empty array. 
  205. if ( ( false === $validated ) && is_array( $values ) && empty( $values ) && $this->accepts_null_value ) { 
  206. $validated = true; 
  207.  
  208. // If there's a whitelist set, make sure that each value is a whitelisted value. 
  209. if ( ( true === $validated ) && ! empty( $values ) && ! empty( $this->validation_whitelist ) ) { 
  210. foreach ( (array) $values as $value ) { 
  211. if ( ! in_array( $value, $this->validation_whitelist, true ) ) { 
  212. $validated = false; 
  213. break; 
  214.  
  215. /** 
  216. * Filters whether or not field type is a valid format. 
  217. * 
  218. * @since 2.0.0 
  219. * 
  220. * @param bool $validated Whether or not the field type is valid. 
  221. * @param string|array $values Value to check against the registered formats. 
  222. * @param BP_XProfile_Field_Type $this Current instance of the BP_XProfile_Field_Type class. 
  223. */ 
  224. return (bool) apply_filters( 'bp_xprofile_field_type_is_valid', $validated, $values, $this ); 
  225.  
  226. /** 
  227. * Check whether the current field type should have a settings ("options") section on the Edit Field panel. 
  228. * 
  229. * Falls back on `supports_options` if no value is set by the field type. 
  230. * 
  231. * @since 2.7.0 
  232. * 
  233. * @return bool 
  234. */ 
  235. public function do_settings_section() { 
  236. if ( null === $this->do_settings_section ) { 
  237. $this->do_settings_section = $this->supports_options; 
  238.  
  239. return (bool) $this->do_settings_section; 
  240.  
  241. /** 
  242. * Output the edit field HTML for this field type. 
  243. * 
  244. * Must be used inside the {@link bp_profile_fields()} template loop. 
  245. * 
  246. * @since 2.0.0 
  247. * 
  248. * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 
  249. * @return void 
  250. */ 
  251. abstract public function edit_field_html( array $raw_properties = array() ); 
  252.  
  253. /** 
  254. * Output HTML for this field type on the wp-admin Profile Fields screen. 
  255. * 
  256. * Must be used inside the {@link bp_profile_fields()} template loop. 
  257. * 
  258. * @since 2.0.0 
  259. * 
  260. * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 
  261. * @return void 
  262. */ 
  263. abstract public function admin_field_html( array $raw_properties = array() ); 
  264.  
  265. /** 
  266. * Output the edit field options HTML for this field type. 
  267. * 
  268. * BuddyPress considers a field's "options" to be, for example, the items in a selectbox. 
  269. * These are stored separately in the database, and their templating is handled separately. 
  270. * Populate this method in a child class if it's required. Otherwise, you can leave it out. 
  271. * 
  272. * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because 
  273. * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility. 
  274. * 
  275. * Must be used inside the {@link bp_profile_fields()} template loop. 
  276. * 
  277. * @since 2.0.0 
  278. * 
  279. * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}. 
  280. */ 
  281. public function edit_field_options_html( array $args = array() ) {} 
  282.  
  283. /** 
  284. * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens. 
  285. * 
  286. * You don't need to implement this method for all field types. It's used in core by the 
  287. * selectbox, multi selectbox, checkbox, and radio button fields, to allow the admin to 
  288. * enter the child option values (e.g. the choices in a select box). 
  289. * 
  290. * Must be used inside the {@link bp_profile_fields()} template loop. 
  291. * 
  292. * @since 2.0.0 
  293. * 
  294. * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 
  295. * @param string $control_type Optional. HTML input type used to render the current 
  296. * field's child options. 
  297. */ 
  298. public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) { 
  299. $type = array_search( get_class( $this ), bp_xprofile_get_field_types() ); 
  300. if ( false === $type ) { 
  301. return; 
  302.  
  303. $class = $current_field->type != $type ? 'display: none;' : ''; 
  304. $current_type_obj = bp_xprofile_create_field_type( $type ); 
  305. ?> 
  306.  
  307. <div id="<?php echo esc_attr( $type ); ?>" class="postbox bp-options-box" style="<?php echo esc_attr( $class ); ?> margin-top: 15px;"> 
  308. <h3><?php esc_html_e( 'Please enter options for this Field:', 'buddypress' ); ?></h3> 
  309. <div class="inside" aria-live="polite" aria-atomic="true" aria-relevant="all"> 
  310. <p> 
  311. <label for="sort_order_<?php echo esc_attr( $type ); ?>"><?php esc_html_e( 'Sort Order:', 'buddypress' ); ?></label> 
  312. <select name="sort_order_<?php echo esc_attr( $type ); ?>" id="sort_order_<?php echo esc_attr( $type ); ?>" > 
  313. <option value="custom" <?php selected( 'custom', $current_field->order_by ); ?>><?php esc_html_e( 'Custom', 'buddypress' ); ?></option> 
  314. <option value="asc" <?php selected( 'asc', $current_field->order_by ); ?>><?php esc_html_e( 'Ascending', 'buddypress' ); ?></option> 
  315. <option value="desc" <?php selected( 'desc', $current_field->order_by ); ?>><?php esc_html_e( 'Descending', 'buddypress' ); ?></option> 
  316. </select> 
  317. </p> 
  318.  
  319. <?php 
  320.  
  321. // Does option have children? 
  322. $options = $current_field->get_children( true ); 
  323.  
  324. // If no children options exists for this field, check in $_POST 
  325. // for a submitted form (e.g. on the "new field" screen). 
  326. if ( empty( $options ) ) { 
  327.  
  328. $options = array(); 
  329. $i = 1; 
  330.  
  331. while ( isset( $_POST[$type . '_option'][$i] ) ) { 
  332.  
  333. // Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE. 
  334. if ( $current_type_obj->supports_options && ! $current_type_obj->supports_multiple_defaults && isset( $_POST["isDefault_{$type}_option"][$i] ) && (int) $_POST["isDefault_{$type}_option"] === $i ) { 
  335. $is_default_option = true; 
  336. } elseif ( isset( $_POST["isDefault_{$type}_option"][$i] ) ) { 
  337. $is_default_option = (bool) $_POST["isDefault_{$type}_option"][$i]; 
  338. } else { 
  339. $is_default_option = false; 
  340.  
  341. // Grab the values from $_POST to use as the form's options. 
  342. $options[] = (object) array( 
  343. 'id' => -1,  
  344. 'is_default_option' => $is_default_option,  
  345. 'name' => sanitize_text_field( stripslashes( $_POST[$type . '_option'][$i] ) ),  
  346. ); 
  347.  
  348. ++$i; 
  349.  
  350. // If there are still no children options set, this must be the "new field" screen, so add one new/empty option. 
  351. if ( empty( $options ) ) { 
  352. $options[] = (object) array( 
  353. 'id' => -1,  
  354. 'is_default_option' => false,  
  355. 'name' => '',  
  356. ); 
  357.  
  358. // Render the markup for the children options. 
  359. if ( ! empty( $options ) ) { 
  360. $default_name = ''; 
  361.  
  362. for ( $i = 0, $count = count( $options ); $i < $count; ++$i ) : 
  363. $j = $i + 1; 
  364.  
  365. // Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE. 
  366. if ( $current_type_obj->supports_options && $current_type_obj->supports_multiple_defaults ) { 
  367. $default_name = '[' . $j . ']'; 
  368. ?> 
  369.  
  370. <div id="<?php echo esc_attr( "{$type}_div{$j}" ); ?>" class="bp-option sortable"> 
  371. <span class="bp-option-icon grabber"></span> 
  372. <label for="<?php echo esc_attr( "{$type}_option{$j}" ); ?>" class="screen-reader-text"><?php 
  373. /** translators: accessibility text */ 
  374. esc_html_e( 'Add an option', 'buddypress' ); 
  375. ?></label> 
  376. <input type="text" name="<?php echo esc_attr( "{$type}_option[{$j}]" ); ?>" id="<?php echo esc_attr( "{$type}_option{$j}" ); ?>" value="<?php echo esc_attr( stripslashes( $options[$i]->name ) ); ?>" /> 
  377. <label for="<?php echo esc_attr( "{$type}_option{$default_name}" ); ?>"> 
  378. <input type="<?php echo esc_attr( $control_type ); ?>" id="<?php echo esc_attr( "{$type}_option{$default_name}" ); ?>" name="<?php echo esc_attr( "isDefault_{$type}_option{$default_name}" ); ?>" <?php checked( $options[$i]->is_default_option, true ); ?> value="<?php echo esc_attr( $j ); ?>" /> 
  379. <?php _e( 'Default Value', 'buddypress' ); ?> 
  380. </label> 
  381.  
  382. <?php if ( 1 !== $j ) : ?> 
  383. <div class ="delete-button"> 
  384. <a href='javascript:hide("<?php echo esc_attr( "{$type}_div{$j}" ); ?>")' class="delete"><?php esc_html_e( 'Delete', 'buddypress' ); ?></a> 
  385. </div> 
  386. <?php endif; ?> 
  387.  
  388. </div> 
  389.  
  390. <?php endfor; ?> 
  391.  
  392. <input type="hidden" name="<?php echo esc_attr( "{$type}_option_number" ); ?>" id="<?php echo esc_attr( "{$type}_option_number" ); ?>" value="<?php echo esc_attr( $j + 1 ); ?>" /> 
  393. <?php } ?> 
  394.  
  395. <div id="<?php echo esc_attr( "{$type}_more" ); ?>"></div> 
  396. <p><a href="javascript:add_option('<?php echo esc_js( $type ); ?>')"><?php esc_html_e( 'Add Another Option', 'buddypress' ); ?></a></p> 
  397.  
  398. <?php 
  399.  
  400. /** 
  401. * Fires at the end of the new field additional settings area. 
  402. * 
  403. * @since 2.3.0 
  404. * 
  405. * @param BP_XProfile_Field $current_field Current field being rendered. 
  406. */ 
  407. do_action( 'bp_xprofile_admin_new_field_additional_settings', $current_field ) ?> 
  408. </div> 
  409. </div> 
  410.  
  411. <?php 
  412.  
  413. /** 
  414. * Allow field types to modify submitted values before they are validated. 
  415. * 
  416. * In some cases, it may be appropriate for a field type to catch 
  417. * submitted values and modify them before they are passed to the 
  418. * is_valid() method. For example, URL validation requires the 
  419. * 'http://' scheme (so that the value saved in the database is always 
  420. * a fully-formed URL), but in order to allow users to enter a URL 
  421. * without this scheme, BP_XProfile_Field_Type_URL prepends 'http://' 
  422. * when it's not present. 
  423. * 
  424. * By default, this is a pass-through method that does nothing. Only 
  425. * override in your own field type if you need this kind of pre- 
  426. * validation filtering. 
  427. * 
  428. * @since 2.1.0 
  429. * @since 2.4.0 Added the `$field_id` parameter. 
  430. * 
  431. * @param mixed $field_value Submitted field value. 
  432. * @param string|int $field_id Optional. ID of the field. 
  433. * @return mixed 
  434. */ 
  435. public static function pre_validate_filter( $field_value, $field_id = '' ) { 
  436. return $field_value; 
  437.  
  438. /** 
  439. * Allow field types to modify the appearance of their values. 
  440. * 
  441. * By default, this is a pass-through method that does nothing. Only 
  442. * override in your own field type if you need to provide custom 
  443. * filtering for output values. 
  444. * 
  445. * @since 2.1.0 
  446. * @since 2.4.0 Added `$field_id` parameter. 
  447. * 
  448. * @param mixed $field_value Field value. 
  449. * @param string|int $field_id ID of the field. 
  450. * @return mixed 
  451. */ 
  452. public static function display_filter( $field_value, $field_id = '' ) { 
  453. return $field_value; 
  454.  
  455. /** 
  456. * Save miscellaneous settings related to this field type. 
  457. * 
  458. * Override in a specific field type if it requires an admin save routine. 
  459. * 
  460. * @since 2.7.0 
  461. * 
  462. * @param int $field_id Field ID. 
  463. * @param array $settings Array of settings. 
  464. */ 
  465. public function admin_save_settings( $field_id, $settings ) {} 
  466.  
  467. /** Protected *************************************************************/ 
  468.  
  469. /** 
  470. * Get a sanitised and escaped string of the edit field's HTML elements and attributes. 
  471. * 
  472. * Must be used inside the {@link bp_profile_fields()} template loop. 
  473. * This method was intended to be static but couldn't be because php.net/lsb/ requires PHP >= 5.3. 
  474. * 
  475. * @since 2.0.0 
  476. * 
  477. * @param array $properties Optional key/value array of attributes for this edit field. 
  478. * @return string 
  479. */ 
  480. protected function get_edit_field_html_elements( array $properties = array() ) { 
  481.  
  482. $r = bp_parse_args( $properties, array( 
  483. 'id' => bp_get_the_profile_field_input_name(),  
  484. 'name' => bp_get_the_profile_field_input_name(),  
  485. ) ); 
  486.  
  487. if ( bp_get_the_profile_field_is_required() ) { 
  488. $r['aria-required'] = 'true'; 
  489.  
  490. /** 
  491. * Filters the edit html elements and attributes. 
  492. * 
  493. * @since 2.0.0 
  494. * 
  495. * @param array $r Array of parsed arguments. 
  496. * @param string $value Class name for the current class instance. 
  497. */ 
  498. $r = (array) apply_filters( 'bp_xprofile_field_edit_html_elements', $r, get_class( $this ) ); 
  499.  
  500. return bp_get_form_field_attributes( sanitize_key( bp_get_the_profile_field_name() ), $r ); 
.