Fieldmanager
  • Package
  • Function
  • Tree
  • Todo

Packages

  • Fieldmanager
    • Context
    • Datasource
    • Field
    • Util
  • None

Functions

  • Fieldmanager_Util_Validation
  • fm_page_form_did_save
  • fm_the_page_form
  • fm_url_to_post_id
  1 <?php
  2 
  3 /**
  4  * Datasource to populate autocomplete and option fields with WordPress Posts.
  5  *
  6  * @package Fieldmanager_Datasource
  7  */
  8 class Fieldmanager_Datasource_Post extends Fieldmanager_Datasource {
  9 
 10     /**
 11      * Supply a function which returns a list of posts; takes one argument,
 12      * a possible fragment
 13      */
 14     public $query_callback = Null;
 15 
 16     /**
 17      * Arguments to get_posts(), which uses WP's defaults, plus
 18      * suppress_filters = False, which can be overriden by setting
 19      * suppress_filters = True here.
 20      * @see http://codex.wordpress.org/Template_Tags/get_posts
 21      */
 22     public $query_args = array();
 23 
 24     /**
 25      * @var boolean
 26      * Allow AJAX. If set to false, Autocomplete will pre-load get_items() with no fragment,
 27      * so False could cause performance problems.
 28      */
 29     public $use_ajax = True;
 30 
 31     /**
 32      * @var string|Null
 33      * If not empty, set this post's ID as a value on the linked post. This is used to
 34      * establish two-way relationships.
 35      */
 36     public $reciprocal = Null;
 37 
 38     /**
 39      * @var boolean
 40      * Display the post publish date in the typeahead menu.
 41      */
 42     public $show_date = False;
 43 
 44     /**
 45      * @var string
 46      * If $show_date is true, the format to use for displaying the date.
 47      */
 48     public $date_format = 'Y-m-d';
 49 
 50     /**
 51      * @var boolean
 52      * Publish the child post when/if the parent is published.
 53      */
 54     public $publish_with_parent = False;
 55 
 56     /**
 57      * @var boolean
 58      * Save to post parent
 59      */
 60     public $save_to_post_parent = False;
 61 
 62     /**
 63      * @var boolean
 64      * Only save to post parent
 65      */
 66     public $only_save_to_post_parent = False;
 67 
 68     public function __construct( $options = array() ) {
 69         parent::__construct( $options );
 70 
 71         // Infer $save_to_post_parent if $only_save_to_post_parent
 72         if ( $this->only_save_to_post_parent ) {
 73             $this->save_to_post_parent = true;
 74         }
 75     }
 76 
 77     /**
 78      * Get a post title by post ID
 79      * @param int $value post_id
 80      * @return string post title
 81      */
 82     public function get_value( $value ) {
 83         $id = intval( $value );
 84         return $id ? get_the_title( $id ) : '';
 85     }
 86 
 87     /**
 88      * Get posts which match this datasource, optionally filtered by
 89      * a fragment, e.g. for Autocomplete.
 90      * @param string $fragment
 91      * @return array post_id => post_title for display or AJAX
 92      */
 93     public function get_items( $fragment = Null ) {
 94         if ( is_callable( $this->query_callback ) ) {
 95             return call_user_func( $this->query_callback, $fragment );
 96         }
 97         $default_args = array(
 98             'numberposts' => 10,
 99             'orderby' => 'post_date',
100             'order' => 'DESC',
101             'post_status' => 'publish',
102             'post_type' => 'any',
103             'suppress_filters' => False,
104         );
105         $post_args = array_merge( $default_args, $this->query_args );
106         $ret = array();
107         if ( $fragment ) {
108             $post_id = $exact_post = Null;
109             if ( preg_match( '/^https?\:/i', $fragment ) ) {
110                 $url = esc_url( $fragment );
111                 $url_parts = parse_url( $url );
112 
113                 if ( ! empty( $url_parts['query'] ) )  {
114                     $get_vars = array();
115                     parse_str( $url_parts['query'], $get_vars );
116                 }
117 
118                 if ( ! empty( $get_vars['post'] )  ) {
119                     $post_id = intval( $get_vars['post'] );
120                 } elseif ( ! empty( $get_vars['p'] ) ) {
121                     $post_id = intval( $get_vars['p'] );
122                 } else {
123                     $post_id = fm_url_to_post_id( $fragment );
124                 }
125             } elseif ( is_numeric( $fragment ) ) {
126                 $post_id = intval( $fragment );
127             }
128             if ( $post_id ) {
129                 $exact_post = get_post( $post_id );
130                 if ( $exact_post && (
131                     $post_args['post_type'] == 'any' ||
132                     $post_args['post_type'] == $exact_post->post_type ||
133                     in_array( $exact_post->post_type, $post_args['post_type'] )
134                 ) ) {
135                     if ( $this->show_date ) {
136                         $date_pad = ' (' . date( $this->date_format, strtotime( $exact_post->post_date ) ) . ')';
137                     }
138                     else {
139                         $date_pad = '';
140                     }
141                     $ret[ $post_id ] = html_entity_decode( $exact_post->post_title ) . $date_pad;
142                 }
143             }
144             $this->_fragment = $fragment;
145             add_filter( 'posts_where', array( $this, 'title_like' ), 10, 2 );
146         }
147         $posts = get_posts( $post_args );
148         if ( $fragment ) {
149             remove_filter( 'posts_where', array( $this, 'title_like' ), 10, 2 );
150         }
151         foreach ( $posts as $p ) {
152             if ( $this->show_date ) {
153                 $date_pad = ' (' . date( $this->date_format, strtotime( $p->post_date ) ) . ')';
154             }
155             else {
156                 $date_pad = '';
157             }
158             $ret[$p->ID] = $p->post_title . $date_pad;
159         }
160         return $ret;
161     }
162 
163     /**
164      * Get an action to register by hashing (non cryptographically for speed)
165      * the options that make this datasource unique.
166      * @return string ajax action
167      */
168     public function get_ajax_action() {
169         if ( !empty( $this->ajax_action ) ) return $this->ajax_action;
170         $unique_key = json_encode( $this->query_args );
171         $unique_key .= (string) $this->query_callback;
172         return 'fm_datasource_post' . crc32( $unique_key );
173     }
174 
175     /**
176      * Perform a LIKE search on post_title, since 's' in WP_Query is too fuzzy when trying to autocomplete a title
177      */
178     public function title_like( $where, &$wp_query ) {
179         global $wpdb;
180         if ( method_exists( $wpdb, 'esc_like' ) ) {
181             $like = esc_sql( $wpdb->esc_like( $this->_fragment ) );
182         } else {
183             $like = esc_sql( like_escape( $this->_fragment ) );
184         }
185         $where .= " AND {$wpdb->posts}.post_title LIKE '%{$like}%'";
186         return $where;
187     }
188 
189     /**
190      * For post relationships, delete reciprocal post metadata prior to saving (presave will re-add)
191      * @param array $values new post values
192      * @param array $current_values existing post values
193      */
194     public function presave_alter_values( Fieldmanager_Field $field, $values, $current_values ) {
195         if ( 'post' == $field->data_type && ! empty( $this->reciprocal ) && ! empty( $current_values ) && is_array( $current_values ) ) {
196             foreach ( $current_values as $reciprocal_post_id ) {
197                 delete_post_meta( $reciprocal_post_id, $this->reciprocal, $field->data_id );
198             }
199         }
200 
201         return $values;
202     }
203 
204     /**
205      * Handle reciprocal postmeta and post parents
206      * @param int $value
207      * @return string
208      */
209     public function presave( Fieldmanager_Field $field, $value, $current_value ) {
210         if ( empty( $value ) ) {
211             return;
212         }
213         $value = intval( $value );
214 
215         if ( ! empty( $this->publish_with_parent ) || ! empty( $this->reciprocal ) ) {
216             // There are no permissions in cron, but no changes are coming from a user either
217             if ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ) {
218                 $post_type_obj = get_post_type_object( get_post_type( $value ) );
219                 if ( empty( $post_type_obj->cap->edit_post ) || ! current_user_can( $post_type_obj->cap->edit_post, $value ) ) {
220                     wp_die( esc_html( sprintf( __( 'Tried to alter %s %d through field "%s", which user is not permitted to edit.', 'fieldmanager' ), $post_type_obj->name, $value, $field->name ) ) );
221                 }
222             }
223             $this->presave_status_transition( $field, $value );
224             if ( $this->reciprocal ) {
225                 add_post_meta( $value, $this->reciprocal, $field->data_id );
226             }
227         }
228 
229         if ( $this->save_to_post_parent && 1 == $field->limit && 'post' == $field->data_type ) {
230             if ( ! wp_is_post_revision( $field->data_id ) ) {
231                 Fieldmanager_Context_Post::safe_update_post(
232                     array(
233                         'ID' => $field->data_id,
234                         'post_parent' => $value,
235                     )
236                 );
237             }
238             if ( $this->only_save_to_post_parent ) {
239                 return array();
240             }
241         }
242 
243         return $value;
244     }
245 
246     /**
247      * Handle any actions based on the parent's status transition
248      * @param Fieldmanager_Field $field the parent
249      * @param int $value the child post id
250      * @return void
251      */
252     public function presave_status_transition( Fieldmanager_Field $field, $value ) {
253         // if this child post is in a post (or quickedit) context on a published post, publish the child also
254         if ( $this->publish_with_parent && 'post' === $field->data_type && ! empty( $field->data_id ) && 'publish' === get_post_status( $field->data_id ) ) {
255             // use wp_update_post so that post_name is generated if it's not been already
256             wp_update_post( array( 'ID' => $value, 'post_status' => 'publish' ) );
257         }
258     }
259 
260     /**
261      * Preload alter values for post parent
262      * The post datasource can store data outside FM's array.
263      * This is how we add it back into the array for editing.
264      * @param Fieldmanager_Field $field
265      * @param array $values
266      * @return array $values loaded up, if applicable.
267      */
268     public function preload_alter_values( Fieldmanager_Field $field, $values ) {
269         if ( $this->only_save_to_post_parent ) {
270             $post_parent = wp_get_post_parent_id( $field->data_id );
271             if ( $post_parent ) {
272                 return ( 1 == $field->limit && empty( $field->multiple ) ) ? $post_parent : array( $post_parent );
273             }
274         }
275         return $values;
276     }
277 
278     /**
279      * Get edit link for a post
280      * @param int $value
281      * @return string
282      */
283     public function get_view_link( $value ) {
284         return sprintf(
285             ' <a target="_new" class="fm-autocomplete-view-link %s" href="%s">%s</a>',
286             empty( $value ) ? 'fm-hidden' : '',
287             empty( $value ) ? '#' : esc_url( get_permalink( $value ) ),
288             esc_html__( 'View', 'fieldmanager' )
289         );
290     }
291 
292     /**
293      * Get edit link for a post
294      * @param int $value
295      * @return string
296      */
297     public function get_edit_link( $value ) {
298         return sprintf(
299             ' <a target="_new" class="fm-autocomplete-edit-link %s" href="%s">%s</a>',
300             empty( $value ) ? 'fm-hidden' : '',
301             empty( $value ) ? '#' : esc_url( get_edit_post_link( $value ) ),
302             esc_html__( 'Edit', 'fieldmanager' )
303         );
304     }
305 
306 }
307 
308 /**
309  * Post URLs to IDs function, supports custom post types.
310  * Borrowed and modified from url_to_postid() in wp-includes/rewrite.php
311  * @author http://betterwp.net/
312  * @param string $url
313  * @return int|boolean post ID on success, false on failure
314  */
315 function fm_url_to_post_id( $url ) {
316     global $wp_rewrite;
317 
318     $url = apply_filters('url_to_postid', $url);
319 
320     // First, check to see if there is a 'p=N' or 'page_id=N' to match against
321     if ( preg_match('#[?&](p|page_id|attachment_id)=(\d+)#', $url, $values ) ) {
322         $id = absint($values[2]);
323         if ( $id )
324             return $id;
325     }
326 
327     // Check to see if we are using rewrite rules
328     $rewrite = $wp_rewrite->wp_rewrite_rules();
329 
330     // Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options
331     if ( empty( $rewrite ) )
332         return 0;
333 
334     // Get rid of the #anchor
335     $url_split = explode( '#', $url );
336     $url = $url_split[0];
337 
338     // Get rid of URL ?query=string
339     $url_split = explode('?', $url);
340     $url = $url_split[0];
341 
342     // Add 'www.' if it is absent and should be there
343     if ( false !== strpos(home_url(), '://www.') && false === strpos($url, '://www.') )
344         $url = str_replace('://', '://www.', $url);
345 
346     // Strip 'www.' if it is present and shouldn't be
347     if ( false === strpos(home_url(), '://www.') )
348         $url = str_replace('://www.', '://', $url);
349 
350     // Strip 'index.php/' if we're not using path info permalinks
351     if ( !$wp_rewrite->using_index_permalinks() )
352         $url = str_replace('index.php/', '', $url);
353 
354     if ( false !== strpos($url, home_url()) ) {
355         // Chop off http://domain.com
356         $url = str_replace(home_url(), '', $url);
357     } else {
358         // Chop off /path/to/blog
359         $home_path = parse_url(home_url());
360         $home_path = isset( $home_path['path'] ) ? $home_path['path'] : '' ;
361         $url = str_replace($home_path, '', $url);
362     }
363 
364     // Trim leading and lagging slashes
365     $url = trim($url, '/');
366 
367     $request = $url;
368     // Look for matches.
369     $request_match = $request;
370     foreach ( (array)$rewrite as $match => $query) {
371         // If the requesting file is the anchor of the match, prepend it
372         // to the path info.
373         if ( !empty($url) && ($url != $request) && (strpos($match, $url) === 0) )
374             $request_match = $url . '/' . $request;
375 
376         if ( preg_match("!^$match!", $request_match, $matches) ) {
377             // Got a match.
378             // Trim the query of everything up to the '?'.
379             $query = preg_replace("!^.+\?!", '', $query);
380 
381             // Substitute the substring matches into the query.
382             $query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
383 
384             // Filter out non-public query vars
385             global $wp;
386             parse_str($query, $query_vars);
387             $query = array();
388             foreach ( (array) $query_vars as $key => $value ) {
389                 if ( in_array($key, $wp->public_query_vars) )
390                     $query[$key] = $value;
391             }
392 
393         // Taken from class-wp.php
394         foreach ( $GLOBALS['wp_post_types'] as $post_type => $t )
395             if ( $t->query_var )
396                 $post_type_query_vars[$t->query_var] = $post_type;
397 
398         foreach ( $wp->public_query_vars as $wpvar ) {
399             if ( isset( $wp->extra_query_vars[$wpvar] ) )
400                 $query[$wpvar] = $wp->extra_query_vars[$wpvar];
401             elseif ( isset( $_POST[$wpvar] ) )
402                 $query[$wpvar] = $_POST[$wpvar];
403             elseif ( isset( $_GET[$wpvar] ) )
404                 $query[$wpvar] = $_GET[$wpvar];
405             elseif ( isset( $query_vars[$wpvar] ) )
406                 $query[$wpvar] = $query_vars[$wpvar];
407 
408             if ( !empty( $query[$wpvar] ) ) {
409                 if ( ! is_array( $query[$wpvar] ) ) {
410                     $query[$wpvar] = (string) $query[$wpvar];
411                 } else {
412                     foreach ( $query[$wpvar] as $vkey => $v ) {
413                         if ( !is_object( $v ) ) {
414                             $query[$wpvar][$vkey] = (string) $v;
415                         }
416                     }
417                 }
418 
419                 if ( isset($post_type_query_vars[$wpvar] ) ) {
420                     $query['post_type'] = $post_type_query_vars[$wpvar];
421                     $query['name'] = $query[$wpvar];
422                 }
423             }
424         }
425 
426             // Do the query
427             $query = new WP_Query($query);
428             if ( !empty($query->posts) && $query->is_singular )
429                 return $query->post->ID;
430             else
431                 return 0;
432         }
433     }
434     return 0;
435 }
436 
Fieldmanager API documentation generated by ApiGen 2.8.0