Hey guys I'm relatively new to drupal and have a quick question. Does anyone know how to deny duplicate node titles? I am making a website to review different kinds of food and I don't want users to double post the same food. I am using cck for my "food" node. When the user goes to add a new food I want an error to pop up and deny them submission if the food name already exists. Any modules for this? I'm a noob so I need pretty clear instructions...thanks guys!

Comments

Jonathan Bell’s picture

If the food already exists, redirect them to the page. Popup denials makes the user feel like they've done something wrong.

collegegirl’s picture

how do i make it so the system recognizes the dupe? right now if the food apple exists, you can enter in the food apple again. then there will be two foods with the same name...i just don't want dupes.

nevets’s picture

You could write a small module that implement the nodeapi hook and have it handle the validate case. The code for would look something like (I have called the module noduptitles).

<?php
function noduptitles_nodeapi(&$node, $op) {

	if ( $op == 'validate' ) {
		// We only care about nodes of type 'desired type'
		$type = 'story';  // Change this to the type you want to restrict titles on
		if ( $node->type == $type ) {
		  $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
		  $results = db_query($sql, $type, $node->title);
		  $existing = db_fetch_object($results);
		  // We get here on both inserts and updates
		  // For updates we want to make sure the title does not match another one
		  // So we make sure it is an insert ( ! $node->nid )
		  // or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
		  if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->title == $node->title ) {
		    $link = l(t('existing %type', array('%type' => $type)), "node/$existing->nid");
		    form_set_error('title', t('There is already a %type called %title, see %link.', array('%type' => $type, '%title' => $node->title, '%link' => $link)));
		  }
		}
	}
}

Under the modules directory create a directory called noduptitles, in noduptitles create a file called noduptitles.module and place the code above in the module. You will need to change the type you want to restrict duplicate titles on. Enable the module and you should be good to go (tested under 4.7.3)

collegegirl’s picture

thank you so much...worked perfectly. didn't work first time b/c i defined my type as "food" instead of "content_food" i'm guessing that has something to do with cck. anyways, thanks a lot, and you should post this module somewhere. i spent hours trying to find it.

mbottorff’s picture

Found this in a search, looked like exactly what I wanted, but It didn't seem to be working for me.
Realized after the fact that it's a 4.7 fix, and I'm running 5.x, duh.
What would need to be done differently to run it under 5.x?

nevets’s picture

Other than the need to add a .info file it should work as is. What sort of problem are you having?

mbottorff’s picture

Thanks for being willing to help me with this.
Sorry it took so long to respond, I haven't been feeling well.

The problem is that I attempted to follow your instructions, and yet the uploaded "module" does not appear in my modules list to be activated.

Is that because it needs an .info file?
What would an .info file look like?

nevets’s picture

Place the following in noduptitles.info

name = No Duplicate Titles
description = Prevents duplicate titles for selected content type
coupet’s picture

Excellent validation code.

----
Darly

greencheetah’s picture

This module is great! Thanks.

Does anyone know how I can get rid of this?

See %link.', array('%type' => $type, '%title' => $node->title, '%link' => $link)));

I really don't need a link to the existing node, and the error response link does not work.

AndreasST’s picture

I created the .module and the .info for 5.x, loaded them up and activated the module.
... but nothing happens when i create a duplicate node of the type i want to restrict.
Do i have to put the 'validate' anywhere?

nevets’s picture

After this line

 $type = 'story';  // Change this to the type you want to restrict titles on

add

drupal_set_messge("Type is $node->type");

which will list the node type for each node viewed to make sure you have $type set correctly.

AndreasST’s picture

... all i see is "Type is" and nothing more. It doesn´t matter what type of node i´m viewing.

nevets’s picture

Can you either post the version of the code you are using (or email using my contact form). There is nothing in the original code that would explain $node->type being empty.

AndreasST’s picture

<?php
function noduptitles_nodeapi(&$node, $op) {

if ( $op == 'validate' ) {
// We only care about nodes of type 'desired type'
$type = 'story';  // Change this to the type you want to restrict titles on
drupal_set_message("Type is $node->type");
if ( $node->type == $type ) {
  $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
  $results = db_query($sql, $type, $node->title);
  $existing = db_fetch_object($results);
  // We get here on both inserts and updates
  // For updates we want to make sure the title does not match another one
  // So we make sure it is an insert ( ! $node->nid )
  // or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
  if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->title == $node->title ) {
    $link = l(t('existing %type', array('%type' => $type)), "node/$existing->nid");
    form_set_error('title', t('There is already a %type called %title, see %link.', array('%type' => $type, '%title' => $node->title, '%link' => $link)));
  }
}
}
}
nevets’s picture

You have the code above in a file called noduptitles.module in a directory called noduptitles, have added a file noduptitles.info and enabled the module? Is that correct?

You are creating nodes from the user interface, 'Create Content' -> 'Story'?

I ask because the code works fine for me, after if ( $op == 'validate' ) { try adding

drupal_set_message("NODE: <pre>" . print_r($node, TRUE) . '</pre>');

so we can see what the node looks like (it is odd that title is not set).

AndreasST’s picture

Yes, yes, yes, yes. Correct!
Yes.
Code added, nothing more to see than before.

nevets’s picture

do you mean nothing prints after the word 'NODE'? That would imply that nodeapi is being called either incorrectly or from somewhere other than the node module. It gets called through node_invoke_nodeapi() which the only call I can find for the validate case is in node_validate() (calls at end of function). You could add a drupal_set_message("Validate: <pre>" . print_r($node, TRUE) . '</pre>'); to see what $node looks like at that point.

dindon’s picture

can we make this a module?

ZrO-1’s picture

hey guys, I want to start-off by saying how happy I am that this code is here. I am working on a project where I want to allow all of my users to post particular content types, but really cannot have any dupes in the database. I think this bit of code will go a long way in getting that capability. Thank you AndreasST for posting the original code. I'm commenting because I am hoping you guys can check the switch-case I added into the original function to have it check multiple types of nodes. If it is good, I also hope that others will be able to use it. So here's the modified function:

<?php
function noduptitles_nodeapi(&$node, $op) {
  if ( $op == 'validate' ) {
    // We only care about nodes of type 'desired type'
    switch ($type) {
      case 'content_type_provider' // the type you want to restrict titles on
        if ( $node->type == $type ) {
          $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
          $results = db_query($sql, $type, $node->title);
          $existing = db_fetch_object($results);
          // We get here on both inserts and updates. For updates we want to make sure the title does not match another one
          // So we make sure it is an insert ( ! $node->nid ) or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
          if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->title == $node->title ) {
            $link = l(t('existing %type', array('%type' => $type)), "node/$existing->nid");
            form_set_error('title', t('There is already a %type called %title, see %link.', array('%type' => $type, '%title' => $node->title, '%link' => $link)));
          }
        }
        break;
        
      case 'content_type_show' // the type you want to restrict titles on
        if ( $node->type == $type ) {
          $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
          $results = db_query($sql, $type, $node->title);
          $existing = db_fetch_object($results);
          // We get here on both inserts and updates. For updates we want to make sure the title does not match another one
          // So we make sure it is an insert ( ! $node->nid ) or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
          if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->title == $node->title ) {
            $link = l(t('existing %type', array('%type' => $type)), "node/$existing->nid");
            form_set_error('title', t('There is already a %type called %title, see %link.', array('%type' => $type, '%title' => $node->title, '%link' => $link)));
          }
        }
        break;
        
      case 'content_type_episode' // the type you want to restrict titles on
        if ( $node->type == $type ) {
          $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
          $results = db_query($sql, $type, $node->title);
          $existing = db_fetch_object($results);
          // We get here on both inserts and updates. For updates we want to make sure the title does not match another one
          // So we make sure it is an insert ( ! $node->nid ) or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
          if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->title == $node->title ) {
            $link = l(t('existing %type', array('%type' => $type)), "node/$existing->nid");
            form_set_error('title', t('There is already a %type called %title, see %link.', array('%type' => $type, '%title' => $node->title, '%link' => $link)));
          }
        }
        break;
    }
  }
}
?>

Also, is there a way to check on multiple fields and not just the title?
Thanks in advance for any help here.

nevets’s picture

You have a switch on $type which is not set. I think you want your code structured like this

function noduptitles_nodeapi(&$node, $op) {
  if ( $op == 'validate' ) {
    // We only care about nodes of type 'desired type'
    switch ($node->type) {
      case 'content_type_provider': // the type(s) you want to restrict titles on
      case 'content_type_show':
      case 'content_type_episode':
          $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
          $results = db_query($sql, $node->type, $node->title);
          $existing = db_fetch_object($results);
          // We get here on both inserts and updates. For updates we want to make sure the title does not match another one
          // So we make sure it is an insert ( ! $node->nid ) or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
          if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->title == $node->title ) {
            $link = l(t('existing %type', array('%type' => $type)), "node/$existing->nid");
            form_set_error('title', t('There is already a %type called %title, see %link.', array('%type' => $node->type, '%title' => $node->title, '%link' => $link)));
          }
        break;
    }
  }
}

All the types can use the same code so there is no need to replicate that, we just make sure we use $node->type where the original code used $type.

As for using other fields it is possible but that will generally mean content type specific table and tests.

ZrO-1’s picture

Thank you for your help nevets. I'm quite new to PHP and I still am struggling along. I've seen your snippets in several threads I've been using to get my site the way I would like it. You are an awesome person. Thank you so much for helping all of us noobs out.

Tpainton-1’s picture

Great module!! I have been wanting this and it works except for one simple thing that I am sure anyone with marginal php/drupal experience can solve.. I am using the original code. When I do try to enter a dupe the error message given to user looks like this...

There is already a bar called Test, see <a href="/biz/node/4">existing &lt;em&gt;bar&lt;/em&gt;</a>.

It appears that the link just isnt working.. I'm using Drupal 5.

Any help greatly appreciated.

jbhan’s picture

can this be tweaked to do a slightly different validation?

I have similar issues. However, my site allows users to post links to other sites. I want to check for duplicates by checking one of the fields in the node being submitted against the content in the db fields.

so, instead of checking the node titles, i am checking a DB field. can i use this same code? where do i make the substitutions?

thanks!

j

nevets’s picture

You would need to change

 $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
$results = db_query($sql, $type, $node->title);

So it uses the table with the link data (instead of the node table), return the nid and link field from the table (instead of n.title and n.nid). Then it would need to change the WHERE clause to compare using the link field. If you want to restrict the type as in the example, instead of changing the table you would need to do a join with your other table. Lastly in db_query you would need to change $node->title to the link field (and possibly drop $type if you drop it from there WHERE clause

Using link as the field name and my_table as the table name with the link field, keeping the type check it would look something like

 $sql = "SELECT m.link, n.nid FROM {node} n JOIN {my_table} m USING(nid) WHERE type = '%s' AND link = '%s'";
$results = db_query($sql, $type, $node->link);
shimamoto’s picture

I'm trying to get the above code to work, but I could use a little help.

I used CCK and the link field-type module to create a custom URL field for my Drupal forum submissions. I don't want people submitting duplicate URLs in this field. The URL is stored as the field "field_map_link_url" in the table "content_type_forum" of the MySQL database.

I've tried modifying the above code, but I can't get it to work. No matter what I enter in the URL field I get an error saying it's a duplicate. I think I'm screwing up the database query. This is my first time trying to write a custom module. I'd really appreciate if someone could show me how to get the code right. Thanks.

nevets’s picture

You should be able to change the sql and instances of 'title' to 'field_map_link_url'. For the sql change

  $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";

to

  $sql = "SELECT f.field_map_link_url, n.nid FROM {node} n JOIN {content_type_forum} f  USING(nid) WHERE type = '%s' AND field_map_link_url = '%s'";

(This assumes field_map_link_url is a string)

Then change instances of 'title' to 'field_map_link_url' and you should be good to go.

shimamoto’s picture

I'm still getting the error message no matter what I enter as a url. Also, the error message is blank where the URL variable is supposed to be. Do I need to change anything else because field_map_link_url is under the content_type_forum table instead of the node table? In phpmyadmin the field_map_link_url has a type of varchar(255).

Here's my code:

<?php
function noduplinks_nodeapi(&$node, $op) {

if ( $op == 'validate' ) {
// We only care about nodes of type 'desired type'
$type = 'forum';  // Change this to the type you want to restrict titles on
if ( $node->type == $type ) {
  $sql = "SELECT f.field_map_link_url, n.nid FROM {node} n JOIN {content_type_forum} f  USING(nid) WHERE type = '%s' AND field_map_link_url = '%s'";
  $results = db_query($sql, $type, $node->field_map_link_url);
  $existing = db_fetch_object($results);
  // We get here on both inserts and updates
  // For updates we want to make sure the title does not match another one
  // So we make sure it is an insert ( ! $node->nid )
  // or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
  if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->field_map_link_url == $node->field_map_link_url ) {
    $link = l(t('existing %type', array('%type' => $type)), "node/$existing->nid");
    form_set_error('title', t('There is already a %type called %field_map_link_url, see %link.', array('%type' => $type, '%field_map_link_url' => $node->field_map_link_url, '%link' => $link)));
  }
}
}
}
nevets’s picture

The fact the field is blank is why the test alway fails, the query matches nothing and the result is to compare two empty strings.

Is the 'field_map_link_url' required or optional, if optional you will want to change if ( $node->type == $type ) { to if ( $node->type == $type && trim($node->field_map_link_url) ) { so the check only runs if the url is provided.

The other thing to do is check that the field name is correct and showing up as part of $node, right before $sql = ... add the line

drupal_set_message('<pre>' . print_r($node,  TRUE) . '</pre>');

which will show all of the fields for the node in the message area.

shimamoto’s picture

The URL field is required for new submissions, but there are older ones that don't have it.

I added the two segments of code above and now it's not giving me an error, but it's not detecting duplicates either. I'm not seeing anything from the drupal_set_message?

I got the name of the field and the table it's in from phpMyAdmin. BTW- only new forum topics with this URL field end up with their nid in the "content_type_forum" table. The "field_map_link_url" field is in that table and not the "node" table. Thanks for all the help.

nevets’s picture

Try moving the call to drupal_set_message so it is before the 'if' test on the content type. You might also change the call to something like

drupal_set_message('Start Node:<pre>' . print_r($node, TRUE) . '</pre>End Node');

to insure something will print as long as drupal_set_message() is called.

shimamoto’s picture

I entered Google as the URL and got a bunch of info including:

    [field_map_link] => Array
        (
            [0] => Array
                (
                    [url] => http://www.google.com
                    [attributes] => N;
                )

        

Apparently, it's an array. Do I need to be referencing the field differently?

nevets’s picture

From you description so far it sounds like there is only one instance of the field so you can reference it as $node->field_map_link[0]['url'].

shimamoto’s picture

I finally got it working perfectly. I appreciate the help.

bsuttis’s picture

Do you mind posting your finished code for your custom cck field?

I've been messing about with this and can't seem to get it to work, if I could see your finished version, it'll very likely help me a lot, thanks.

bsuttis’s picture

This is my code - I've got a custom Integer field called 'field_prefixation_value' within my content type's database table.

With this, I get the error message about existent values returned no matter what, even if the value does not exist. Following the above, I really am baffled as to why I can't get this to work. Help is appreciated.

function noduptitles_nodeapi(&$node, $op) {

if ( $op == 'validate' ) {
// We only care about nodes of type 'desired type'
$type = 'car';  // Change this to the type you want to restrict titles on
if ( $node->type == $type ) {

  $sql = "SELECT c.field_prefixation_value, n.nid FROM {node} n JOIN {content_type_car} c USING(nid) WHERE type = '%s' AND field_prefixation_value = '%s'";
  $results = db_query($sql, $type, $node->field_prefixation[0]['view']);
  $existing = db_fetch_object($results);

  if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->field_prefixation[0]['view'] == $node->field_prefixation[0]['view'] ) {

    $link = l(t('existing content'), "node/$existing->nid");

    form_set_error('field_prefixation', t('There is already content with the reference number of %field_prefixation_value, see '. $link, array('%type' => $type, '%field_prefixation_value' => $node->field_prefixation[0]['view'])));
  }
}
}
}
ZrO-1’s picture

Hey bsuttis, I know this is old but I'm mostly new here, and it looks like you question is still unanswered, so I'll offer a tip.
In your $sql = line you have field_prefixation_value = '%s'. Since you mentioned that field_prefixation is an integer, you should change the '%s' (for a string) to %d (for a decimal).

I'm too new at this to tell if anything else is off, but I did notice that.
Hope it helps...

nlowhor’s picture

I'm trying to use the original code above with a content type 'product'. The content type has its title generated automatically using a combination of the token and auto nodetitles modules from a taxonomy field and a CCK field. I've added the line of code to see all the node data after creation and can see that the title is being generated correctly. I've also tried using the module weight module to make sure that this fires after both token and autonodetitles.

I also tried it with another content type that doesn't use autonodetitles and it works fine(except for the link output is weird-I'll worry about that later).

Can anyone offer any suggestions? Thank you so much. This is just what I need and seems to be soooooo close to working.

ZrO-1’s picture

OK guys,
I think I have the code written now so that the function will check both the title and a specified field for a given content_type. I added the field check to the title check because it seems to me that you could still get dupes if the titles did not EXACTLY match. In other words, a new node titled "The Cars" would not be equal to "the cars" or "teh cars". By checking on a required field in addition to the title, I'm hoping to catch typos, and punctuation differences as well. Please correct me if I'm wrong with the above assumption.

In my version, I need to check 3 different content_types, so with the help of nevets, I set up a switch to check each of the 3 types.
The first and second cases in the switch are equivalent except for the name of the URL field that they are checking.
The third case in the switch is different, because in that case, I need to check that the title AND the field BOTH do not match an existing node. In other words, the title can match an existing node as long as the field does not also match, and vice-versa.

So here's the code:

<?php
// $Id: nodupescheck.module,v 0.1 2007/10/09 13:31:17 ZrO-1 Exp $

/**
 * No Dupes Check implements the nodeAPI hook - nodupescheck_nodeapi()
 * This function is to ensure that:
 * First, the submitted node titles do not match existing node titles
 * and Second, that the specified fields do not match existing fields.
 *
 * ->Developer Note: If this were to become a full module, then we should build a UI that lets the user select
 * the CCK content_types, and fields that need to be unique and pass them to this function.
 */

function nodupescheck_nodeapi(&$node, $op) {
  if ( $op == 'validate' ) {
    // We only care about nodes of a particular type - not all nodes,
    // since there are a few node types, and they each need to check different fields we make a switch for each type.
    switch ($node->type) {
      case 'content_type_provider': // the first type you want to restrict titles and fields on
        // For this content_type, we want to ensure that the node->title does not match an existing node.
        // First, we run a check on the title of the node. The variables we use are prefixed with t_* for the title
        $t_sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
        $t_results = db_query($t_sql, $node->type, $node->title);
        $t_existing = db_fetch_object($t_results);
        // NOTE: We get here on both inserts of new nodes, and updates of existing nodes.
        // For updates, we want to make sure the title does not match another one...
        // So first, we make sure it is an insert: (!$node->nid)
        // Or for updates that we have not just found the node being updated: ($t_exisiting->nid != $node->nid)
        if ( (!$node->nid || $t_existing->nid != $node->nid) && $t_existing->title == $node->title ) {
          // If there is a node with a matching title, we output an error-message stating so with a link to see the existing node
          $link = l(t('existing '.$node->type.' called '.$t_existing->title), "node/$t_existing->nid");
          form_set_error('title', t('Oops: There is already a provider called %title, click this link to see it: %link.', array('%title' => $node->title, '%link' => $link)));
        }
        // For this content_type, we want to also ensure that the "URL" field does not match an existing node.
        // Now, we run a check on the fields we want to ensure are also unique. The variables we use are prefixed with f_* for the field
        $f_sql = "SELECT f.field_provider_url, n.nid FROM {node} n JOIN {content_type_provider} f  USING(nid) WHERE type = '%s' AND field_provider_url[0]['url'] = '%s'";
        $f_results = db_query($f_sql, $node->type, $node->field_provider_url[0]['url']);
        $f_existing = db_fetch_object($f_results);
        // NOTE: Again, we get here on both inserts of new nodes, and updates of existing nodes.
        // For updates, we want to make sure the "URL" field does not match another one...
        // So first, we make sure it is an insert: (!$node->nid)
        // Or for updates that we have not just found the node being updated: ($f_exisiting->nid != $node->nid)
        if ( (!$node->nid || $f_existing->nid != $node->nid) && $f_existing->field_provider_url[0]['url'] == $node->field_provider_url[0]['url'] ) {
          $link = l(t('existing '.$node->type.' called '.$f_existing->title), "node/$f_existing->nid");
          form_set_error('title', t('Oops: There is already a provider called %title with the same address of %url, click this link to see it: %link.', array('%title' => $node->title, '%url' => $node->field_provider_url[0]['url'], '%link' => $link)));
        }
        break;
      
      case 'content_type_show':  // the second type you want to restrict titles on - Same as above, just different node type and field name
        $t_sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
        $t_results = db_query($t_sql, $node->type, $node->title);
        $t_existing = db_fetch_object($t_results);
        if ( (!$node->nid || $t_existing->nid != $node->nid) && $t_existing->title == $node->title ) {
          $link = l(t('existing '.$node->type.' called '.$t_existing->title), "node/$t_existing->nid");
          form_set_error('title', t('Oops: There is already a show called %title, click this link to see it: %link.', array('%title' => $node->title, '%link' => $link)));
        }
        $f_sql = "SELECT f.field_show_url, n.nid FROM {node} n JOIN {content_type_show} f  USING(nid) WHERE type = '%s' AND field_show_url[0]['url'] = '%s'";
        $f_results = db_query($f_sql, $node->type, $node->field_show_url[0]['url']);
        $f_existing = db_fetch_object($f_results);
        if ( (!$node->nid || $f_existing->nid != $node->nid) && $f_existing->field_show_url[0]['url'] == $node->field_show_url[0]['url'] ) {
          $link = l(t('existing '.$node->type.' called '.$f_existing->title), "node/$f_existing->nid");
          form_set_error('title', t('Oops: There is already a show called %title with the same address of %url, click this link to see it: %link.', array('%title' => $node->title, '%url' => $node->field_show_provider[0]['url'], '%link' => $link)));
        }
        break;
        
      // This content_type is a little different, because we need to check BOTH the title AND the node-reference field together
      // In other words, the title or the field can match an existing, but they both can't match an existing together
      case 'content_type_episode': // the third type you want to restrict titles on
        // First, for this content_type, we want to ensure that the node->title does not match an existing node.
        $t_sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
        $t_results = db_query($t_sql, $node->type, $node->title);
        $t_existing = db_fetch_object($t_results);
        // For this content_type, we want to also ensure that the "Show" node-reference field does not match an existing node with the same title.
        $f_sql = "SELECT f.field_episode_show, n.nid FROM {node} n JOIN {content_type_episode} f  USING(nid) WHERE type = '%s' AND field_episode_show[0]['nid'] = '%d'";
        $f_results = db_query($f_sql, $node->type, $node->field_episode_show[0]['nid']);
        $f_existing = db_fetch_object($f_results);
        if ( (!$node->nid || $t_existing->nid != $node->nid) && ($t_existing->title == $node->title && $f_existing->field_episode_show[0]['nid'] == $node->field_episode_show[0]['nid']) ) {
          $link = l(t('existing '.$node->type.' called '.$f_existing->title), "node/$f_existing->nid");
          form_set_error('title', t('Oops: There is already an episode called %title which is part of %show, click this link to see it: %link.', array('%title' => $node->title, '%show' => $node->field_episode_show[0]['view'], '%link' => $link)));
        }
        break;
    }
  }
}
?>

A word of caution to you: I am very new to PHP and drupal, so I can't say that the logic or code or syntax is perfect. It's probably very "cludgey". I have the greatest doubts about the third case... But hopefully we can work together to make it rock!
Thanks.
edit:
I also changed the link data, so the link shouldn't display weird anymore. Take a look and see if it will work for you.
/edit

nevets’s picture

I suspect this code does not even work, field_show_url[0]['url'] is PHP syntax for a field and will not work as part of an SQL statement. I am guessing you should be using just field_show_url.

For the case where you want to see if to things are both true, you probably want one sql statement, something like this

        // For this content_type, we want to also ensure that the "Title" AND "Show" node-reference field does not match an existing node with the same title.
        $f_sql = "SELECT f.field_episode_show, n.nid FROM {node} n JOIN {content_type_episode} f  USING(nid) WHERE type = '%s' AND title = '%s' AND field_episode_show = %d";
        $f_results = db_query($f_sql, $node->type, $node->title, $node->field_episode_show[0]['nid']);
        $f_existing = db_fetch_object($f_results);
        if ( (!$node->nid || $f_existing->nid != $node->nid) && ($f_existing->title == $node->title && $f_existing->field_episode_show == $node->field_episode_show[0]['nid']) ) {
          $link = l(t('existing '.$node->type.' called '.$f_existing->title), "node/$f_existing->nid");
          form_set_error('title', t('Oops: There is already an episode called %title which is part of %show, click this link to see it: %link.', array('%title' => $node->title, '%show' => $node->field_episode_show[0]['view'], '%link' => $link)));
        }
        break;

I will let you adjust the message part as needed.

ZrO-1’s picture

Thanks nevets. I noticed that also. i am testing the module now on my drupal install. Right now it's not working. So I am going to remove the code I posted above until I get this sorted out.
Ah, i see I can't edit the above post now that there are replies to it.
OK, so anyone reading this thread, ignore the code I posted above. I'll post the final version once I have this all sorted out.

thanks again nevets. You've been a big help.

ZrO-1’s picture

Thanks to nevets' help. I now see how the $sql query can be used to get everything needed in one shot. I'm cleaning-up the cases I made before, and will try it a couple times on my drupal install. If all goes as planned, I'll post the revised module here with how it works.

ZrO-1’s picture

Hey guys,
I'm still working on a more robust version of this function to check more than just titles, but for now I wanted to let you know that I have fixed the error message and link for when a dupe title is found.
Here is the code:

if ( ( ! $node->nid ||  $existing->nid != $node->nid ) && $existing->title == $node->title ) {
          $link = l('view the existing '.$node->type, 'node/'.$existing->nid, NULL, NULL, NULL, FALSE, FALSE);
          form_set_error('title', 'There is already a '.$node->type.' called '.$node->title.'. Here is a link to '.$link);
        }

The result of this is a single line that reads:
"There is already a [your content type] called [content type's title]. Here is a link to view the existing [your content type]."

Where [your content type] is the name of your content type, and [content type's title] is the node's title.

I have tested this feature out, and the link html and the link value are correct now.
Hope that helps...

ZrO-1’s picture

If you want to drop the if() I wrote right into the function that nevets wrote above, then replace all of the $node->type with $type ... ah heck, I'll just give you the whole thing ;)

<?php
function noduptitles_nodeapi(&$node, $op) {
  if ( $op == 'validate' ) {
  // We only care about nodes of type 'desired type'
  $type = 'story';  // Change this to the type you want to restrict titles on
  if ( $node->type == $type ) {
    $sql = "SELECT n.title, n.nid FROM {node} n WHERE type = '%s' AND title = '%s'";
    $results = db_query($sql, $type, $node->title);
    $existing = db_fetch_object($results);
    // We get here on both inserts and updates
    // For updates we want to make sure the title does not match another one
    // So we make sure it is an insert ( ! $node->nid )
    // or for update that we have not just found the node being updated ( $exisiting->nid != $node->nid )
    if ( (!$node->nid || $existing->nid != $node->nid) && $existing->title == $node->title ) {
      $link = l('view the existing '.$type, 'node/'.$existing->nid, NULL, NULL, NULL, FALSE, FALSE);
      form_set_error('title', 'There is already a '.$type.' called '.$node->title.'. Here is a link to '.$link);
    }
  }
}
?>

Hope that helps.

ZrO-1’s picture

I have figured out why my extra features have not been working. I don't know how much people who read this know about CCK types but I thought I'd share what I found so if someone is trying something similar to what I've been trying, they might find this helpful.

When you are calling $node->type you can use the "machine-readable" type for any node type. This includes CCK type nodes. You can find the machine-readable type for CCK nodes by looking at your content types in the admin section.
For an example: a CCK content type with the name "Foo Bar" might have a machine-readable type of "foobar"

That's all very straightforward. So what's the big deal?
Well, If you need to do a SQL query that checks a CCK node type, you can't just use the machine-readable name. You see, CCK types are stored in the database as content_type_*. So if we use our above example, we would have the following:

CCK Name = Foo Bar, machine-readable type = foobar, and db table = content_type_foobar

So when writing a function to query the db based on a particular CCK type, you would have something like this:

switch ($node->type) {
  case 'foobar': // the machine-readable type
    $sql = "SELECT * FROM {content_type_foobar}" // the actual table in the db for the type

The same holds true for CCK Multiple Value fields, where as example:
CCK Label = First Name, machine-readable name = field_first_name, and db table = content_field_first_name

This was really screwing me up before, so I wanted to post this to help others who might also be having trouble.

ZrO-1’s picture

Hey guys,
I am so close to having the enhanced version of dupe check working I can smell it. There is just one last issue which I can't see how to fix in the code. I'm hoping someone can give me a pointer.
So here is what's happening:
1) When a new node is created, and the title and URL match an existing node, an error displays with both fields being in error.
That is what I want.
2) But, when the title is changed to be unique, but the URL is still the same as an existing node, the node passes and is created. Basically, the URL is not triggering an error by itself, but it does trigger correctly when the title also triggers.
Which is not what I want.

Here's the part of the code in question:

<?php
$sql = "SELECT f.field_provider_url_url, n.title, n.nid 
                FROM {node} n JOIN {content_type_provider} f  USING(nid) 
                WHERE type = '%s' AND title = '%s' AND field_provider_url_url = '%s'";
        $results = db_query($sql, $node->type, $node->title, $node->field_provider_url[0]['url']);
        $existing = db_fetch_object($results);
        // if the node is new, OR we're not updating/editing an existing node
        if (!$node->nid || $existing->nid != $node->nid) {
          // if an existing title matches the new title
          if ($existing->title == $node->title) {
            $link = l('view the existing '.$node->type, 'node/'.$existing->nid, NULL, NULL, NULL, FALSE, FALSE);
            form_set_error('title', 'Oops! There is already a '.$node->type.' called '.$node->title.'. Here is a link to '.$link.'. Please be absolutely sure that you are not creating a duplicate '.$node->type.'. Thanks!');
          }
          // if an existing URL field matches the new URL field
          if ($existing->field_provider_url_url == $node->field_provider_url[0]['url']) {
            $link = l('view the existing '.$node->type, 'node/'.$existing->nid, NULL, NULL, NULL, FALSE, FALSE);
            form_set_error('field_provider_url', 'Oops! There is already a '.$node->type.', called '.$node->title.', with the address '.$existing->field_provider_url_url.'. Here is a link to '.$link.'. Please be absolutely sure that you are not creating a duplicate '.$node->type.'. Thanks!');
          }
        }
?>

You see, the title and URL are being checked independently, but the URL is only caught if the title is also.
Any help here would be really appreciated. Thanks.

ZrO-1’s picture

would writing the two if() conditions inside the parent if() condition as "if ... endif;" rather than "if {...}" help anything?

nevets’s picture

This part of the sql is where the issue is

WHERE type = '%s' AND title = '%s' AND field_provider_url_url = '%s'

Since it only finds a node where the title AND the url are the same. One way to deal with that is to change the AND to an OR but that also requires adding a loop (since one node might have the same title, another the same URL)

So which rule to you want to code
Title not the same AND url not the same
Title not the same OR url not the same
(In this case no two items may have the same title
or no two items may have the same URL
So if there existed one item with title = 'blue' and url = 'http://red,org'
and another with title = 'red' and url = 'http://blue,org'

Some one could not add title = 'blue' and url = 'http://blue,org'
and they could not add title = 'blue' and url = any value
or add title = 'red' and URL = any value
or add title = any title and url = 'http://blue,org'
or add title = any title and url = 'http://red,org'

ZrO-1’s picture

thank you once again nevets. I see exactly how that's happening now.

For this specific node type, the title OR field_provider condition is what I want.
So if title = example and url = example.com, then no other node of the same type can have either title = example or url = example.com.

Now that I know, I can make the adjustments to the rest of the function and test. Then I'll post the code so other's can use it.

ZrO-1’s picture

Hey gang.
I have a version of the function working which is checking title, field, and/or a combination of title and field. Because i still have some things I'd like to see done with this, and because i feel bad cluttering-up this thread, I have started a new topic over in the module-development forum. You can see the code I have and post replies here: http://drupal.org/node/182860

Thanks to nevets for all of the help.

I hope my work can be used by others here.

summit’s picture

Hi,

Could this code also be used to implement the validation of a url with links_weblink.module?
I had code for this, but this doesn't work anymore after implementing location.module. I don't know why. See the code here:

/**
* Implementation of hook_validate().
*/
function links_weblink_validate(&$node) {
if (($row = db_fetch_object( db_query("SELECT l.lid , ln.nid FROM {links} l INNER JOIN
{links_node} ln ON l.lid = ln.lid WHERE l.url ='%s'",
$node->links_weblink_url))) && ($row->nid != $node->nid)){
form_set_error('url', t('Link allready added in the system'));
}
else
{
links_weblink_node_build($node);
}
}

Could somebody help with this. Can this be implemented in the 'module'?
Thanks a lot in advance for your remarks in advance!

greetings,
Martijn

arithmetric’s picture

I've released a module called unique_field, which provides an interface for requiring that node titles or CCK fields have unique values. It is available from:

http://drupal.org/project/unique_field

I'd appreciate any comments or suggestions.

LeMale’s picture

Why don't have the same option for "body" field ?

I use nodecomments module, comment are nodes, nodes boddy are the comments and the titre is always the same, so I will need to prevent posting the same comment (the same node boddy) twice.

paganwinter’s picture

Subscribing...

Samsoe’s picture

Hi, I'm a newbie and i'm using drupal 6. I have a "wiki" content type and I just confused how to prevent duplicate title in drupal 6. Anyone can help me ? pls..

nevets’s picture

See the post just above for unique field.