Source: fields/class-field.php

  1. <?php
  2. /**
  3. * Abstract field.
  4. *
  5. * @package HivePress\Fields
  6. */
  7. namespace HivePress\Fields;
  8. use HivePress\Helpers as hp;
  9. use HivePress\Traits;
  10. // Exit if accessed directly.
  11. defined( 'ABSPATH' ) || exit;
  12. /**
  13. * Abstract field class.
  14. */
  15. abstract class Field {
  16. use Traits\Mutator;
  17. use Traits\Context;
  18. use Traits\Meta {
  19. set_meta as _set_meta;
  20. }
  21. /**
  22. * Field arguments.
  23. *
  24. * @var array
  25. */
  26. protected $args = [];
  27. /**
  28. * Display type.
  29. *
  30. * @var string
  31. */
  32. protected $display_type;
  33. /**
  34. * Display template.
  35. *
  36. * @var string
  37. */
  38. protected $display_template;
  39. /**
  40. * Field name.
  41. *
  42. * @var string
  43. */
  44. protected $name;
  45. /**
  46. * Field label.
  47. *
  48. * @var string
  49. */
  50. protected $label;
  51. /**
  52. * Field description.
  53. *
  54. * @var string
  55. */
  56. protected $description;
  57. /**
  58. * Field statuses.
  59. *
  60. * @var array
  61. */
  62. protected $statuses = [];
  63. /**
  64. * Field value.
  65. *
  66. * @var mixed
  67. */
  68. protected $value;
  69. /**
  70. * Parent field value.
  71. *
  72. * @var mixed
  73. */
  74. protected $parent_value;
  75. /**
  76. * SQL filter.
  77. *
  78. * @var mixed
  79. */
  80. protected $filter;
  81. /**
  82. * Disable this field?
  83. *
  84. * @var bool
  85. */
  86. protected $disabled = false;
  87. /**
  88. * Is value required?
  89. *
  90. * @var bool
  91. */
  92. protected $required = false;
  93. /**
  94. * Field errors.
  95. *
  96. * @var array
  97. */
  98. protected $errors = [];
  99. /**
  100. * HTML attributes.
  101. *
  102. * @var array
  103. */
  104. protected $attributes = [];
  105. /**
  106. * Class initializer.
  107. *
  108. * @param array $meta Class meta values.
  109. */
  110. public static function init( $meta = [] ) {
  111. $meta = hp\merge_arrays(
  112. [
  113. 'name' => hp\get_class_name( static::class ),
  114. 'type' => 'CHAR',
  115. 'editable' => true,
  116. 'filterable' => false,
  117. 'sortable' => false,
  118. 'settings' => [
  119. 'required' => [
  120. 'label' => esc_html_x( 'Required', 'field', 'hivepress' ),
  121. 'caption' => esc_html__( 'Make this field required', 'hivepress' ),
  122. 'type' => 'checkbox',
  123. '_context' => 'edit',
  124. '_order' => 10,
  125. ],
  126. 'description' => [
  127. 'label' => hivepress()->translator->get_string( 'description' ),
  128. 'type' => 'textarea',
  129. 'max_length' => 2048,
  130. 'html' => true,
  131. '_context' => 'edit',
  132. '_order' => 20,
  133. ],
  134. ],
  135. ],
  136. $meta
  137. );
  138. // Filter meta.
  139. foreach ( hp\get_class_parents( static::class ) as $class ) {
  140. /**
  141. * Filters the field class meta. The class meta stores properties related to the field type rather than a specific field instance. For example, it stores the field settings displayed for an attribute. The dynamic part of the hook refers to the field type (e.g. `textarea`). You can check the available field types in the `includes/fields` directory of HivePress.
  142. *
  143. * @hook hivepress/v1/fields/{field_type}/meta
  144. * @param {array} $meta Class meta values.
  145. * @return {array} Class meta values.
  146. */
  147. $meta = apply_filters( 'hivepress/v1/fields/' . hp\get_class_name( $class ) . '/meta', $meta );
  148. }
  149. // Set meta.
  150. static::set_meta( $meta );
  151. }
  152. /**
  153. * Class constructor.
  154. *
  155. * @param array $args Field arguments.
  156. */
  157. public function __construct( $args = [] ) {
  158. $args = hp\merge_arrays(
  159. [
  160. 'display_type' => hp\get_class_name( static::class ),
  161. 'display_template' => '%value%',
  162. ],
  163. $args
  164. );
  165. // Filter properties.
  166. foreach ( hp\get_class_parents( static::class ) as $class ) {
  167. /**
  168. * Filters the field properties. The dynamic part of the hook refers to the field type (e.g. `textarea`). You can check the available field types in the `includes/fields` directory of HivePress.
  169. *
  170. * @hook hivepress/v1/fields/{field_type}
  171. * @param {array} $props Field properties.
  172. * @param {object} $field Field object.
  173. * @return {array} Field properties.
  174. */
  175. $args = apply_filters( 'hivepress/v1/fields/' . hp\get_class_name( $class ), $args, $this );
  176. }
  177. // Set arguments.
  178. $this->args = $args;
  179. // Set properties.
  180. foreach ( $args as $name => $value ) {
  181. $this->set_property( $name, $value );
  182. }
  183. // Bootstrap properties.
  184. $this->boot();
  185. }
  186. /**
  187. * Bootstraps field properties.
  188. */
  189. protected function boot() {
  190. // Set default value.
  191. if ( isset( $this->args['default'] ) ) {
  192. $this->set_value( $this->args['default'] );
  193. }
  194. // Set optional status.
  195. if ( ! $this->required ) {
  196. $this->statuses = array_merge( [ 'optional' => esc_html_x( 'optional', 'field', 'hivepress' ) ], $this->statuses );
  197. }
  198. $this->statuses = array_filter( $this->statuses );
  199. // Set attributes.
  200. if ( 'hidden' === $this->display_type ) {
  201. $this->attributes = array_filter(
  202. $this->attributes,
  203. function( $name ) {
  204. return strpos( $name, 'data-' ) === 0;
  205. },
  206. ARRAY_FILTER_USE_KEY
  207. );
  208. }
  209. $this->attributes = hp\merge_arrays(
  210. $this->attributes,
  211. [
  212. 'class' => [ 'hp-field', 'hp-field--' . hp\sanitize_slug( $this->display_type ) ],
  213. ]
  214. );
  215. }
  216. /**
  217. * Sets class meta values.
  218. *
  219. * @param array $meta Meta values.
  220. */
  221. final protected static function set_meta( $meta ) {
  222. // Get settings.
  223. $settings = array_filter( hp\get_array_value( $meta, 'settings', [] ) );
  224. if ( $settings ) {
  225. $meta['settings'] = [];
  226. foreach ( $settings as $name => $args ) {
  227. // Create field.
  228. $field = hp\create_class_instance( '\HivePress\Fields\\' . $args['type'], [ array_merge( $args, [ 'name' => $name ] ) ] );
  229. // Add field.
  230. if ( $field ) {
  231. $meta['settings'][ $name ] = $field;
  232. }
  233. }
  234. }
  235. static::_set_meta( $meta );
  236. }
  237. /**
  238. * Gets field arguments.
  239. *
  240. * @return array
  241. */
  242. final public function get_args() {
  243. return $this->args;
  244. }
  245. /**
  246. * Gets field argument.
  247. *
  248. * @param string $name Argument name.
  249. * @return mixed
  250. */
  251. final public function get_arg( $name ) {
  252. return hp\get_array_value( $this->args, $name );
  253. }
  254. /**
  255. * Gets display type.
  256. *
  257. * @return string
  258. */
  259. final public function get_display_type() {
  260. return $this->display_type;
  261. }
  262. /**
  263. * Sets field display template.
  264. *
  265. * @param string $display_template Display template.
  266. */
  267. protected function set_display_template( $display_template ) {
  268. $this->display_template = $display_template;
  269. }
  270. /**
  271. * Gets field name.
  272. *
  273. * @return string
  274. */
  275. final public function get_name() {
  276. return $this->name;
  277. }
  278. /**
  279. * Gets field slug.
  280. *
  281. * @return string
  282. */
  283. final public function get_slug() {
  284. return hp\sanitize_slug( $this->name );
  285. }
  286. /**
  287. * Gets field label.
  288. *
  289. * @param mixed $default Default label.
  290. * @return string
  291. */
  292. final public function get_label( $default = null ) {
  293. $label = $this->label;
  294. if ( ! $label && $default ) {
  295. $label = true === $default ? $this->name : $default;
  296. }
  297. return $label;
  298. }
  299. /**
  300. * Gets field description.
  301. *
  302. * @return string
  303. */
  304. final public function get_description() {
  305. return $this->description;
  306. }
  307. /**
  308. * Gets field statuses.
  309. *
  310. * @return array
  311. */
  312. final public function get_statuses() {
  313. return $this->statuses;
  314. }
  315. /**
  316. * Sets field value.
  317. *
  318. * @param mixed $value Field value.
  319. * @return object
  320. */
  321. public function set_value( $value ) {
  322. $this->value = $value;
  323. $this->filter = null;
  324. if ( ! is_null( $this->value ) ) {
  325. $this->normalize();
  326. if ( ! is_null( $this->value ) ) {
  327. $this->sanitize();
  328. $this->update_filter();
  329. }
  330. }
  331. return $this;
  332. }
  333. /**
  334. * Gets field value.
  335. *
  336. * @return mixed
  337. */
  338. final public function get_value() {
  339. return $this->value;
  340. }
  341. /**
  342. * Gets field value for display.
  343. *
  344. * @return mixed
  345. */
  346. public function get_display_value() {
  347. return $this->value;
  348. }
  349. /**
  350. * Sets parent field value.
  351. *
  352. * @param mixed $value Field value.
  353. * @return object
  354. */
  355. public function set_parent_value( $value ) {
  356. $this->parent_value = $value;
  357. return $this;
  358. }
  359. /**
  360. * Adds SQL filter.
  361. */
  362. protected function add_filter() {
  363. $this->filter = [
  364. 'name' => $this->name,
  365. 'type' => static::get_meta( 'type' ),
  366. 'value' => $this->value,
  367. 'operator' => '=',
  368. ];
  369. }
  370. /**
  371. * Gets SQL filter.
  372. *
  373. * @return mixed
  374. */
  375. final public function get_filter() {
  376. return $this->filter;
  377. }
  378. /**
  379. * Updates SQL filter.
  380. *
  381. * @param bool $force Force update?
  382. */
  383. final public function update_filter( $force = false ) {
  384. if ( $force || ( ! is_null( $this->value ) && static::get_meta( 'filterable' ) ) ) {
  385. $this->add_filter();
  386. }
  387. }
  388. /**
  389. * Checks if field is disabled.
  390. *
  391. * @return bool
  392. */
  393. final public function is_disabled() {
  394. return $this->disabled;
  395. }
  396. /**
  397. * Checks if field is required.
  398. *
  399. * @return bool
  400. */
  401. final public function is_required() {
  402. return $this->required;
  403. }
  404. /**
  405. * Adds field errors.
  406. *
  407. * @param mixed $errors Field errors.
  408. */
  409. final protected function add_errors( $errors ) {
  410. $this->errors = array_merge( $this->errors, (array) $errors );
  411. }
  412. /**
  413. * Gets field errors.
  414. *
  415. * @return array
  416. */
  417. final public function get_errors() {
  418. return $this->errors;
  419. }
  420. /**
  421. * Normalizes field value.
  422. */
  423. protected function normalize() {
  424. if ( '' === $this->value ) {
  425. $this->value = null;
  426. }
  427. }
  428. /**
  429. * Sanitizes field value.
  430. */
  431. abstract protected function sanitize();
  432. /**
  433. * Validates field value.
  434. *
  435. * @return bool
  436. */
  437. public function validate() {
  438. $this->errors = [];
  439. if ( $this->required && is_null( $this->value ) ) {
  440. /* translators: %s: field label. */
  441. $this->errors['required'] = sprintf( esc_html__( '"%s" field is required.', 'hivepress' ), $this->get_label( true ) );
  442. }
  443. return empty( $this->errors );
  444. }
  445. /**
  446. * Renders field HTML.
  447. *
  448. * @return string
  449. */
  450. abstract public function render();
  451. /**
  452. * Displays field HTML.
  453. *
  454. * @return string
  455. */
  456. public function display() {
  457. // Check shortcodes.
  458. $shortcode = hp\has_shortcode( $this->display_template );
  459. // Get value.
  460. $value = $this->get_display_value();
  461. foreach ( hp\get_class_parents( static::class ) as $class ) {
  462. /**
  463. * Filters the field display value. The dynamic part of the hook refers to the field type (e.g. `textarea`). You can check the available field types in the `includes/fields` directory of HivePress.
  464. *
  465. * @hook hivepress/v1/fields/{field_type}/display_value
  466. * @param {string} $value Display value.
  467. * @return {string} Display value.
  468. */
  469. $value = apply_filters( 'hivepress/v1/fields/' . hp\get_class_name( $class ) . '/display_value', $value, $this );
  470. }
  471. if ( $shortcode ) {
  472. $value = strip_shortcodes( $value );
  473. }
  474. // Render output.
  475. $output = hp\replace_tokens(
  476. array_merge(
  477. $this->context,
  478. [
  479. 'label' => '<strong>' . $this->label . '</strong>',
  480. 'value' => $value,
  481. ]
  482. ),
  483. $this->display_template,
  484. true
  485. );
  486. if ( $shortcode ) {
  487. $output = do_shortcode( $output );
  488. }
  489. return $output;
  490. }
  491. }