get_field( $field_name, $post_id, $options ); } return $this->get_fields( $post_id, $options ); } /* ================================================================ Get a field value ================================================================ */ public function get_field( $field_name, $post_id, $options ) { global $post; $defaults = array( 'format' => 'api' ); // "api", "input", or "raw" $options = array_merge( $defaults, $options ); if ( empty( $post_id ) && empty( $post ) ) { return null; } $post_id = empty( $post_id ) ? $post->ID : (int) $post_id; // Trigger get_fields if not in cache if ( ! isset( $this->cache[ $post_id ][ $options['format'] ][ $field_name ] ) ) { $fields = $this->get_fields( $post_id, $options ); return isset( $fields[ $field_name ] ) ? $fields[ $field_name ] : null; } return $this->cache[ $post_id ][ $options['format'] ][ $field_name ]; } /* ================================================================ Get all field values for a specific post ================================================================ */ public function get_fields( $post_id, $options ) { global $post, $wpdb; $defaults = array( 'format' => 'api' ); // "api", "input", or "raw" $options = array_merge( $defaults, $options ); $post_id = empty( $post_id ) ? $post->ID : (int) $post_id; // Return cached results if ( isset( $this->cache[ $post_id ][ $options['format'] ] ) ) { return $this->cache[ $post_id ][ $options['format'] ]; } $fields = array(); $field_data = array(); // Get all field groups for this post $group_ids = $this->get_matching_groups( $post_id, true ); if ( ! empty( $group_ids ) ) { $results = $this->find_input_fields( array( 'group_id' => array_keys( $group_ids ) ) ); foreach ( $results as $result ) { $result = (object) $result; $fields[ $result->id ] = $result; } if ( ! empty( $fields ) ) { // Make sure we're using active field groups $field_ids = implode( ',', array_keys( $fields ) ); // Get all the field data $sql = " SELECT m.meta_value, v.field_id, v.hierarchy, v.weight FROM {$wpdb->prefix}cfs_values v INNER JOIN {$wpdb->postmeta} m ON m.meta_id = v.meta_id WHERE v.field_id IN ($field_ids) AND v.post_id IN ($post_id) ORDER BY v.depth, FIELD(v.field_id, $field_ids), v.weight, v.sub_weight"; $results = $wpdb->get_results( $sql ); $num_rows = $wpdb->num_rows; $prev_hierarchy = ''; $prev_field_id = ''; $prev_item = ''; foreach ( $results as $row_count => $result ) { $field = $fields[$result->field_id]; $current_item = "{$result->hierarchy}.{$result->weight}.{$result->field_id}"; if ( ! empty( $result->hierarchy ) ) { // Format for API (field names) if ( 'api' == $options['format'] || 'raw' == $options['format'] ) { $tmp = explode( ':', $result->hierarchy ); foreach ( $tmp as $key => $val ) { if ( 0 == ( $key % 2 ) ) { $tmp[ $key ] = $fields[ $val ]->name; } } $hierarchy = implode( ':', $tmp ); } // Format for input (field IDs) else { $hierarchy = $result->hierarchy; } $this->assemble_value_array( $field_data, $hierarchy, $field, $result->meta_value ); } else { // Get the field name for "api" or "raw" formats if ( 'api' == $options['format'] || 'raw' == $options['format'] ) { $hierarchy = $field->name; } else { $hierarchy = $field->id; } $field_data[ $hierarchy ][] = $result->meta_value; } // Assemble the values if ( $current_item != $prev_item && '' != $prev_item ) { // call assemble_value_array on previous field $this->assemble_value_array( $field_data, $prev_hierarchy, $fields[ $prev_field_id ], false, $options ); } if ( $num_rows == ( $row_count + 1 ) ) { // last row $this->assemble_value_array( $field_data, $hierarchy, $field, false, $options ); } $prev_hierarchy = $hierarchy; $prev_field_id = $field->id; $prev_item = $current_item; } } } $this->cache[ $post_id ][ $options['format'] ] = $field_data; return $field_data; } /* ================================================================ Get properties for one or more fields ================================================================ */ public function get_field_info( $field_name = false, $post_id = false ) { global $post, $wpdb; $post_id = empty( $post_id ) ? $post->ID : (int) $post_id; // Get all field groups for this post $group_ids = $this->get_matching_groups( $post_id, true ); $group_ids = array_keys( $group_ids ); $output = array(); if ( ! empty( $group_ids ) ) { $results = $this->find_input_fields( array( 'group_id' => $group_ids ) ); foreach ( $results as $result ) { if ( $result['name'] == $field_name ) { $output = (array) $result; } elseif ( empty( $field_name ) ) { $output[ $result['name'] ] = (array) $result; } } } return $output; } /* ================================================================ Get referenced field values (using relationship fields) ================================================================ */ public function get_reverse_related( $post_id, $options = array() ) { global $wpdb; $where = "m.meta_value = '$post_id'"; if ( isset( $options['field_name'] ) ) { $field_name = implode( "','", (array) $options['field_name'] ); $where .= " AND m.meta_key IN ('$field_name')"; } if ( isset( $options['field_type'] ) ) { $field_type = $options['field_type']; } if ( isset( $options['post_type'] ) ) { $post_type = implode( "','", (array) $options['post_type'] ); $where .= " AND p.post_type IN ('$post_type')"; } if ( isset( $options['post_status'] ) ) { $post_status = implode( "','", (array) $options['post_status'] ); $where .= " AND p.post_status IN ('$post_status')"; } // Limit to specific field types $field_type = empty( $field_type ) ? 'relationship' : $field_type; $results = $this->find_input_fields( array( 'field_type' => $field_type ) ); if ( ! empty( $results ) ) { $field_ids = array(); foreach ( $results as $result ) { $field_ids[] = $result['id']; } $where .= " AND v.field_id IN (" . implode( ',', $field_ids ) . ")"; } $sql = " SELECT DISTINCT p.ID FROM {$wpdb->prefix}cfs_values v INNER JOIN $wpdb->posts p ON p.ID = v.post_id INNER JOIN $wpdb->postmeta m ON m.meta_id = v.meta_id WHERE $where"; $results = $wpdb->get_results( $sql ); $output = array(); foreach ( $results as $result ) { $output[] = $result->ID; } return $output; } /* ================================================================ Save field value ================================================================ */ public function save_fields( $field_data = array(), $post_data = array(), $options = array() ) { global $wpdb; $defaults = array( 'format' => 'api', // "api" or "input" 'field_groups' => array(), ); $options = array_merge( $defaults, $options ); // Log saved fields $this->saved_fields = array(); // create post if the ID is missing if ( empty( $post_data['ID'] ) ) { $post_defaults = array( 'post_title' => 'My CFS post', 'post_content' => '', 'post_content_filtered' => '', 'post_excerpt' => '', 'to_ping' => '', 'pinged' => '', ); $post_data = array_merge( $post_defaults, $post_data ); $post_id = wp_insert_post( $post_data ); } else { $post_id = (int) $post_data['ID']; if ( 1 < count( $post_data ) ) { $wpdb->update( $wpdb->posts, $post_data, array( 'ID' => $post_id ) ); clean_post_cache( $post_id ); } } // For input forms, get the group IDs from the HTTP POST // Otherwise, the field group might not match anymore (e.g. the taxonomy changed) if ( 'input' == $options['format'] ) { $group_ids = $options['field_groups']; } elseif ( 'api' == $options['format'] ) { // For revisions, get the parent post's field groups if ( wp_is_post_revision( $post_id ) ) { $revision_id = wp_is_post_revision( $post_id ); $group_ids = $this->get_matching_groups( $revision_id, true ); } else { $group_ids = $this->get_matching_groups( $post_id, true ); } $group_ids = array_keys( $group_ids ); } if ( ! empty( $group_ids ) ) { $parent_fields = array(); $results = $this->find_input_fields( array( 'group_id' => $group_ids ) ); foreach ( $results as $result ) { // Store all the field objects for the current field group(s) $fields[ $result['id'] ] = (object) $result; // Store lookup values for the recursion $field_id_lookup[ $result['parent_id'] . ':' . $result['name'] ] = $result['id']; // Store parent fields separately if ( 0 == (int) $result['parent_id'] ) { $parent_fields[ $result['name'] ] = $result['id']; } } } // If this is an API call, flatten the data! if ( 'api' == $options['format'] ) { $field_ids = array(); foreach ( $field_data as $field_name => $junk ) { if ( isset( $parent_fields[ $field_name ] ) ) { $field_ids[] = (int) $parent_fields[ $field_name ]; } } $field_ids = implode( ',', $field_ids ); $sql = " DELETE v, m FROM {$wpdb->prefix}cfs_values v LEFT JOIN {$wpdb->postmeta} m ON m.meta_id = v.meta_id WHERE v.post_id = '$post_id' AND (v.field_id IN ($field_ids) OR v.base_field_id IN ($field_ids))"; if ( ! empty( $field_ids ) ) { $wpdb->query( $sql ); } } elseif ( 'input' == $options['format'] ) { // If saving raw input, delete existing postdata $results = $this->find_input_fields( array( 'group_id' => $group_ids ) ); if ( ! empty( $results ) ) { $field_ids = array(); foreach ( $results as $result ) { $field_ids[] = $result['id']; } $field_ids = implode( ',', $field_ids ); $sql = " DELETE v, m FROM {$wpdb->prefix}cfs_values v LEFT JOIN {$wpdb->postmeta} m ON m.meta_id = v.meta_id WHERE v.post_id = '$post_id' AND v.field_id IN ($field_ids)"; if ( ! empty( $field_ids ) ) { $wpdb->query( $sql ); } } } // Save recursively $field_data = stripslashes_deep( $field_data ); foreach ( $field_data as $field_id => $field_array ) { $this->save_fields_recursive( array( 'field_id' => $field_id, 'field_array' => $field_array, 'post_id' => $post_id, 'parent_id' => 0, 'all_fields' => $fields, 'hierarchy' => array(), 'format' => $options['format'], 'field_id_lookup' => $field_id_lookup, 'weight' => 0, 'depth' => 0, ) ); } // Clear the cache $this->cache[ $post_id ] = null; return $post_id; } /* ================================================================ Extend save_fields to support loop fields ================================================================ */ private function save_fields_recursive( $params ) { global $wpdb; $field_type = 'loop'; $field_id = $params['field_id']; $field_array = (array) $params['field_array']; if ( 0 == $params['depth'] % 2 ) { // If not raw input, then field_id is actually the field name, and // we need to lookup the ID from the "field_id_lookup" array if ( 'input' != $params['format'] ) { $field_name = $field_id; $field_id = (int) $params['field_id_lookup'][ $params['parent_id'] . ':' . $field_name ]; } // Exit if the field is missing if ( ! isset( $params['all_fields'][ $field_id ] ) ) { return; } $field_type = $params['all_fields'][ $field_id ]->type; } // We've found the values if ( isset( $field_array['value'] ) || 'loop' != $field_type ) { $values = isset( $field_array['value'] ) ? $field_array['value'] : $field_array; // Trigger the pre_save hook $values = CFS()->fields[ $field_type ]->pre_save( $values, $params['all_fields'][ $field_id ] ); $sub_weight = 0; foreach ( (array) $values as $value ) { // Insert into postmeta $data = array( 'post_id' => $params['post_id'], 'meta_key' => $params['all_fields'][ $field_id ]->name, 'meta_value' => $value, ); $wpdb->insert( $wpdb->postmeta, $data ); $meta_id = $wpdb->insert_id; // Get the top-level field ID from the hierarchy array $base_field_id = empty( $params['hierarchy'] ) ? 0 : $params['hierarchy'][0]; // Insert into cfs_values $data = array( 'field_id' => $field_id, 'meta_id' => $meta_id, 'post_id' => $params['post_id'], 'base_field_id' => $base_field_id, 'hierarchy' => implode( ':', $params['hierarchy'] ), 'depth' => floor( count( $params['hierarchy'] ) / 2 ), 'weight' => $params['weight'], 'sub_weight' => $sub_weight, ); $wpdb->insert( $wpdb->prefix . 'cfs_values', $data ); $sub_weight++; // Update log $this->saved_fields[ $field_id ] = true; } } // Keep recursing else { foreach ( $field_array as $sub_field_id => $sub_field_array ) { $new_params = $params; $new_params['field_array'] = $sub_field_array; $new_params['field_id'] = $sub_field_id; $new_params['weight'] = $field_id; $new_params['depth']++; // If not raw input, then sub_field_id is actually the field name if ( 'input' != $params['format'] ) { if ( 0 == $new_params['depth'] % 2 ) { $sub_field_id = $params['field_id_lookup'][ $new_params['parent_id'] . ':' . $sub_field_id ]; } else { $new_params['parent_id'] = $field_id; } } if ( empty( $new_params['hierarchy'] ) ) { $new_params['hierarchy'][] = $field_id; } $new_params['hierarchy'][] = $sub_field_id; $this->save_fields_recursive( $new_params ); } } } /* ================================================================ Find input fields ================================================================ */ public function find_input_fields( $params = array() ) { global $wpdb; $defaults = array( 'post_id' => false, // a single post ID 'group_id' => array(), 'field_id' => array(), 'field_type' => array(), 'field_name' => array(), 'parent_id' => array(), ); $params = array_merge( $defaults, $params ); $field_groups = CFS()->field_group->load_field_groups(); $output = array(); foreach ( $field_groups as $group_id => $group ) { // Exclude by group ID if ( ! empty( $params['group_id'] ) && ! in_array( $group_id, (array) $params['group_id'] ) ) { continue; } // Exclude by group ID (groups attached to a specific post) if ( ! empty( $params['post_id'] ) ) { $post_id = (int) $params['post_id']; $matching_group_ids = array_keys( $this->get_matching_groups( $post_id, true ) ); if ( ! in_array( $group_id, $matching_group_ids ) ) { continue; } } foreach ( $group['fields'] as $field ) { // Other exclusions if ( ! empty( $params['field_id'] ) && ! in_array( $field['id'], (array) $params['field_id'] ) ) { continue; } if ( ! empty( $params['parent_id'] ) && ! in_array( $field['parent_id'], (array) $params['parent_id'] ) ) { continue; } if ( ! empty( $params['field_type'] ) && ! in_array( $field['type'], (array) $params['field_type'] ) ) { continue; } if ( ! empty( $params['field_name'] ) && ! in_array( $field['name'], (array) $params['field_name'] ) ) { continue; } // Attach the group ID $field['group_id'] = $group_id; $output[] = $field; } } return $output; } /* ================================================================ Get input fields / values for a specific post ID ================================================================ */ public function get_input_fields( $params = array() ) { global $post, $wpdb; $fields = $this->find_input_fields( $params ); $values = $this->get_fields( $post->ID, array( 'format' => 'input' ) ); $output = array(); foreach ( $fields as $field ) { $field = (object) $field; // If no field value exists, set it to NULL $field->value = isset( $values[ $field->id ] ) ? $values[ $field->id ] : null; if ( ! isset( $field->value ) && isset( $field->options['default_value'] ) ) { $field->value = $field->options['default_value']; } $output[ $field->id ] = $field; } return apply_filters( 'cfs_get_input_fields', $output, $params ); } /* ================================================================ Determine which field groups to use for the current post ================================================================ */ public function get_matching_groups( $params, $skip_roles = false ) { global $wpdb, $current_user; // Set post ID if ( ! is_array( $params ) ) { $post_id = (int) $params; if ( wp_is_post_revision( $post_id ) ) { $post_id = wp_is_post_revision( $post_id ); } $rule_types = array( 'post_ids' => $post_id ); } else { $rule_types = $params; } // Detect post_types / page_templates if they weren't sent if ( ! empty( $rule_types[ 'post_ids' ] ) ) { $rule_types['post_ids'] = array_map( 'absint', (array) $rule_types['post_ids'] ); if ( ! isset( $rule_types['post_types'] ) ) { $rule_types['post_types'] = array(); foreach ( $rule_types['post_ids'] as $pid ) { $post_type = get_post_type( $pid ); if ( ! in_array( $post_type, $rule_types['post_types'] ) ) { $rule_types['post_types'][] = $post_type; } } } if ( ! isset( $rule_types['post_formats'] ) ) { $rule_types['post_formats'] = array(); foreach ( $rule_types['post_ids'] as $pid ) { $post_format = get_post_format( $pid ); // Prevent post_format = false $post_format = ( false === $post_format ) ? 'standard' : $post_format; if ( ! in_array( $post_format, $rule_types['post_formats'] ) ) { $rule_types['post_formats'][] = $post_format; } } } if ( ! isset( $rule_types['page_templates'] ) ) { $rule_types['page_templates'] = array(); foreach ( $rule_types['post_ids'] as $pid ) { $page_template = get_post_meta( $pid, '_wp_page_template', true ); if ( ! empty( $page_template ) && ! in_array( $page_template, $rule_types['page_templates'] ) ) { $rule_types['page_templates'][] = $page_template; } } } } // Set defaults $rule_types = array_merge( array( 'post_types' => array(), 'post_formats' => array(), 'user_roles' => $current_user->roles, 'term_ids' => array(), 'post_ids' => array(), 'page_templates' => array() ), $rule_types ); // Get all field groups $field_groups = CFS()->field_group->load_field_groups(); // Ignore user_roles if used within get_fields if ( false !== $skip_roles ) { unset( $rule_types['user_roles'] ); } foreach ( $field_groups as $group_id => $result ) { $fail = false; $rules = $result['rules']; $extras = $result['extras']; foreach ( $rule_types as $rule_type => $value ) { if ( ! empty( $rules[ $rule_type ] ) ) { // Only lookup a post's term IDs if the rule exists if ( 'term_ids' == $rule_type ) { $sql = " SELECT tt.term_id FROM $wpdb->term_taxonomy tt INNER JOIN $wpdb->term_relationships tr ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tr.object_id = %d"; $value = $wpdb->get_col( $wpdb->prepare( $sql, $post_id ) ); } $operator = (array) $rules[ $rule_type ]['operator']; $in_array = ( 0 < count( array_intersect( (array) $value, $rules[ $rule_type ]['values'] ) ) ); if ( ( $in_array && '!=' == $operator[0] ) || ( ! $in_array && '==' == $operator[0] ) ) { $fail = true; } } } if ( ! $fail ) { $temp[] = array( 'post_id' => $group_id, 'post_title' => $result['title'], 'order' => empty( $extras['order'] ) ? 0 : (int) $extras['order'] ); } } $matches = array(); // Sort the field groups if ( ! empty( $temp ) ) { $temp = $this->array_orderby( $temp, 'order' ); foreach ( $temp as $values ) { $matches[ $values['post_id'] ] = $values['post_title']; } } // Allow for overrides return apply_filters( 'cfs_matching_groups', $matches, $params, $rule_types ); } /* ================================================================ Replace a value within a multidimensional array ================================================================ */ private function assemble_value_array( &$field_data, $hierarchy, $field, $field_value = false, $options = false ) { $value = &$field_data; foreach ( explode( ':', $hierarchy ) as $i ) { $value = &$value[ $i ]; } if ( false !== $field_value ) { $value = (array) $value; $value[] = $field_value; } else { if ( ! isset( CFS()->fields[ $field->type ] ) ) { return; } $value = CFS()->fields[ $field->type ]->prepare_value( $value, $field ); if ( 'api' == $options['format'] ) { $value = CFS()->fields[ $field->type ]->format_value_for_api( $value, $field ); } elseif ( 'input' == $options['format'] ) { $value = CFS()->fields[ $field->type ]->format_value_for_input( $value, $field ); } } } /* ================================================================ MySQL "ORDER BY" for PHP associative arrays @link https://gist.github.com/mgibbs189/4634604 ================================================================ */ private function array_orderby() { $args = func_get_args(); $data = array_shift( $args ); if ( ! is_array( $data ) ) { return array(); } $multisort_params = array(); foreach ( $args as $n => $field ) { if ( is_string( $field ) ) { $tmp = array(); foreach ( $data as $row ) { $tmp[] = $row[ $field ]; } $args[ $n ] = $tmp; } $multisort_params[] = &$args[ $n ]; } $multisort_params[] = &$data; call_user_func_array( 'array_multisort', $multisort_params ); return end( $multisort_params ); } } CFS()->api = new cfs_api();