0

So over on wordpress I have a custom post type called crosses, and I need to list them on my search results page in order with the shortest titles first and longest titles last. I thought the way to do it would be to create a custom field called "name-length" and then include a function to count the number of letters in each post title and assign it as a number in a that custom field, but the code I'm trying got it isn't working.

function update_my_metadata(){
$args = array(
    'post_type' => 'crosses', // Only get the posts
    'post_status' => 'publish', // Only the posts that are published
    'posts_per_page'   => -1 // Get every post
);
$posts = get_posts($args);
foreach ( $posts as $post ) {
    $step = get_the_title($post->ID);
    $title_word = count_chars($step);
    update_post_meta( $post->ID, 'name_length_crosses', $title_word );
  }
}

*edit: To clarify, the field isn't being filled with the title length. I can't check the database to see if anything is going in there, but I can view the post edit screen and the values aren't showing up there at least.

*edit again: Posts added by ACF, hooked using

add_action('init','update_my_metadata');

*final edit: Posted a related question to this one here, if anyone is interested: Front of word preference

  • are you not getting your meta-field populated with the title length? Did you check your database to see if the fields are getting filled? NB: you don't need to use the function get_the_title(), simply use $post->post_title – Aurovrata Apr 9 '18 at 13:45
  • Edited the question to clarify. – Aaron McNatt Apr 9 '18 at 16:03
  • if by 'edit screen' you mean the custom meta field metabox, this is not always showing you the meta-fields properly. Unless you have a custom metabox which retrives your meta-field, you should really check your databsee wp_postmeta table using phpmyadmin. One more questtion: how are you calling your function? Ideally you should hook it using the admin_init action and set a flag in your option table which you can check for to ensure this function does not repeat itself on each load. – Aurovrata Apr 9 '18 at 17:45
  • Sorry, should probably have mentioned the custom field was created by ACF. Sadly, the host my workplace insists on using doesn't use phpmyadmin. I don't have database access at all. It sucks cause we used to use it, but they switched to this other host cause it was more "secure". – Aaron McNatt Apr 9 '18 at 17:57
  • so how do you hook your function? I am assuming your funciton is in your functions.php file, but how did you hook it? – Aurovrata Apr 9 '18 at 17:59
0

Hooking on the save_post will only work for new posts. It will not solve the question of existing posts. This can only be solved with a hook on init or even better admin_init and flagging it in the options table to prevent it executing on each subsequent requests.

Furthermore, for new posts it is more efficient to hook onsave_post_{$post_type},

//for existing posts...
add_action('admin_init', 'udpate_crosses_once`);
function udpate_crosses_once(){
  $flag = get_option('crosses_updated', 'no');
  if('no'===$flag){
    $args = array(
      'post_type' => 'crosses', 
      'post_status' => 'publish', 
      'posts_per_page'   => -1 
    );
    $posts = get_posts($args);
    foreach ( $posts as $post ) {
      $length = strlen( trim( $post->post_title ) );
      update_post_meta( $post->ID, 'name_length_crosses', $length );
    }
    add_option('crosses_updated', 'yes'); //set flag.
  }
}
//for new posts...
add_action( 'save_post_crosses', 'save_crosses_title_length', 10, 2 );
function save_crosses_title_length( $post_id, $post) {
  // Check to see if we are autosaving
  if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE){
    return;
  }
  $length = strlen( trim($post->post_title) );// get the length of title
  //udpate the meta field, in case the post title is being updated
  update_post_meta( $post_id, 'name_length_crosses', $length );
  //note, no need to add_post_meta, udpate_post_meta fn will add the field if not found.
}

For the front-end display,

$crossposts = get_posts(array(
    'post_type' => 'crosses',
    'meta_key'  => 'name_length_crosses',
    'orderby'   => 'meta_value_num',
    'order'     => 'ASC'
));
if ( $crossposts ) {
    foreach ( $crossposts as $post ) :
        setup_postdata( $crossposts ); ?>
        <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
        <?php the_content(); ?>
    <?php
    endforeach; 
    wp_reset_postdata(); //Important, reset the query post data.
}
  • So I tried this one and the functions worked fine, added the custom field values and everything. The front end display though isn't working so well. It may have to do with how my search.php is set up in the first place. I'm using <?php if(isset($_GET['post_type'])) { $type = $_GET['post_type']; if($type == 'crosses') {?> with the loop inside and then another for a second post type, all on the same page, so that different results are called/formatted depending on which of two search forms are used to call the search.php. May have to look into a different method. – Aaron McNatt Apr 10 '18 at 18:57
  • glad it worked. For the search, its probably to execute a default search on non-crosses posts and a custom one on crosses. – Aurovrata Apr 11 '18 at 8:03
0

You can try this code. I think this is the best way to sort them. Actually, you need to sort the $posts array before using it in foreach.

$args = array(
    'post_type' => 'crosses', // Only get the posts
    'post_status' => 'publish', // Only the posts that are published
    'posts_per_page'   => -1 // Get every post
);
$posts = get_posts($args);

//This funtion is for sorting the array, depends on post's title length
function post_title_length_sort($a, $b){
    return strlen($a->post_title)-strlen($b->post_title);
}
usort($posts, 'post_title_length_sort');

//You cam check your array now
//Now $posts is sorted by function "post_title_length_sort"
//and you can use the $posts array in a foreach

//print_r($posts);  for checking the array

foreach ( $posts as $post ) {

    //Your code goes here...
}

Try the code , then let me know the result.

0

Hi @Aaron you can use just simple MySQL query and the CHAR_LENGTH() function;)

Hooking the posts_orderby filter would be the fastest way to achieve the desired result without saving any meta-field by simply customising the default front-end query for the 'crosses' post type. However, note that this would actually modify all front-end queries,

add_filter('posts_orderby', 'modify_crosses_query', 10, 2);
function modify_crosses_query($orderby_statement, $wp_query){
  if(is_admin()){ //admin queries.
    return $orderby_statement;
  }
  global $wpdb;
  // Verify correct post type, or any other query variable
  if ($wp_query->get("post_type") === "crosses") {
    $orderby_statement = 'ORDER BY CHAR_LENGTH('.$wpdb->posts.'.post_title) ASC';
  }
}
  • Ended up going with this method. Thank you so much! – Aaron McNatt Apr 10 '18 at 18:01
  • I spoke too soon. I thought it was working fine cause they were sorted on on page one, but I'm using pagination and it didn't carry over to the other pages. – Aaron McNatt Apr 10 '18 at 18:24
  • this is a filter based approach, so keep in mind that any queries which bypass filters will not work, such as get_posts queries which are by default suppressing filters. Similarly, a theme might be using the remove_all_filters function when paginating results. – Aurovrata Apr 11 '18 at 7:44
0

Add the following to your functions.php for the on save. This will create a meta value on save that stores the length of the post title.

function save_crosses_title_length( $post_id ) {

  $title = get_the_title($post_id);//get the title
  $length = strlen( $title );// get the length of title

  if ( get_post_meta( $post_id, 'name_length_crosses' ) ) {//if meta value exists
    update_post_meta( $post_id, 'name_length_crosses', $length );//update meta value
    return;
  }

  add_post_meta( $post_id, 'name_length_crosses', $length );//else create meta value

}

add_action( 'save_post_crosses', 'save_crosses_title_length');

For actually querying the posts.. Use the following. You can learn more about WP_Query here: https://codex.wordpress.org/Class_Reference/WP_Query

$args = array(
    'post_type' => 'crosses',
    'meta_key'  => 'name_length_crosses',
    'orderby'   => 'meta_value_num',
    'order'     => 'ASC'
);

$custom_query = new WP_Query( $args );


while ( $custom_query->have_posts() ) :
   $custom_query->the_post();

   echo '<p>' . the_title() . '</p>';

endwhile;

If you like this approach I can build you a simple loop to run to update the existing posts.

  • Hooking on the save_post will only work for new posts. It will not solve the question of existing posts. – Aurovrata Apr 10 '18 at 7:29
  • @Aurovrata if you read the bottom of my post you will see I mention if they like this approach I can provide a loop to update existing posts. – Liam Stewart Apr 10 '18 at 17:09
  • true, but the reverse would have been more appropriate, provide a solution for updating existing posts, which is what is being attempted, and then suggest a solution for future posts. To a beginner, your approach is rather confusing. – Aurovrata Apr 11 '18 at 7:55
  • note also that you code has some errors which will result in a fatal php error. – Aurovrata Apr 11 '18 at 8:06
  • @Aurovrata What snippet do you receive the error and what is the error. – Liam Stewart Apr 12 '18 at 1:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.