1 <?php
2
3 /**
4 * Base class for contexts that store data.
5 *
6 * @package Fieldmanager_Context
7 */
8 abstract class Fieldmanager_Context_Storable extends Fieldmanager_Context {
9
10 /**
11 * @var array
12 */
13 public $taxonomies_to_save = array();
14
15 /**
16 * Render the field.
17 *
18 * @param array $args {
19 * Optional. Arguments to adjust the rendering behavior.
20 *
21 * @type mixed $data The existing data to display with the field. If
22 * absent, data will be loaded using
23 * Fieldmanager_Context::_load().
24 * @type boolean $echo Output if true, return if false. Default is true.
25 * }
26 * @return string if $args['echo'] == false.
27 */
28 protected function render_field( $args = array() ) {
29 if ( ! array_key_exists( 'data', $args ) ) {
30 $args['data'] = $this->load();
31 }
32 return parent::render_field( $args );
33 }
34
35
36 /**
37 * Handle saving data for any context.
38 *
39 * @param mixed $data Data to save. Should be raw, e.g. POST data.
40 */
41 protected function save( $data = null ) {
42 // Reset the save keys in the event this context instance is saved twice
43 $this->save_keys = array();
44
45 $this->fm->current_context = $this;
46 if ( $this->fm->serialize_data ) {
47 $this->save_field( $this->fm, $data, $this->fm->data_id );
48 } else {
49 if ( null === $data ) {
50 $data = isset( $_POST[ $this->fm->name ] ) ? $_POST[ $this->fm->name ] : '';
51 }
52 $this->save_walk_children( $this->fm, $data, $this->fm->data_id );
53 }
54 if ( ! empty( $this->taxonomies_to_save ) ) {
55 foreach( $this->taxonomies_to_save as $taxonomy => $data ) {
56 wp_set_object_terms( $this->fm->data_id, $data['term_ids'], $taxonomy, $data['append'] );
57 }
58 $this->taxonomies_to_save = array();
59 }
60 }
61
62 /**
63 * Save a single field.
64 *
65 * @param object $field Fieldmanager field.
66 * @param mixed $data Data to save.
67 */
68 protected function save_field( $field, $data ) {
69 $field->data_id = $this->fm->data_id;
70 $field->data_type = $this->fm->data_type;
71
72 if ( isset( $this->save_keys[ $field->get_element_key() ] ) ) {
73 throw new FM_Developer_Exception( sprintf( esc_html__( 'You have two fields in this group saving to the same key: %s', 'fieldmanager' ), $field->get_element_key() ) );
74 } else {
75 $this->save_keys[ $field->get_element_key() ] = true;
76 }
77
78 $current = $this->get_data( $this->fm->data_id, $field->get_element_key(), $field->serialize_data );
79 $data = $this->prepare_data( $current, $data, $field );
80 if ( ! $field->skip_save ) {
81 if ( $field->serialize_data ) {
82 $this->update_data( $this->fm->data_id, $field->get_element_key(), $data );
83 } else {
84 $this->delete_data( $this->fm->data_id, $field->get_element_key() );
85 foreach ( $data as $value ) {
86 $this->add_data( $this->fm->data_id, $field->get_element_key(), $value );
87 }
88 }
89 }
90 }
91
92 /**
93 * Walk group children to save when serialize_data => false.
94 *
95 * @param object $field Fieldmanager field.
96 * @param mixed $data Data to save.
97 */
98 protected function save_walk_children( $field, $data ) {
99 if ( $field->serialize_data || ! $field->is_group() ) {
100 $this->save_field( $field, $data );
101 } else {
102 foreach ( $field->children as $child ) {
103 if ( isset( $data[ $child->name ] ) ) {
104 $this->save_walk_children( $child, $data[ $child->name ] );
105 }
106 }
107 }
108 }
109
110
111 /**
112 * Handle loading data for any context.
113 *
114 * @return mixed The loaded data.
115 */
116 protected function load() {
117 if ( $this->fm->serialize_data ) {
118 return $this->load_field( $this->fm, $this->fm->data_id );
119 } else {
120 return $this->load_walk_children( $this->fm, $this->fm->data_id );
121 }
122 }
123
124 /**
125 * Load a single field.
126 *
127 * @param object $field The Fieldmanager field for which to load data.
128 * @return mixed Data stored for that field in this context.
129 */
130 protected function load_field( $field ) {
131 $data = $this->get_data( $this->fm->data_id, $field->get_element_key() );
132 if ( $field->serialize_data ) {
133 return empty( $data ) ? null : reset( $data );
134 } else {
135 return $data;
136 }
137 }
138
139 /**
140 * Walk group children to load when serialize_data => false.
141 *
142 * @param object $field Fieldmanager field for which to load data.
143 * @return mixed Data stored for a singular field with serialized data, or
144 * array of data for a groups's children.
145 */
146 protected function load_walk_children( $field ) {
147 if ( $field->serialize_data || ! $field->is_group() ) {
148 return $this->load_field( $field );
149 } else {
150 $return = array();
151 foreach ( $field->children as $child ) {
152 $return[ $child->name ] = $this->load_walk_children( $child );
153 }
154 return $return;
155 }
156 }
157
158 /**
159 * Method to get data from the context's storage engine.
160 *
161 * @param int $data_id The ID of the object holding the data, e.g. Post ID.
162 * @param string $data_key The key for the data, e.g. a meta_key.
163 * @param boolean $single Optional. If true, only returns the first value
164 * found for the given data_key. This won't apply to
165 * every context. Default is false.
166 * @return string|array The stored data. If no data is found, should return
167 * an empty string (""). @see get_post_meta().
168 */
169 abstract protected function get_data( $data_id, $data_key, $single = false );
170
171 /**
172 * Method to add data to the context's storage engine.
173 *
174 * @param int $data_id The ID of the object holding the data, e.g. Post ID.
175 * @param string $data_key The key for the data, e.g. a meta_key.
176 * @param mixed $data_value The value to store.
177 * @param boolean $unique Optional. If true, data will only be added if the
178 * object with the given $data_id doesn't already
179 * contain data for the given $data_key. This may not
180 * apply to every context. Default is false.
181 * @return boolean|integer On success, should return the ID of the stored
182 * data (an integer, which will evaluate as true).
183 * If the $unique argument is set to true and data
184 * with the given key already exists, this should
185 * return false. @see add_post_meta().
186 */
187 abstract protected function add_data( $data_id, $data_key, $data_value, $unique = false );
188
189 /**
190 * Method to update the data in the context's storage engine.
191 *
192 * @param int $data_id The ID of the object holding the data, e.g. Post ID.
193 * @param string $data_key The key for the data, e.g. a meta_key.
194 * @param mixed $data_value The value to store.
195 * @param mixed $data_prev_value Optional. Only update data if the previous
196 * value matches the data provided. This may
197 * not apply to every context.
198 * @return mixed Should return data id if the data doesn't exist, otherwise
199 * should return true on success and false on failure. If the
200 * value passed to this method is the same as the stored
201 * value, this method should return false.
202 * @see update_post_meta().
203 */
204 abstract protected function update_data( $data_id, $data_key, $data_value, $data_prev_value = '' );
205
206 /**
207 * Method to delete data from the context's storage engine.
208 *
209 * @param int $data_id The ID of the object holding the data, e.g. Post ID.
210 * @param string $data_key The key for the data, e.g. a meta_key.
211 * @param mixed $data_value Only delete the data if the stored value matches
212 * $data_value. This may not apply to every
213 * context.
214 * @return boolean False for failure. True for success.
215 * @see delete_post_meta().
216 */
217 abstract protected function delete_data( $data_id, $data_key, $data_value = '' );
218
219 }
220