In the time-honoured tradition I have managed to solve this after writing the question.
For anybody who also needs the answer this is how I excluded certain CPT entries from having their own page and appearing in the Yoast XML Sitemap if they did NOT have the “has_page” conditional field checked:
class CPT_Person
{
public static $CPT_PERSON = 'person';
public function __construct()
{
$this->hook_actions();
$this->hook_filters();
}
private function hook_actions()
{
add_action('template_redirect', array($this, 'people_404'));
}
private function hook_filters()
{
add_filter('wpseo_exclude_from_sitemap_by_post_ids', array($this, 'hide_people_from_yoast_sitemap'));
}
public function people_404_ids()
{
// Returns array of people CPT IDs where 'has page' has not been set to true
$exclude_args = array(
'post_type' => self::$CPT_PERSON,
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'has_page',
'value' => 0,
'compare' => '='
),
array(
'key' => 'has_page',
'compare' => 'NOT EXISTS'
)
),
'fields' => 'ids'
);
$the_query = new WP_Query($exclude_args);
return $the_query->posts;
}
public function hide_people_from_yoast_sitemap()
{ // Exclude some people from Yoast XML Sitemap
$exclude_ids = $this->people_404_ids();
return $exclude_ids;
}
public function people_404()
{
// Exclude some people from having single CPT page
if(is_singular(self::$CPT_PERSON))
{
$exclude_ids = $this->people_404_ids();
if (in_array(get_the_ID(), $exclude_ids))
{
global $wp_query;
$wp_query->set_404();
http_response_code(404);
get_template_part(404);
exit();
}
}
}
}
new CPT_Person();
Hi, thanks for the reply. I should have explained that this is below the content on a single-cpt.php page.
What I think I will do is run the above query and create an array of ordered post IDs. Then I’ll find the current post’s ID and choose the IDs directly before and after my one.
Ok, I’ve solved this crudely by putting the #SEP# separater and cost number in the label not the value field of the <option> tag. This way the label will remain unique unless changed and a change to the price will not effect which option has been preselected.
Here’s the amended code:
function acf_load_product_type_options($field) {
$field['choices'] = array();
if (have_rows('type_of_product', 'option')) :
while (have_rows('type_of_product', 'option')) : the_row();
$value = get_sub_field('price');
$label = get_sub_field('product_type');
$field['choices'][$label] = $label . '#SEP#' . $value;
endwhile;
endif;
return $field;
}
add_filter('acf/load_field/name=product_picker', 'acf_load_product_type_options');
Then I use jQuery once the field has loaded to run through all select option fields on a page. If they have the #SEP# seperator in the label then I take the cost value and create a data attribute on the <option> tag:
// ACF Select Dropdowns set up. Convert select option labels to data attributes
$('.acf-field-select select option').each(function() {
var optionLabel = $(this).text();
// If select field has a label containing '#SEP#' string then process
if (optionLabel.indexOf('#SEP#') >= 0) {
var labelArray = optionLabel.split('#SEP#');
// Set 'cost-value' data attribute on <option> tag
var costValue = parseFloat(labelArray[1]).toFixed(2);
$(this).data('cost-value', costValue);
// Rewrite option label to remove '#SEP#' and cost figure
var optionDescriptionText = labelArray[0];
$(this).text(optionDescriptionText);
}
});
That’s the set up. There is probably a better way to do this with PHP before the select field has been drawn. This way does show a flash of the ‘#SEP#’ and cost value in the actual select dropdown on the screen. It also seems to run twice for each field.
Finally when a user changes the select dropdown we take the ‘cost’ value from that option’s new data attribute and use it to populate the next box:
// ACF Form Fields Dynamically populated cost elements
$('body').on('change', '.acf-field-select select', function() {
// If manually changed select dropdown has a 'cost-value' data attribute
if (typeof $(this).find('option:selected').data('cost-value') !== 'undefined') {
// Populate next box with cost value
var costValue = $(this).find('option:selected').data('cost-value');
$(this).closest('.acf-field-select').next().find('.acf-input input[type=number]').val(costValue);
}
});
If somebody does know a safer way of doing this, maybe being able to add the data attributes to each option before the page is ‘ready’, then that would be great?
Or suggest something else that should be triggering the data attribute creation apart from document ready to avoid what looks like duplication?
Thanks!
Welcome to the Advanced Custom Fields community forum.
Browse through ideas, snippets of code, questions and answers between fellow ACF users
Helping others is a great way to earn karma, gain badges and help ACF development!
We use cookies to offer you a better browsing experience, analyze site traffic and personalize content. Read about how we use cookies and how you can control them in our Privacy Policy. If you continue to use this site, you consent to our use of cookies.