Fieldmanager
  • Package
  • Class
  • Tree
  • Todo

Packages

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

Classes

  • Fieldmanager_Datasource
  • Fieldmanager_Datasource_Post
  • Fieldmanager_Datasource_Term
  • Fieldmanager_Datasource_User
  1 <?php
  2 
  3 /**
  4  * Datasource to populate autocomplete and option fields with WordPress Terms.
  5  *
  6  * @package Fieldmanager_Datasource
  7  */
  8 class Fieldmanager_Datasource_Term extends Fieldmanager_Datasource {
  9 
 10     /**
 11      * @var string|array
 12      * Taxonomy name or array of taxonomy names
 13      */
 14     public $taxonomy = null;
 15 
 16     /**
 17      * @var array
 18      * Helper for taxonomy-based option sets; arguments to find terms
 19      */
 20     public $taxonomy_args = array();
 21 
 22     /**
 23      * @var boolean
 24      * Sort taxonomy hierarchically and indent child categories with dashes?
 25      */
 26     public $taxonomy_hierarchical = false;
 27 
 28     /**
 29      * @var int
 30      * How far to descend into taxonomy hierarchy (0 for no limit)
 31      */
 32     public $taxonomy_hierarchical_depth = 0;
 33 
 34     /**
 35      * @var boolean
 36      * Pass $append = true to wp_set_object_terms?
 37      */
 38     public $append_taxonomy = False;
 39 
 40     /**
 41      * @var string
 42      * If true, additionally save taxonomy terms to WP's terms tables.
 43      */
 44     public $taxonomy_save_to_terms = True;
 45 
 46     /**
 47      * @var string
 48      * If true, only save this field to the taxonomy tables, and do not serialize in the FM array.
 49      */
 50     public $only_save_to_taxonomy = False;
 51 
 52     /**
 53      * @var boolean
 54      * If true, store the term_taxonomy_id instead of the term_id
 55      */
 56     public $store_term_taxonomy_id = False;
 57 
 58     /**
 59      * @var boolean
 60      * Build this datasource using AJAX
 61      */
 62     public $use_ajax = True;
 63 
 64     /**
 65      * Constructor
 66      */
 67     public function __construct( $options = array() ) {
 68         global $wp_taxonomies;
 69 
 70         // default to showing empty tags, which generally makes more sense for the types of fields
 71         // that fieldmanager supports
 72         if ( !isset( $options['taxonomy_args']['hide_empty'] ) ) {
 73             $options['taxonomy_args']['hide_empty'] = False;
 74         }
 75 
 76         parent::__construct( $options );
 77 
 78         // Ensure that $taxonomy_save_to_terms is true if it needs to be
 79         if ( $this->only_save_to_taxonomy ) {
 80             $this->taxonomy_save_to_terms = true;
 81         }
 82 
 83         if ( $this->taxonomy_save_to_terms ) {
 84             // Ensure that the taxonomies are sortable if we're not using FM storage.
 85             foreach ( $this->get_taxonomies() as $taxonomy ) {
 86                 if ( ! empty( $wp_taxonomies[ $taxonomy ] ) ) {
 87                     $wp_taxonomies[ $taxonomy ]->sort = true;
 88                 }
 89             }
 90         }
 91     }
 92 
 93     /**
 94      * Get taxonomies; normalizes $this->taxonomy to an array
 95      * @return array of taxonomies
 96      */
 97     public function get_taxonomies() {
 98         return is_array( $this->taxonomy ) ? $this->taxonomy : array( $this->taxonomy );
 99     }
100 
101     /**
102      * Get an action to register by hashing (non cryptographically for speed)
103      * the options that make this datasource unique.
104      * @return string ajax action
105      */
106     public function get_ajax_action() {
107         if ( !empty( $this->ajax_action ) ) return $this->ajax_action;
108         $unique_key = json_encode( $this->taxonomy_args );
109         $unique_key .= json_encode( $this->get_taxonomies() );
110         $unique_key .= (string) $this->taxonomy_hierarchical;
111         $unique_key .= (string) $this->taxonomy_hierarchical_depth;
112         return 'fm_datasource_term_' . crc32( $unique_key );
113     }
114 
115     /**
116      * Unique among FM types, the taxonomy datasource can store data outside FM's array.
117      * This is how we add it back into the array for editing.
118      * @param Fieldmanager_Field $field
119      * @param array $values
120      * @return array $values loaded up, if applicable.
121      */
122     public function preload_alter_values( Fieldmanager_Field $field, $values ) {
123         if ( $this->only_save_to_taxonomy ) {
124             $taxonomies = $this->get_taxonomies();
125             $terms = wp_get_object_terms( $field->data_id, $taxonomies[0], array( 'orderby' => 'term_order' ) );
126 
127             if ( count( $terms ) > 0 ) {
128                 if ( $field->limit == 1 && empty( $field->multiple ) ) {
129                     return $terms[0]->term_id;
130                 } else {
131                     $ret = array();
132                     foreach ( $terms as $term ) {
133                         $ret[] = $term->term_id;
134                     }
135                     return $ret;
136                 }
137             }
138         }
139         return $values;
140     }
141 
142     /**
143      * Presave hook to set taxonomy data
144      * @param int[] $values
145      * @param int[] $current_values
146      * @return int[] $values
147      */
148     public function presave_alter_values( Fieldmanager_Field $field, $values, $current_values ) {
149         if ( !is_array( $values ) ) {
150             $values = array( $values );
151         }
152 
153         // maybe we can create terms here.
154         if ( get_class( $field ) == 'Fieldmanager_Autocomplete' && !$field->exact_match && isset( $this->taxonomy ) ) {
155             foreach( $values as $i => $value ) {
156                  // could be a mix of valid term IDs and new terms.
157                 if ( is_numeric( $value ) ) {
158                     continue;
159                 }
160 
161                 // the JS adds an '=' to the front of numeric values if it's not a found term to prevent problems with new numeric terms.
162                 if ( '=' === substr( $value, 0, 1 ) ) {
163                     $value = sanitize_text_field( substr( $value, 1 ) );
164                 }
165 
166                 // an affordance for our friends at WordPress.com
167                 $term_by = function_exists( 'wpcom_vip_get_term_by' ) ? 'wpcom_vip_get_term_by' : 'get_term_by';
168                 $term = call_user_func( $term_by, 'name', $value, $this->taxonomy );
169 
170                 if ( !$term ) {
171                     $term = wp_insert_term( $value, $this->taxonomy );
172                     if ( is_wp_error( $term ) ) {
173                         unset( $value );
174                         continue;
175                     }
176                     $term = (object) $term;
177                 }
178                 $values[$i] = $term->term_id;
179             }
180         }
181 
182         // If this is a taxonomy-based field, must also save the value(s) as an object term
183         if ( $this->taxonomy_save_to_terms && isset( $this->taxonomy ) && !empty( $values ) ) {
184             $tax_values = array();
185             foreach ( $values as $value ) {
186                 if ( !empty( $value ) ) {
187                     if( is_numeric( $value ) )
188                         $tax_values[] = $value;
189                     else if( is_array( $value ) )
190                         $tax_values = $value;
191                 }
192             }
193             $this->pre_save_taxonomy( $tax_values, $field );
194         }
195         if ( $this->only_save_to_taxonomy ) {
196             if ( empty( $values ) && ! ( $this->append_taxonomy ) ) {
197                 $this->pre_save_taxonomy( array(), $field );
198             }
199             return array();
200         }
201         return $values;
202     }
203 
204     /**
205      * Sanitize a value
206      */
207     public function presave( Fieldmanager_Field $field, $value, $current_value ) {
208         return empty( $value ) ? $value : intval( $value );
209     }
210 
211     /**
212      * Save taxonomy data
213      * @param mixed[] $tax_values
214      * @return void
215      */
216     public function pre_save_taxonomy( $tax_values, $field ) {
217 
218         $tax_values = array_map( 'intval', $tax_values );
219         $tax_values = array_unique( $tax_values );
220         $taxonomies = $this->get_taxonomies();
221 
222         $tree = $field->get_form_tree();
223         $oldest_parent = array_shift( $tree );
224 
225         foreach( $taxonomies as $taxonomy ) {
226             if ( ! isset( $oldest_parent->current_context->taxonomies_to_save[ $taxonomy ] ) ) {
227                 $oldest_parent->current_context->taxonomies_to_save[ $taxonomy ] = array(
228                     'term_ids' => array(),
229                     'append'   => $this->append_taxonomy,
230                 );
231             } else {
232                 // Append any means append all
233                 $oldest_parent->current_context->taxonomies_to_save[ $taxonomy ]['append'] = $oldest_parent->current_context->taxonomies_to_save[ $taxonomy ]['append'] && $this->append_taxonomy;
234             }
235         }
236 
237         // Store the each term for this post. Handle grouped fields differently since multiple taxonomies are present.
238         if ( count( $taxonomies ) > 1 ) {
239             // Build the taxonomy insert data
240             $taxonomies_to_save = array();
241             foreach ( $tax_values as $term_id ) {
242                 $term = $this->get_term( $term_id );
243                 $oldest_parent->current_context->taxonomies_to_save[ $term->taxonomy ]['term_ids'][] = $term_id;
244             }
245         } else {
246             $oldest_parent->current_context->taxonomies_to_save[ $taxonomies[0] ]['term_ids'] = array_merge( $oldest_parent->current_context->taxonomies_to_save[ $taxonomies[0] ]['term_ids'], $tax_values );
247         }
248     }
249 
250     /**
251      * Get taxonomy data per $this->taxonomy_args
252      * @param $value The value(s) currently set for this field
253      * @return array[] data entries for options
254      */
255     public function get_items( $fragment = Null ) {
256 
257         // If taxonomy_hierarchical is set, assemble recursive term list, then bail out.
258         if ( $this->taxonomy_hierarchical ) {
259             $tax_args = $this->taxonomy_args;
260             $tax_args['parent'] = 0;
261             $parent_terms = get_terms( $this->get_taxonomies(), $tax_args );
262             return $this->build_hierarchical_term_data( $parent_terms, $this->taxonomy_args, 0, $fragment );
263         }
264 
265         $tax_args = $this->taxonomy_args;
266         if ( !empty( $fragment ) ) $tax_args['search'] = $fragment;
267         $terms = get_terms( $this->get_taxonomies(), $tax_args );
268 
269         // If the taxonomy list was an array and group display is set, ensure all terms are grouped by taxonomy
270         // Use the order of the taxonomy array list for sorting the groups to make this controllable for developers
271         // Order of the terms within the groups is already controllable via $taxonomy_args
272         // Skip this entirely if there is only one taxonomy even if group display is set as it would be unnecessary
273         if ( count( $this->get_taxonomies() ) > 1 && $this->grouped && $this->allow_optgroups ) {
274             // Group the data
275             $term_groups = array();
276             foreach ( $this->get_taxonomies() as $tax ) {
277                 $term_groups[$tax] = array();
278             }
279             foreach ( $terms as $term ) {
280                 $term_groups[$term->taxonomy][ $term->term_id ] = $term->name;
281             }
282             return $term_groups;
283         }
284 
285         // Put the taxonomy data into the proper data structure to be used for display
286         $stack = array();
287         foreach ( $terms as $term ) {
288             // Store the label for the taxonomy as the group since it will be used for display
289             $key = $this->store_term_taxonomy_id ? $term->term_taxonomy_id : $term->term_id;
290             $stack[ $key ] = $term->name;
291         }
292         return apply_filters( 'fm_datasource_term_get_items', $stack, $terms, $this, $fragment );
293     }
294 
295     /**
296      * Helper to support recursive building of a hierarchical taxonomy list.
297      * @param array $parent_terms
298      * @param array $tax_args as used in top-level get_terms() call.
299      * @param int $depth current recursive depth level.
300      * @param string $fragment optional matching pattern
301      * @return array of terms or false if no children found.
302      */
303     protected function build_hierarchical_term_data( $parent_terms, $tax_args, $depth, $stack = array(), $pattern = '' ) {
304 
305         // Walk through each term passed, add it (at current depth) to the data stack.
306         foreach ( $parent_terms as $term ) {
307             $taxonomy_data = get_taxonomy( $term->taxonomy );
308             $prefix = '';
309 
310             // Prefix term based on depth. For $depth = 0, prefix will remain empty.
311             for ( $i = 0; $i < $depth; $i++ ) {
312                 $prefix .= '--';
313             }
314 
315             $key = $this->store_term_taxonomy_id ? $term->term_taxonomy_id : $term->term_id;
316             $stack[ $key ] = $prefix . ' ' . $term->name;
317 
318             // Find child terms of this. If any, recurse on this function.
319             $tax_args['parent'] = $term->term_id;
320             if ( !empty( $pattern ) ) $tax_args['search'] = $fragment;
321             $child_terms = get_terms( $this->get_taxonomies(), $tax_args );
322             if ( $this->taxonomy_hierarchical_depth == 0 || $depth + 1 < $this->taxonomy_hierarchical_depth ) {
323                 if ( !empty( $child_terms ) ) {
324                     $stack = $this->build_hierarchical_term_data( $child_terms, $this->taxonomy_args, $depth + 1, $stack );
325                 }
326             }
327         }
328         return $stack;
329     }
330 
331     /**
332      * Translate term id to title, e.g. for autocomplete
333      * @param mixed $value
334      * @return string
335      */
336     public function get_value( $value ) {
337         $id = intval( $value );
338         if ( ! $id )
339             return null;
340 
341         $term = $this->get_term( $id );
342         $value = is_object( $term ) ? $term->name : '';
343         return apply_filters( 'fm_datasource_term_get_value', $value, $term, $this );
344     }
345 
346     /**
347      * Get term by ID only, potentially using multiple taxonomies
348      * @param int $term_id
349      * @return object|null
350      */
351     private function get_term( $term_id ) {
352         if ( $this->store_term_taxonomy_id ) {
353             global $wpdb;
354             return $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.term_taxonomy_id = %d LIMIT 1", $term_id ) );
355         } else {
356             $terms = get_terms( $this->get_taxonomies(), array( 'hide_empty' => false, 'include' => array( $term_id ), 'number' => 1 ) );
357             return !empty( $terms[0] ) ? $terms[0] : Null;
358         }
359     }
360 
361     /**
362      * Get link to view a term
363      * @param int $value term id
364      * @return string
365      */
366     public function get_view_link( $value ) {
367         return sprintf(
368             ' <a target="_new" class="fm-autocomplete-view-link %s" href="%s">%s</a>',
369             empty( $value ) ? 'fm-hidden' : '',
370             empty( $value ) ? '#' : esc_url( get_term_link( $this->get_term( $value ) ) ),
371             esc_html__( 'View', 'fieldmanager' )
372         );
373     }
374 
375     /**
376      * Get link to edit a term
377      * @param int $value term id
378      * @return string
379      */
380     public function get_edit_link( $value ) {
381         $term = $this->get_term( $value );
382         return sprintf(
383             '<a target="_new" class="fm-autocomplete-edit-link %s" href="%s">%s</a>',
384             empty( $value ) ? 'fm-hidden' : '',
385             empty( $value ) ? '#' : esc_url( get_edit_term_link( $term->term_id, $term->taxonomy ) ),
386             esc_html__( 'Edit', 'fieldmanager' )
387         );
388     }
389 
390 }
391 
Fieldmanager API documentation generated by ApiGen 2.8.0