Jump to content

Field set to unique in table


horst
 Share

Recommended Posts

$check_field_dupe = $pages->find( "id!=$current_page_id, $field_name=$field_value, include=hidden, check_access=0" );

... even though you mentioned find()->first(). I searched through the wire directory and couldn't find that usage.

Ignore my mention of find()->first(). Looks like I was writing one thing, and changed my mind by the time I got to the code. Using first() would be fine, but I opted to just if(count($field_dupe_check)) instead. 

Does your find() line above, with the hidden and check_access params, not include the trash?

 

It should exclude trash unless you add "include=all" to it. 

On your second example [!$check_field_dupe->isTrash()] -- I would think that if there were more than one page returned, in the array,

that we'd have to use a loop to check if each was in the trash. Is that correct?

No, because I used get() rather than find(). get() only ever returns 1 page. find() can return any number of pages. 

I guess you're also confirming that the textunique field does indeed require additional API code, unlike the email field, for example.

TextUnique enforces the uniqueness via a database index. So it's not necessary for you to perform your own checks, unless you want to manage the flow control yourself. Another way to do it would be to capture the WireDatabaseException that gets thrown, but I think your method is fine. 

Link to comment
Share on other sites

Dear Ryan,

(and Martijn, thanks, too.)

> It should exclude trash unless you add "include=all" to it.

This, combined with the get function, is perfect. Thanks!

I do want to point out again, however, that the TextUnique function, when used with the API on the frontend, with no programming checks,

simply erases the duplicate value, and saves the field without a warning.

I think that's less than ideal, because it's so easy to miss that point (that the data is erased).

Perhaps the function should NOT erase the data, and instead throw a warning.

Thanks, Ryan, as always, for your help. You are a mensch!

Peter

Link to comment
Share on other sites

Dear Ryan,

I was in error above. As you wrote, get implies "all". So, I think your first option, with find, but excluding the trash, is best.

$check_field_dupe = $pages->find( "id!=$current_page_id, $field_name=$field_value, include=hidden, check_access=0" ); 
if(count($check_field_dupe)) {// you found a duplicate that's not in the trash
}

What I meant previously, about the get option, is that since get only returns one page, which could be in the trash, one would have to check again, in a loop, to make sure that there wasn't another page, that wasn't in the trash.

Thus, I think your find option is better.

Thanks again,

Peter

Link to comment
Share on other sites

Dear Ryan,

I just updated my code, using the include=hidden, which indeed does NOT check the trash.

However, after the code allowed a value (ignoring the duplicate in the trash), the TextUnique function

*still* erased the field value, when it saved the record! Oy!

This is my code:

            if ( $field->type == 'FieldtypeTextUnique' )
                  {
                  # we use include=hidden, instead of include=all, because we don't want to check the trash

                  $field_value = $sanitizer->text($input->post->$field_name);

                  $check_field_dupe = '';
                  $check_field_dupe = $pages->find( "$field_name=$field_value, include=hidden, check_access=0" );

                  if ( count( $check_field_dupe ) )
                        {
                        # found a duplicate that's not in the trash

                        $field_value_error  = 'yes';

                        $field_error_text  .= "<span class='error_text'>
                                               '$field_value' is not available.<br>
                                               Please try something different.
                                               </span><br>
                                              ";
                        }
                  }

 

The net result is that a TextUnique field becomes dangerous to data if the trash has not been purged.

(Unless I'm missing something.)

I found it convenient to not have to keep an array of Unique-Only fields, but simply refer to their backend settings.

However, unless we can work around this, and not have the data deleted, it would seem that the TextUnique field type

is too dangerous to use. Better to just use a Text field and do the deduping in code.

What are your thoughts?

Yours,

Peter

Link to comment
Share on other sites

Dear Ryan,

If $pages->get always looks in the trash, is it correct that a selector using "parent" would not find

an item in the trash that would have worked before it got to the trash, because the trash item has

/trash/ in front of the url?

That is, if the selector is "parent=/my_url_segments/" and then that page is trashed, the page would suddenly

have the url of "/trash/my_url_segments/", meaning that the original parent selector would not find it anymore.

Is that correct?

In certain cases, one might want to use $pages->get with a parent selector, and not have to worry that it would find

items in the trash.

I wonder if $pages->get could also be set to exclude items in the trash, as you said find does with "include=hidden",

instead of "include=all".

Generally speaking, one wouldn't want any searches to find things in the trash unless it's specified.

In fact, I'm wondering how I can use "get" safely, since I don't want to return any results from the trash.

Best regards,

Peter

Link to comment
Share on other sites

Peter,

You can easily exclude pages in the trash by using, for example, has_parent!=7. The ID of the trash page is 7 by default...OR...example code from Trashman (the module)

if ($page->parent->id != $this->config->trashPageID) { //blah blah}//used in function/method context 
$config->trashPageID    returns the Page ID of the trash page. Pages sent to the Trash have their parents changed to the Trash page (ID=7). They don't become orphan or lost pages :);)
 
Edited: 
Added Trashman code example
Edited by kongondo
  • Like 2
Link to comment
Share on other sites

Dear Kongondo,

That's great! So, any get() call could simply add this element to the selectors:

parent!=$trash_page_id,

(assuming that $trash_page_id had been set, i.e. $trash_page_id = $config->trashPageID;)

Can one use the config call inside a selector? Like:

parent!=$config->trashPageID,

?

Thanks again. This resolves the issue for me. Although I still think that the *default* behavior of get() should

not include the trash. (Assuming I'm understanding get() correctly.)

Peter

Link to comment
Share on other sites

Quick one...will update later...(will check for errors) - Tested with PW Dev (version from a couple of days back)

Trash Pages

get(); - will NOT get anything from the Trash

get("include=hidden"); - will NOT get pages in the Trash (even if their status there is hidden).

get("include=all"); - will get ALL pages in the Trash (all pages - visible, hidden, published, unpublished)

Tree Pages (non-admin)

get();  Unpublished + Hidden Page => will return something like "for-page-1532" if you echo out the name of the returned document. Echoing title returns the name normally (not the title).

get("include=hidden");  Unpublished + Hidden Page => will return something like "for-page-1532" if you echo out the name of the returned document. Echoing title returns the normal name (i.e. without the "for-page-1532").

get("include=all");  Unpublished + Hidden Page => will return page. Echoing out the name behaves normally (i.e. without the "for-page-id").

get (); Published + Hidden Page => will return page normally.

get (); Unpublished + Visible Page => will return page normally.

You can use has_parent in your selector :)

Edit:

Yes, you can use  parent!=$config->trashPageID in your selector as well ;).

  • Like 2
Link to comment
Share on other sites

Dear Kongondo and Ryan,

I think I may have been confused, somehow. I wasn't able to replicate having get()

pull ids from the trash, unless I used include=all.

I had switched to find, but now I'm back to get(), and this seems to work. It doesn't pull from the trash.

I'm not using TextUnique fields, because of the data deletion issue mentioned above. Instead, I've created

an array of unique fields in my code.

            if ( in_array( "$field_name", $unique_fields_array ) )
                  {
                  # we use include=hidden, instead of include=all, because we don't want to check the trash

                  $field_value         = $sanitizer->text($input->post->$field_name);

                  $check_field_dupe_id = 0;
                  $check_field_dupe_id = $pages->get( "id!=$current_page_id, $field_name=$field_value, include=hidden,

                                                       check_access=0" )->id;

                  if ( $check_field_dupe_id > 0 )
                        {
                        # found a duplicate that's not in the trash

                        $field_value_error  = 'yes';

                        $field_error_text  .= "<span class='error_text'>
                                               '$field_value' is not available.<br>
                                               Please try something different.
                                               </span><br>
                                              ";
                        }
                  }

 

I tried a simple get() call also, like this:

$check_field_dupe_id = $pages->get( "id!=$current_page_id, $field_name=$field_value" )->id;
 

but that didn't check the trash either.

Perhaps I had assumed that it was doing so because the TextUnique field was still in play, deleting the data.

Best regards,

Peter

Link to comment
Share on other sites

Peter,
 
Glad you got it sorted. Two comments
 

we use include=hidden, instead of include=all, because we don't want to check the trash

 
With respect to all pages, as you can see from my examples above and from the quote below, using get() with "include=hidden" (and check_access=0) is basically redundant. get() will grab hidden stuff NOT in the trash without that selector anyway but will NOT get stuff in the trash. So, get() without these two selectors should suffice in this case.
 

Note that if you are using include=all then there is no reason to use check_access=0 since it is assumed.......(snip)

Note that $pages->get("..."); is not subject to this behavior (or access control) and "include=all" is assumed. This is because requesting a single page is a very specific request, and not typically used for generating navigation. To put it another way, if you are asking for a single specific page, we assume you mean it. 

 
Secondly, since $page always returns the current page, I'm wondering why you need to create a variable to hold the current page id ($current_page_id)? The following... 

id!=$page->id

should work.

Link to comment
Share on other sites

Dear Kongondo,

You quoted this text:

> Note that $pages->get("..."); is not subject to this behavior (or access control) and "include=all" is assumed. This is because requesting a single page is a very specific

> request, and not typically used for generating navigation. To put it another way, if you are asking for a single specific page, we assume you mean it.

That line seems conflicted, because it says "include=all" is assumed. Yet, as you pointed out, get() doesn't search the trash unless "include=all" is specified.

About the variable "$current_page_id" -- you are right. I think I've been so used to putting things in simple variables, that I have to get used to using the OOP syntax.

Also, sometimes one variable is simpler to use than a long string of -> arrow modifiers.

So... about the get() and include=all -- perhaps the docs need to be tweaked a bit.

And, going back to TextUnique -- it does seem to have the nasty habit of deleting the duplicated data without a warning, when using the API.

Thanks for your help!

Peter

Link to comment
Share on other sites

Here's what the code actually says. In the Page class, we have these constants: 

const statusOn = 1; // base status for all pages
const statusLocked = 4; // page locked for changes. Not enforced by the core, but checked by Process modules. 
const statusSystemID = 8; // page is for the system and may not be deleted or have it's id changed (everything else, okay)
const statusSystem = 16; // page is for the system and may not be deleted or have it's id, name, template or parent changed
const statusHidden = 1024; // page is excluded selector methods like $pages->find() and $page->children() unless status is specified, like "status&1
const statusUnpublished = 2048; // page is not published and is not renderable. 
const statusTrash = 8192; // page is in the trash
const statusDeleted = 16384; // page is deleted (runtime only)
const statusSystemOverride = 32768; // page is in a state where system flags may be overridden
const statusCorrupted = 131072; // page was corrupted at runtime and is NOT saveable
const statusMax = 9999999; // number to use for max status comparisons, runtime only

These are all the possible values for status. However, status is a bitmask, so it can have more than one value at a time. However, the status levels are ordered from most visible to least visible, so selectors like "include=all" actually translate to "status<9999999" and "include=hidden" translates to "status<2048".

Next is the PageFinder class, which is where the "include=all" and "include=hidden" are defined, among other things. There is also an internal mode in PageFinder called "findOne" that is an option that the $pages->get() function uses. The findOne option is not one that you would use on your own, it's only an internal designation. The findOne option states that: 1) return only the first match; 2) skip over any access control checks; 3) limit to pages with status less than unpublished (unless another status is specified or include=all is specified). There is also the "findAll" option, which is what is assumed when you specify "include=all". That one states that 1) the status must be less than statusMax (meaning, it can include any available status); and 2) skip over any access control checks. Again, you don't need to remember what findOne and findAll are, since you won't ever use them. But I mention them just to explain how selectors translate in the system. 

$pages->get() or $pages->find() are not going to include pages in the trash unless you specify an "include=all" or one of the high status levels. $pages->get() is different from $pages->find() in these respects: $pages->get() only ever returns 1 page. $pages->get() bypasses access control and will include hidden pages. You don't need to specify "include=all" to $pages->get(); unless you want to retrieve an unpublished page or a page from the trash. If there are times when you want a $pages->get() that performs exactly like $pages->find(), except only returning one page, you can do this:

$page = $pages->find("your selector, limit=1")->first(); 
if($page) {
  // you got it
}

Or you can do this:

$page = $pages->get("your selector");
if($page->viewable() && !$page->isHidden()) {
  // you got it 
} 

I almost never need to do any this though (or necessarily even remember it). $pages->find() behaves the way I usually want it to, including things that aren't viewable and things that don't belong in lists and navigation. And $pages->get() behaves the way I usually want it to, returning the single specific thing that I asked for. 

  • Like 1
Link to comment
Share on other sites

  • 4 weeks later...

Dear Ryan,

I just ran into an issue, I believe.

I'm using the $pages->get($id); syntax, and it's returning a page in the trash.

I'm pulling a page id from a form, and before I run the page->delete() function, I test if the page exists.

      # $delete_page = $pages->get("$page_id");                   - this returns a page in the trash
      # $delete_page = $pages->get("id=$page_id");               
- this returns a page in the trash     

        $delete_page = $pages->get("id=$page_id,include=hidden"); - this does not return the page in the trash

      $delete_page_id = $delete_page->id;


I test it by echoing the $page_id and the $delete_page_id on a response page, with echo and exit.

I thought at first it was because I wasn't using quotes around the variable, e.g. get($page_id), but the behavior was the same, with or without quotes.

Based on your outline above, it seems that "include=hidden" acts as a restriction, i.e. "status<2048".

Of course, I may be doing something wrong, but the behavior seems inconsistent. Any tips?

Thanks,

Peter

Link to comment
Share on other sites

pages->get(id) will return the page with id. Doesn't matter where it is and what user. It's explicit.

As soon as you add another selector, it will behave like a find thus check for if pages can be viewed or accessed. Everything correct here.

Link to comment
Share on other sites

Dear Soma,

That's good to know, but it seems to be an exception to the rules that Ryan laid out above, where he said:

> $pages->get() or $pages->find() are not going to include pages in the trash unless you specify an "include=all" or one of the high status levels.

Also, this accessed the trash:

$pages->get("id=$page_id");

So, did it do so because the selector referenced the "id" field? Or does get() access the trash when there's only ONE selector, including such things as:

parent=/xyz/ ?

I'd vote for having find() and get() programmed to never access the trash unless 'include=all' is specified.

Right now, the usage seems a little fuzzy.

Yours,

Peter

Link to comment
Share on other sites

Asking for a page by ID is really specific, so it's not going to make you jump through more hoops than that to get at what you want there. If you know a page well enough to know the actual ID of it ahead of time, then it's not really a search/find at all. Regardless of whether you use get(123) or get("id=123"), ProcessWire can bypass the entire PageFinder engine (and any overhead associated with it). 

  • Like 2
Link to comment
Share on other sites

Dear Ryan,

I can see the logic of your comments above.

I'm building a multi-user team-based web app, where someone can view a page that has a button to do something

with a sub-page. The button has a hidden field with the target page id.

Theoretically, someone could go to the page where they see the button, which is populated with that

target page id, and then go get a cup of coffee. After returning, the page is still there, being stateless.

The user clicks on the button to work with that target page, and finds that a different user has

deleted that page. Thus, in my code, I work in two steps:

1: populate the button with the target page id, and

2: when the user clicks that button, check for the existence of the target page.

I checked for the existence of the page using get($id) or get("id=$some_id"). But, in testing, having

deliberately deleted the target page, I found that the get thought that the page was still there,

because it checked in the trash.

I based that on your notes above that get never checks the trash. :-)

So, that's why I think that get and find should never check the trash unless it's specified.

Although I understand your point above, about bypassing the PageFinder engine.

Based on my comments or need, here, what would you recommend?

Should I add "parent!=$config->trashPageID", e.g.

$check_page = $pages->get("parent!=$config->trashPageID, id=$some_id");

or is there a better way, perhaps using "include=hidden"?

And... is it ONLY when one uses get(id) that the trash is checked? No other types of parameters?

Does find() ever check the trash, when 'include=all' is not specified?

Thanks very much for your help!

Peter

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...