Prevent a user to create two identical items in a structure field

Hello,

I would like to prevent a user to create two identical items in a structure field. My structure field has a simple text field.

I’ve made a function to check if a multidimensional array has two identical values:

function search_identical_value( $array , $field ) {
    $flag = false;
    foreach ( $array as $current_key => $current_array ) {
        foreach ( $array as $search_key => $search_array ) {
            if ( $search_array[$field] == $current_array[$field]) {
                if ( $search_key != $current_key ) {
                    $flag = true;
                    break;
                }
            }
        }
        if ( $flag ) {
            break;
        }
    }
    return $flag;
}

This function works well and return true if two identical items has been found. I put this function in the plugin folder to be accessible from the config file.

This is my blueprint

fields:
      questions:
        label: Questions or instructions
        help: Start your question or instruction with a numbering
        type: structure
        fields:
          questionsLabel:
            label: Question
            type: textarea
            default: '1- ... (Start your question or instruction with a numbering)'
            required: true

To check and prevent two identical items in my structure field, I put this in my config file:

return [
    'debug' => true,

    'hooks' => [

	    'page.update:after' => function ( $newPage, $oldPage ) {

	            $questions = array();
	            $questions = $newPage->questions()->yaml();

	            if ( search_identical_value( $questions , 'questionslabel' ) ) :
	                throw new Exception('Check your questions, you cannot create two identical questions.');
	            endif;
	    },

	]
];

The Exception pop-up if I create two identical items but when I delete the second item from the structure field, the save orange bar disappears and the two items stay in the txt file. So if I reload my panel page, the two items are visible.

Any idea how I can avoid this?

You can test with this starter kit > go to the about page, the structure field is below the textarea. (delete the account folder to reinstall a new admin user)

Thx

I think a page.update:before hook would be more appropriate, but I’ll test with your example.

I did some test with page.update:before but without success:

return [
    'debug' => true,

    'hooks' => [

	    'page.update:before' => function ( $page, $values, $strings ) {

	            $questions = array();
	            $questions = $page->questions()->yaml();

	            if ( search_identical_value( $questions , 'questionslabel' ) ) :
	                throw new Exception('Check your questions, you cannot create two identical questions.');
	            endif;
	    },

	]
];

PS: I don’t really understand what the parameters $values and $strings contain exactly?

Hm, yes, you are right, neither option works as expected.

And why don’t you remove duplicate items from the array instead of throwing an error message?

The problem is that throwing the error actually prevents the action from being executed. So the duplicate entry is not stored and so if you remove the item again, there is no difference to before, so no orange bar (unless there are more changes than a single added duplicate item).

With your default entry to make it very likely that duplicate entries happen in the first place.

I use the Exception pop-up because I want to inform the user that he/she has created two identical items and he/she can’t do this.

Not sure to understand, why the two identical items are written in the txt file if the action is not executed?

Sorry, I was talking nonsense.

:wink:

Yes, the changes are saved but the orange bar doesn’t disappear after clicking ok in the pop-up. So I assume the content store and what is actually stored in the file are out of sync at this time.

So as you suggested above, the solution would be to delete the duplicate value in the array and update my structure field with the new array state, then throw the error to inform the user :thinking:

I will try to do some test …

Or tell the user in the help text that this is what will happen without throwing the error.

On a side note, you could simplify your logic:

<?php 

$questions = A::pluck(page('about')->questions()->yaml(), 'questionslabel');
$unique = array_unique($questions);

if (count($questions) !== count($unique)) {
  echo "There are duplicate entries";
}
?>

Which has the additional advantage that you can store $unique as your new value.

But what if your users use different numbers but the same questionlabel? Wouldn’t it make more sense to leave the numbers out and only add them in the template. Or if they by mistake use the same number for a different questionlabel? Then your check will find that they are different but they will still come out wrong on the frontend.

Thanks Sonja, it’s an interesting shorten solution.

I ask for the numbering because there was a bug with the sortBy options, but I think the last version has fixed this bug, so maybe I don’t need to ask the user to numbering there questions anymore.