Ask your WordPress questions! Pay money and get answers fast! Comodo Trusted Site Seal
Official PayPal Seal

Title Duplication Warning WordPress

  • SOLVED

Hello,

As you know, WordPress automatically adds suffix in the end of permalink (post name) if a post with the same name already exists in the database.

Example:
I create: http://www.mywebsite.com/mypost
Then try to create with same title, it goes: http://www.mywebsite.com/mypost-2

I don't want to put a suffix, instead i need it to give a warning to the user saying that "the title you are trying to create is already published" . It would be nice, if this warning comes on another page and after clicking ok sends the user to the post-edit page again..

I will use this mechanism, in order to have unique titles in my database and prevent duplicating.

*There is an old "singular" plug-in which have a similar code to what i want. But it just deletes the new one without saying anything.

Any suggestions? I think i need a custom code here..

Answers (4)

2011-04-16

AdamGold answers:

Try to add this to your functions.php:
function check_post_uniqueness($id)
{
global $wpdb;

$post_title = $wpdb->get_var( $wpdb->prepare("SELECT post_title FROM {$wpdb->posts} WHERE ID = '{$id}' LIMIT 1") );

$wpdb->query("SELECT ID FROM {$wpdb->posts} WHERE post_title = '{$post_title}' LIMIT 1");

if ( $wpdb->num_rows > 0 ) {
echo 'A post with this title already exists.';
}
return $id;
}

add_action('publish_post', 'check_post_uniqueness', 8);
add_action('edit_post', 'check_post_uniqueness', 8);


Onur YILMAZ comments:

I think, answer should be something like this but this code gives the error below regardless of its duplicated or not:

A post with this title already exists.
Warning: Cannot modify header information - headers already sent by (output started at /home/xxx/public_html/yyy/wp-content/themes/zzz/functions.php:46) in /home/xxx/public_html/yyy/wp-includes/pluggable.php on line 897


* i have replaced my real attributes with xxx, yyy and zzz. You can still get the message.

Did you try this on your own? Is it related with my theme? (it is a woo theme: Skeptical)


AdamGold comments:

Yes, It is because I echo the error message. Please try the following (I'm not sure it will work, if you are ok with that please give me your FTP details and I will sort this out)

function check_post_uniqueness($id)

{

global $wpdb;



$post_title = $wpdb->get_var( $wpdb->prepare("SELECT post_title FROM {$wpdb->posts} WHERE ID = '{$id}' LIMIT 1") );



$wpdb->query("SELECT ID FROM {$wpdb->posts} WHERE post_title = '{$post_title}' LIMIT 1");



if ( $wpdb->num_rows > 0 ) {

return 'A post with this title already exists.';

}

return $id;

}



add_action('publish_post', 'check_post_uniqueness', 8);



AdamGold comments:

Sorry, forgot to add a line of code:

function check_post_uniqueness($id) {
global $wpdb;

$post_title = $wpdb->get_var( $wpdb->prepare("SELECT post_title FROM {$wpdb->posts} WHERE ID = '{$id}' LIMIT 1") );

$wpdb->query("SELECT ID FROM {$wpdb->posts} WHERE post_title = '{$post_title}' LIMIT 1");

if ( $wpdb->num_rows > 0 ) {
return 'A post with this title already exists.';
}
return $id;
}

add_action('publish_post', 'check_post_uniqueness', 8);
add_action('edit_post', 'check_post_uniqueness', 8);


Onur YILMAZ comments:

This time it doesn't have an effect.

You can tell me where to place or which file to change. (I have a sort of understanding, not that bad :)


AdamGold comments:

Okay. Try the first code, and put ob_start(); right after your <?php tag in functions.php, like this:

<?php
ob_start();


BTW, are you sure line 46 in functions.php is:

echo 'A post with this title already exists.';


Onur YILMAZ comments:

When i added ob_start() now it doesnt give error message but it doesn't have an effect either. It still lets to create title-2 or title-3 etc.

And i am sure line 46 includes that echo message.


AdamGold comments:

Did you use this code with ob_start(); ?
function check_post_uniqueness($id)
{
global $wpdb;
$post_title = $wpdb->get_var( $wpdb->prepare("SELECT post_title FROM {$wpdb->posts} WHERE ID = '{$id}' LIMIT 1") );

$wpdb->query("SELECT ID FROM {$wpdb->posts} WHERE post_title = '{$post_title}' LIMIT 1");

if ( $wpdb->num_rows > 0 ) {

echo 'A post with this title already exists.';

}
return $id;

}
add_action('publish_post', 'check_post_uniqueness', 8);

add_action('edit_post', 'check_post_uniqueness', 8);


Onur YILMAZ comments:

Sure, it was the first code you've sent and i used it


AdamGold comments:

Try this:
1. Remove ob_flush();
2. Replace the echo part:
echo 'A post with this title already exists.';
with:
return new WP_Error('invalid_post', __('A post with this title already exists.'));


Onur YILMAZ comments:

i cant find ob_flush()

Where do you think it exists?


AdamGold comments:

Sorry, ob_start();


Onur YILMAZ comments:

no effect :(

It still lets duplicates to be created..


AdamGold comments:

Okay I found an alternative solution. Go to wp-includes/post.php and find:

if ( $post_name_check || in_array( $slug, $feeds ) || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
$suffix = 2;


Replace with:
if ( $post_name_check || in_array( $slug, $feeds ) || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
return;


I know it's not the nicest solution but this is working :)


AdamGold comments:

Nope, not working yet. This just prevents adding the number to the URL. Sorry.


Onur YILMAZ comments:

yeah not working :(


AdamGold comments:

Sorry, out of ideas. I found a way to prevent duplicate titles like this:
if( str_replace("-pleasedontchangeuniqueduplicatetitle", "", $post_name) ) {
$error = new WP_Error('duplicate_post_title', __('Duplicate post title, Please change it.'));
return $error;
}

With some changes to the wp_unique_post_slug function but haven't found a way to display a message because you are redirected right after the save.


AdamGold comments:

Last try:
function check_post_uniqueness($id) {
global $wpdb;

$prefix = $wpdb->prefix;

$wpdb->query("DELETE bad_rows . * FROM ".$prefix."posts AS bad_rows INNER JOIN (
SELECT ".$prefix."posts.post_title, MIN( ".$prefix."posts.ID ) AS min_id
FROM ".$prefix."posts
GROUP BY post_title
HAVING COUNT( * ) >1
) AS good_rows ON ( good_rows.post_title = bad_rows.post_title
AND good_rows.min_id <> bad_rows.ID )");

$post_title = $wpdb->get_var( $wpdb->prepare("SELECT post_title FROM {$wpdb->posts} WHERE ID = '{$id}' LIMIT 1") );
$wpdb->query("SELECT ID FROM {$wpdb->posts} WHERE post_title = '{$post_title}' LIMIT 1");
if ( $wpdb->num_rows > 0 ) {
echo 'A post with this title already exists';
return 0;
}

return $id;
}

add_action('publish_post', 'check_post_uniqueness');
add_action('edit_post', 'check_post_uniqueness');


Onur YILMAZ comments:

headers already sent error :(


AdamGold comments:

I know, but this is the only way to achieve that kind of thing. It works (avoid the error), right? The post isn't being created and the error is being showed. There's a dirty way to remove the error - Go to pluggable.php line 897 and add @ before header.
@header(


AdamGold comments:

Actually it is being created just erased right after. I can't think of another way to achieve this.


AdamGold comments:

Okay, so according to the link Peter posted, HERE IS THE FINAL SOLUTION, AND IT'S WORKING:

function check_post_uniqueness($id) {
global $wpdb;

$post_title = $wpdb->get_var( $wpdb->prepare("SELECT post_title FROM {$wpdb->posts} WHERE ID = '{$id}' LIMIT 1") );

$wpdb->query("SELECT ID FROM {$wpdb->posts} WHERE post_title = '{$post_title}' LIMIT 1");

if ( $wpdb->num_rows == 1 ) {
update_option('check_post_uniqueness_notice', 'A post with this title already exists.');
update_option('display_post_uniqueness_notice', true);
$prefix = $wpdb->prefix;

$wpdb->query("DELETE bad_rows . * FROM ".$prefix."posts AS bad_rows INNER JOIN (

SELECT ".$prefix."posts.post_title, MIN( ".$prefix."posts.ID ) AS min_id

FROM ".$prefix."posts

GROUP BY post_title

HAVING COUNT( * ) >1

) AS good_rows ON ( good_rows.post_title = bad_rows.post_title

AND good_rows.min_id <> bad_rows.ID )");
}
return $id;
}
add_action('publish_post', 'check_post_uniqueness');
add_action('edit_post', 'check_post_uniqueness');


add_action('admin_head', 'add_plugin_notice');
function add_plugin_notice() {
if( get_option('display_post_uniqueness_notice') ) {
add_action('admin_notices' , create_function( '', "echo '<div class=\'updated below-h2\' id=\'message\'>" . get_option('check_post_uniqueness_notice') . "</div>';" ) );
update_option('display_post_uniqueness_notice', false);
}
}

If you can, please raise you prize because I worked kinda hard on this one :)


Onur YILMAZ comments:

Thank you for contributing everyone,

I thought this would be an easy one, but seems not :(

Adam tried hard! Even though, we couldn't have a succesful result, i think he deserved the price (he was very close though)

Regards,

2011-04-16

Christianto answers:

Hi Onur,

Please try this

1. Copy paste below code to function.php

// include script on admin panel
function onur_js(){
wp_register_script( 'onur', get_template_directory_uri().'/onur.js');
wp_enqueue_script( 'onur' );
}
add_action('admin_print_scripts','onur_js');

// db checking
function onur_checkslug(){
if($_POST['type'] == 'checkslug'){

// convert space to '-'
$title = str_replace(' ','-',$_POST['title']);
// use wp_query to check if givin post exists
if($_POST['post_type'] = 'post'){
// if this post
$check_post = new WP_Query("name=$title");
} else {
// if this page
$check_post = new WP_Query("pagename=$title");
}
if ( $check_post->have_posts() ) :
echo 'duplicate';
else:
echo 'no-duplicate';
endif;

} else {

echo 'error';

}
die;
}
add_action('wp_ajax_checkslug', 'onur_checkslug');


// remove suffix
function remove_unique_slug($permalink){
$clean_permalink = preg_replace("/\-\d+$/",'', $permalink);
return $clean_permalink;
}
add_filter('editable_slug','remove_unique_slug');


2. create js file onur.js place it on your active theme directory. Copy paste code below to it.

/* livequery */
(function(a){a.extend(a.fn,{livequery:function(e,d,c){var b=this,f;if(a.isFunction(e)){c=d,d=e,e=undefined}a.each(a.livequery.queries,function(g,h){if(b.selector==h.selector&&b.context==h.context&&e==h.type&&(!d||d.$lqguid==h.fn.$lqguid)&&(!c||c.$lqguid==h.fn2.$lqguid)){return(f=h)&&false}});f=f||new a.livequery(this.selector,this.context,e,d,c);f.stopped=false;f.run();return this},expire:function(e,d,c){var b=this;if(a.isFunction(e)){c=d,d=e,e=undefined}a.each(a.livequery.queries,function(f,g){if(b.selector==g.selector&&b.context==g.context&&(!e||e==g.type)&&(!d||d.$lqguid==g.fn.$lqguid)&&(!c||c.$lqguid==g.fn2.$lqguid)&&!this.stopped){a.livequery.stop(g.id)}});return this}});a.livequery=function(b,d,f,e,c){this.selector=b;this.context=d;this.type=f;this.fn=e;this.fn2=c;this.elements=[];this.stopped=false;this.id=a.livequery.queries.push(this)-1;e.$lqguid=e.$lqguid||a.livequery.guid++;if(c){c.$lqguid=c.$lqguid||a.livequery.guid++}return this};a.livequery.prototype={stop:function(){var b=this;if(this.type){this.elements.unbind(this.type,this.fn)}else{if(this.fn2){this.elements.each(function(c,d){b.fn2.apply(d)})}}this.elements=[];this.stopped=true},run:function(){if(this.stopped){return}var d=this;var e=this.elements,c=a(this.selector,this.context),b=c.not(e);this.elements=c;if(this.type){b.bind(this.type,this.fn);if(e.length>0){a.each(e,function(f,g){if(a.inArray(g,c)<0){a.event.remove(g,d.type,d.fn)}})}}else{b.each(function(){d.fn.apply(this)});if(this.fn2&&e.length>0){a.each(e,function(f,g){if(a.inArray(g,c)<0){d.fn2.apply(g)}})}}}};a.extend(a.livequery,{guid:0,queries:[],queue:[],running:false,timeout:null,checkQueue:function(){if(a.livequery.running&&a.livequery.queue.length){var b=a.livequery.queue.length;while(b--){a.livequery.queries[a.livequery.queue.shift()].run()}}},pause:function(){a.livequery.running=false},play:function(){a.livequery.running=true;a.livequery.run()},registerPlugin:function(){a.each(arguments,function(c,d){if(!a.fn[d]){return}var b=a.fn[d];a.fn[d]=function(){var e=b.apply(this,arguments);a.livequery.run();return e}})},run:function(b){if(b!=undefined){if(a.inArray(b,a.livequery.queue)<0){a.livequery.queue.push(b)}}else{a.each(a.livequery.queries,function(c){if(a.inArray(c,a.livequery.queue)<0){a.livequery.queue.push(c)}})}if(a.livequery.timeout){clearTimeout(a.livequery.timeout)}a.livequery.timeout=setTimeout(a.livequery.checkQueue,20)},stop:function(b){if(b!=undefined){a.livequery.queries[b].stop()}else{a.each(a.livequery.queries,function(c){a.livequery.queries[c].stop()})}}});a.livequery.registerPlugin("append","prepend","after","before","wrap","attr","removeAttr","addClass","removeClass","toggleClass","empty","remove");a(function(){a.livequery.play()})})(jQuery);
jQuery(document).ready(function($){
var titledata, duplicatemessage = "_________________________________________________\nWarning!\n\nPermalink of this content is already use by other post\nPlease edit the permalink before publish.\n_________________________________________________",
checkdb = function(){
var uri = window.location.toString();
if(uri.match(/&action=edit/)) return;
$.post(ajaxurl , titledata, function(message){
switch(message){
case 'duplicate':
alert(duplicatemessage);
$('#save-post,#publish').css('display','none');
break;
case 'no-duplicate':
$('#save-post,#publish').css('display','block');
break;
}
});
};
$('#edit-slug-buttons .save').livequery(function(){
$('#edit-slug-buttons .save').click(function(){
titledata = { type: 'checkslug', action: 'checkslug', title: $('#new-post-slug').val(), post_type: typenow };
checkdb();
});
});
$('#title').change(function(){
titledata = { type: 'checkslug', action: 'checkslug', title: $(this).val(), post_type: typenow };
checkdb();
})

});


Since the suffix added from title of new post this script will work on new post only.
It will check and alert you if similar slug exists, hide the publish and save draft button preventing any damage to existing post, until the permalink edited to other slug. You can have similar title but not permalink/slug.

But please notice since I use regular pattern to replace default suffix, if you have new post and using '-' + number at the end (for example 'My title-12') the number wont be included in permalink automatically(should edited manually).

Hope this help.
Christianto


Christianto comments:

Have you tried my code?

:-( sad mode:

My code works but in other way..
My code didn't 'redirect' to a page saying "the title you are trying to create is already published" and put ok button to back to editor. But it warning the user if they finish type a title that wp begin to generate slug.


Christianto comments:

Oh sorry,

Someone already try it... Thanks Denzel

:-) smile again


Onur YILMAZ comments:

Christiano,

I am just working on your solution. However i do not get any effect.

I created js file and put the code in functions.php

I have put onur.js under my active theme's root directory as well as , js folder under functions (tried some places) But do not get any effect..

I am losing my hopes :(


Christianto comments:

Is there any error on your browser error console, or show on wordpress itself?

Please try to type title that has similar slug, and move the focus of title field to somewhere else, editor for example..

Like Denzel said, my solution not perfect, But I'm glad if you manage to get it work in your wp installation..


Christianto comments:

Or if you want PM me your site, temporary username/pass so I can see the problem.
I will check it tommorrow, it late night here.. hope u understand


Christianto comments:

Hi Onur, Please PM me your site and temporary username/pass. I will check it now.

2011-04-16

Denzel Chia answers:

Hi,

This is my reply to your email requesting me to answer this question.
<strong>I did not answer this question because I think it cannot be done.
I may be wrong, but these are my reasons.</strong>

Open up your post.php under wp-includes of your WordPress core code,
you will find a function wp_unique_post_slug() at line 2771.
This is the function that is adding the suffix if permalink duplicates.
This function is being used in wp_insert_post()

There is no action or filter for you to replace it or add your own rule,
excerpt 3 filters in the if condition check. But they are being put at the last condition.
They are there for you to add more conditions and not to overwrite the suffix adding.
so if there is a duplicated permalink, the first condition of the if condition check will be met and the last filter gets ignored and a suffix is added to the permalink.

Cristiano, provided the closes working solution, although his solution does not prevent the creation of a suffix draft copy of the post, but his jQuery script did create an alert box warning and prevent user from publishing unless the user changes to a new slug.
But his answer is still not perfect, because you are still able to bypass it, by editing the draft copy and publish it, the suffix will still be added to the permalink.

Having said so much, I think eventually you will ask for a refund after wasting everybody's effort here. But I am sure someone will challenge your refund request, because I think it is only fair to reward those that put in their effort to answer your "cannot be achieved" question.

Thanks.
Denzel


Onur YILMAZ comments:

Dear Denzel,

First thanks for your answer. To be fair, i also think that i should give the money who gives the most effort regardless of a working solution or not.

However, i don't think it should be that "impossible". There is a plugin code (Duplicate Posts Eraser - Author: BlackMelvyn) Deletes duplicate posts based on their title

Which is very near what i need. Take a look at it:

-----------------------------

function clearDuplicatePosts(){
global $wpdb;
$prefix = $wpdb->prefix;

$wpdb->query("DELETE bad_rows . * FROM ".$prefix."posts AS bad_rows INNER JOIN (
SELECT ".$prefix."posts.post_title, MIN( ".$prefix."posts.ID ) AS min_id
FROM ".$prefix."posts
GROUP BY post_title
HAVING COUNT( * ) >1
) AS good_rows ON ( good_rows.post_title = bad_rows.post_title
AND good_rows.min_id <> bad_rows.ID )");
}
add_action('publish_post', 'clearDuplicatePosts');

--------------------------

But as far as i understand this code searched the database and deletes the duplicates everytime a post is published. It is also "a bit" ok. But it would be fine to inform the user that he just entered a duplicated entry.



Denzel Chia comments:

The function you provided works because the post is already created, if not how does the query delete duplicated post from the database? The post needs to be created first right?
So this hook definitely runs after the draft post creation.

Therefore I think, modifying it to check before a post gets duplicated and post a warning will not work. Because "publish_post" hook runs after draft post is created.

If you notice that after you written a post title and position your cursor into the tinymce editor content area, the slug gets automatically created? It is because clicking on the tinymce content area actually triggers the AJAX and a draft post gets created instantly with a suffix slug.

Unless you find a way to hook into or modify this AJAX, you can never created a warning before a draft post with suffix slug is created.

I cannot come up with a jQuery solution, because I am poor in this language.
But I think you can stop wasting your time finding a pure PHP solution.

But well, I may be wrong.

2011-04-17

Peter Michael answers:

Possible solution from wamitchell at [[LINK href="http://wordpress.org/support/topic/admin_notices-on-save_post"]]http://wordpress.org/support/topic/admin_notices-on-save_post[[/LINK]]

HTH