1 <?php
2
3 /**
4 * Use fieldmanager to create meta boxes on the new/edit post screen and save
5 * data primarily to post meta.
6 *
7 * @package Fieldmanager_Context
8 */
9 class Fieldmanager_Context_Post extends Fieldmanager_Context_Storable {
10
11 /**
12 * @var string
13 * Title of meta box
14 */
15 public $title = '';
16
17 /**
18 * @var string[]
19 * What post types to render this meta box
20 */
21 public $post_types = array();
22
23 /**
24 * @var string
25 * Context (normal, advanced, or side)
26 */
27 public $context = 'normal';
28
29 /**
30 * @var priority
31 * Priority (high, core, default, or low)
32 */
33 public $priority = 'default';
34
35 /**
36 * @var Fieldmanager_Group
37 * Base field
38 */
39 public $fm = null;
40
41 /*
42 * @var boolean
43 */
44 private static $doing_internal_update = false;
45
46 /**
47 * Add a context to a fieldmanager
48 * @param string $title
49 * @param string|string[] $post_types
50 * @param string $context (normal, advanced, or side)
51 * @param string $priority (high, core, default, or low)
52 * @param Fieldmanager_Field $fm
53 */
54 public function __construct( $title, $post_types, $context = 'normal', $priority = 'default', $fm = Null ) {
55
56 // Populate the list of post types for which to add this meta box with the given settings
57 if ( !is_array( $post_types ) ) $post_types = array( $post_types );
58
59 $this->post_types = $post_types;
60 $this->title = $title;
61 $this->context = $context;
62 $this->priority = $priority;
63 $this->fm = $fm;
64
65 add_action( 'admin_init', array( $this, 'meta_box_render_callback' ) );
66 // If this meta box is on an attachment page, add the appropriate filter hook to save the data
67 if ( isset( $this->fm->is_attachment ) && $this->fm->is_attachment ) {
68 add_filter( 'attachment_fields_to_save', array( $this, 'save_fields_for_attachment' ), 10, 2 );
69 }
70 add_action( 'save_post', array( $this, 'delegate_save_post' ) );
71 // Check if any meta boxes need to be removed
72 if ( $this->fm && !empty( $this->fm->meta_boxes_to_remove ) ) {
73 add_action( 'add_meta_boxes', array( $this, 'remove_meta_boxes' ), 100 );
74 }
75 }
76
77 /**
78 * admin_init callback to add meta boxes to content types
79 * Registers render_meta_box()
80 * @return void
81 */
82 public function meta_box_render_callback() {
83 foreach ( $this->post_types as $type ) {
84 add_meta_box(
85 'fm_meta_box_' . $this->fm->name,
86 $this->title,
87 array( $this, 'render_meta_box' ),
88 $type,
89 $this->context,
90 $this->priority
91 );
92 }
93 }
94
95 /**
96 * Helper to attach element_markup() to add_meta_box(). Prints markup for post editor.
97 * @see http://codex.wordpress.org/Function_Reference/add_meta_box
98 * @param $post the post object.
99 * @param $form_struct the structure of the form itself. Unused.
100 * @return void.
101 */
102 public function render_meta_box( $post, $form_struct = null ) {
103 $this->fm->data_type = 'post';
104 $this->fm->data_id = $post->ID;
105
106 $this->render_field();
107
108 // Check if any validation is required
109 $fm_validation = Fieldmanager_Util_Validation( 'post', 'post' );
110 $fm_validation->add_field( $this->fm );
111 }
112
113 /**
114 * Helper to remove all built-in meta boxes for all specified taxonomies on a post type
115 * @param $post_type the post type
116 * @param $taxonomies the taxonomies for which to remove default meta boxes
117 * @return void.
118 */
119 public function remove_meta_boxes() {
120 foreach( $this->post_types as $type ) {
121 foreach( $this->fm->meta_boxes_to_remove as $meta_box ) {
122 remove_meta_box( $meta_box['id'], $type, $meta_box['context'] );
123 }
124 }
125 }
126
127 /**
128 * Handles saving Fieldmanager data when the custom meta boxes are used on an attachment.
129 * Calls save_fields_for_post with the post ID.
130 * @param array $post The post fields
131 * @param array $attachment The attachment fields
132 * @return void
133 */
134 public function save_fields_for_attachment( $post, $attachment ) {
135 // Use save_fields_for_post to handle saving any Fieldmanager meta data
136 $this->save_fields_for_post( $post['ID'] );
137
138 // Return the post data for the attachment unmodified
139 return $post;
140 }
141
142 /**
143 * Action handler to delegate to appropriate methods when a post is saved.
144 * @param int $post_id
145 * @return void
146 */
147 public function delegate_save_post( $post_id ) {
148 if ( self::$doing_internal_update ) {
149 return;
150 }
151 if( defined( 'DOING_CRON' ) && DOING_CRON ) {
152 $this->save_fields_for_cron( $post_id );
153 } else {
154 $this->save_fields_for_post( $post_id );
155 }
156 }
157
158 /**
159 * Takes $_POST data and saves it to, calling save_to_post_meta() once validation is passed
160 * When using Fieldmanager as an API, do not call this function directly, call save_to_post_meta()
161 * @param int $post_id
162 * @return void
163 */
164 public function save_fields_for_post( $post_id ) {
165 // Make sure this field is attached to the post type being saved.
166 if ( empty( $_POST['post_ID'] ) || ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $_POST['action'] != 'editpost' ) {
167 return;
168 }
169
170 // Make sure this hook fired on the post being saved, not a side-effect post for which the $_POST context is invalid.
171 if ( $post_id !== absint( $_POST['post_ID'] ) ) {
172 return;
173 }
174
175 // Prevent saving the same post twice; FM does not yet use revisions.
176 if ( get_post_type( $post_id ) == 'revision' ) {
177 return;
178 }
179
180 // Make sure this post type is intended for handling by this FM context.
181 if ( ! in_array( get_post_type( $post_id ), $this->post_types ) ) {
182 return;
183 }
184
185 // Do not handle quickedit in this context.
186 if ( $_POST['action'] == 'inline-save' ) {
187 return;
188 }
189
190 // Verify nonce is present and valid. If present but not valid, this
191 // throws an exception, but if it's absent we can assume our data is
192 // not present.
193 if ( ! $this->is_valid_nonce() ) {
194 return;
195 }
196
197 // Make sure the current user is authorized to save this post.
198 if ( $_POST['post_type'] == 'post' ) {
199 if ( ! current_user_can( 'edit_post', $post_id ) ) {
200 $this->fm->_unauthorized_access( __( 'User cannot edit this post', 'fieldmanager' ) );
201 return;
202 }
203 }
204
205 $this->save_to_post_meta( $post_id );
206 }
207
208 /**
209 * Process fields during a cron request, without saving data since the data isn't changing.
210 * @param int $post_id
211 * @return void
212 */
213 public function save_fields_for_cron( $post_id ) {
214 if ( ! in_array( get_post_type( $post_id ), $this->post_types ) ) {
215 return;
216 }
217 // don't save values since we aren't provided with any; just trigger presave so that subclass handlers can process as necessary
218 $this->fm->skip_save = true;
219 $current = get_post_meta( $post_id, $this->fm->name, true );
220 $this->save_to_post_meta( $post_id, $current );
221 }
222
223 /**
224 * Helper to save an array of data to post meta
225 * @param int $post_id
226 * @param array $data
227 * @return void
228 */
229 public function save_to_post_meta( $post_id, $data = null ) {
230 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
231 return;
232 }
233 $this->fm->data_id = $post_id;
234 $this->fm->data_type = 'post';
235
236 $this->save( $data );
237 }
238
239 /**
240 * Helper for fieldmanager internals to save a post without worrying about infinite loops
241 */
242 public static function safe_update_post( $args ) {
243 self::$doing_internal_update = true;
244 $ret = wp_update_post( $args );
245 self::$doing_internal_update = false;
246 return $ret;
247 }
248
249 /**
250 * Get post meta.
251 *
252 * @see get_post_meta().
253 */
254 protected function get_data( $post_id, $meta_key, $single = false ) {
255 return get_post_meta( $post_id, $meta_key, $single );
256 }
257
258 /**
259 * Add post meta.
260 *
261 * @see add_post_meta().
262 */
263 protected function add_data( $post_id, $meta_key, $meta_value, $unique = false ) {
264 return add_post_meta( $post_id, $meta_key, $meta_value, $unique );
265 }
266
267 /**
268 * Update post meta.
269 *
270 * @see update_post_meta().
271 */
272 protected function update_data( $post_id, $meta_key, $meta_value, $data_prev_value = '' ) {
273 return update_post_meta( $post_id, $meta_key, $meta_value, $data_prev_value );
274 }
275
276 /**
277 * Delete post meta.
278 *
279 * @see delete_post_meta().
280 */
281 protected function delete_data( $post_id, $meta_key, $meta_value = '' ) {
282 return delete_post_meta( $post_id, $meta_key, $meta_value );
283 }
284
285 }
286