-
-
Save doubleedesign/2fe3034c658f3e62e2d841a420bb4bc0 to your computer and use it in GitHub Desktop.
| <?php | |
| /** | |
| * The SEO Framework + ACF flexible content integration | |
| * TSF will look at the excerpt and then the content to generate the default meta description. | |
| * If both of those are empty, this code looks for ACF flexible modules to get it from. | |
| * // TODO: Make this work with archives as well as posts | |
| * @param $description | |
| * @param $args | |
| * | |
| * @return mixed|string | |
| */ | |
| function doublee_seo_framework_description($description, $args) { | |
| if(empty($description) && get_the_id()) { | |
| $value_to_use = ''; | |
| // Find the first set of flexible modules, if there are any | |
| $field_name = doublee_get_name_of_first_acf_field_of_type('flexible_content'); | |
| $modules = get_field($field_name); | |
| // If there's modules, find the first one with a WYSIWYG or textarea field and get its value | |
| if($field_name && $modules) { | |
| $value_to_use = doublee_get_first_acf_subfield_value_of_type($modules, array('wysiwyg', 'textarea'), $field_name); | |
| } | |
| // Tell TSF to use this value; it will take care of truncating it for us. | |
| $description = strip_tags($value_to_use); | |
| } | |
| return $description; | |
| } | |
| add_filter('the_seo_framework_custom_field_description', 'doublee_seo_framework_description', 10, 2); | |
| add_filter('the_seo_framework_generated_description', 'doublee_seo_framework_description', 10, 2); | |
| add_filter('the_seo_framework_fetched_description_excerpt', 'doublee_seo_framework_description', 10, 2); |
| <?php | |
| /** | |
| * Utility function to get data about sub-fields that we want to use in doublee_get_first_acf_subfield_value_of_type | |
| * because you can't use get_sub_field_object outside of an ACF have_rows loop which was causing headaches with nested repeaters and whatnot | |
| * | |
| * @param $field_name | |
| * @param $post_id | |
| * | |
| * @return array | |
| */ | |
| function doublee_get_sub_field_data($field_name, $post_id) { | |
| global $wpdb; | |
| $data = array(); | |
| // Query the database for the field content of this field's subfields. Starts with the field slug without an underscore. | |
| // Returns an indexed array of meta ID, postt ID, meta key, and meta value sub-arrays. | |
| $meta_key_search = "'" . $field_name . "%'"; | |
| $postmeta = $wpdb->get_results("SELECT * FROM $wpdb->postmeta WHERE post_id = $post_id AND meta_key LIKE $meta_key_search ORDER BY meta_key ASC", ARRAY_A); | |
| // Query the database for the field keys of this field's subfields. Starts with the field slug preceded by an underscore. | |
| // Field keys are not unique - e.g. repeaters will have the same field key for each instance of a subfield. | |
| $meta_key_search = "'_" . $field_name . "%'"; | |
| $keymeta = $wpdb->get_results("SELECT * FROM $wpdb->postmeta WHERE post_id = $post_id AND meta_key LIKE $meta_key_search ORDER BY meta_key ASC", ARRAY_A); | |
| // Merge the results | |
| $merged = array(); | |
| foreach($postmeta as $index => $result_data) { | |
| $merged[] = array_merge_recursive($result_data, $keymeta[$index]); | |
| } | |
| // Because the keys are the same in the arrays we merged, this will cause the values to be a sub-array | |
| // Let's fix that, and don't include data we don't need | |
| $flattened = array(); | |
| foreach($merged as $merged_array) { | |
| $flattened[] = array( | |
| 'post_id' => $merged_array['post_id'][0], | |
| 'value' => $merged_array['meta_value'][0], | |
| 'key' => $merged_array['meta_value'][1] | |
| ); | |
| } | |
| // Use this and some more processing to build an array of all the data we need | |
| $i = 0; | |
| foreach($flattened as $index => $raw_data) { | |
| if(is_array($raw_data)) { | |
| $object = get_field_object($raw_data['key']); | |
| $value = $raw_data['value']; | |
| $parent_key = $object['parent']; | |
| $parent_name = ''; | |
| if(!empty($parent_key)) { | |
| $parent_object = get_field_object($parent_key); | |
| $parent_name = $parent_object['name']; | |
| } | |
| $data[$i]['name'] = $object['name']; | |
| $data[$i]['value'] = $value; | |
| $data[$i]['type'] = $object['type']; | |
| $data[$i]['parent_name'] = $parent_name; | |
| $i++; | |
| } | |
| } | |
| return $data; | |
| } |
| <?php | |
| /** | |
| * Utility function to get the name of the first ACF field of the specified type. | |
| * @param $field_type | |
| * @param string $post_id | |
| * | |
| * @return int|string | |
| */ | |
| function doublee_get_name_of_first_acf_field_name_of_type($field_type, $post_id = '') { | |
| $field_name = ''; | |
| if(!$post_id) { | |
| $post_id = get_the_id(); | |
| } | |
| $acf_fields = get_fields($post_id, true); | |
| if($acf_fields) { | |
| foreach($acf_fields as $name => $value) { | |
| $field_object = get_field_object($name); | |
| if($field_object['type'] == $field_type) { | |
| $field_name = $name; | |
| break; | |
| } | |
| } | |
| } | |
| return $field_name; | |
| } |
| <?php | |
| /** | |
| * Utility function to get the first direct subfield in an ACF set (flexible content or repeater) that is of the given type(s) | |
| * Recursively checks within nested sets for their first instance of the type when applicable | |
| * Returns the field (or sub-field) value ready for use by the calling function. | |
| * // TODO: Test this on grouped fields too. | |
| * | |
| * @param array $fields Array of ACF fields or subfields, as returned by get_field() on a flexible content or repeater field | |
| * @param array $types The field types we want to look for | |
| * @param string $parent_field_name The name of the top level field, e.g. the flexible content field. | |
| * Optional because when looking at nested fields recursively, the original value needs to be passed again. | |
| * | |
| * @return string | |
| */ | |
| function doublee_get_first_acf_subfield_value_of_type(array $fields, array $types, string $parent_field_name = '') { | |
| $all_field_data = doublee_get_sub_field_data('content_modules', get_the_id()); | |
| // If no fields were provided if they're not an array, bail early | |
| // Brought this out on its own to keep the main loop's nesting as simple and shallow as possible | |
| if(empty($fields) && !is_array($fields)) { | |
| return false; | |
| } | |
| // Loop through the fields | |
| foreach($fields as $index => $subfield) { | |
| // If the subfield's value is an array, it's a nested fieldset so we need to go another level down | |
| if(is_array($subfield)) { | |
| return doublee_get_first_acf_subfield_value_of_type($fields[$index], $types, $parent_field_name); | |
| } | |
| // We've reached content fields and can now proceed to look for our desired field types | |
| foreach($all_field_data as $data) { | |
| if(($data['name'] == $index) && (in_array($data['type'],$types)) && (!empty($data['value']))) { | |
| return $value_to_use = $data['value']; | |
| } | |
| } | |
| } | |
| // If a value hasn't been returned yet, there isn't one | |
| return false; | |
| } |
Please see https://gist.github.com/sybrew/b0bd829846fc1eda24025eb9f50eb86d for a proper format of using $args so that the filters also work on the administrative screens.
P.S. I'm The SEO Framework's developer.
On https://gist.github.com/doubleedesign/2fe3034c658f3e62e2d841a420bb4bc0#file-acf-tsf-integration-php-L18 you call an undefined function.
This snippet is from quite some time ago and I don't use it anymore (it was for some particular projects I no longer work on) so can't be 100% sure without setting up a test site to check, but I think it's likely meant to be doublee_get_name_of_first_acf_field_name_of_type in the data.php file above. I probably just messed up the naming when de-identifying the client by renaming functions for this gist.
On https://gist.github.com/doubleedesign/2fe3034c658f3e62e2d841a420bb4bc0#file-acf-tsf-integration-php-L18 you call an undefined function.