Create a new page with a structure/snippetfield field from a front-end form

Hi,

I’ve tried to create a subpage and set its structure/snippetfield with an array of data coming from a front-end form.
No problem to create the subpage but I fail to append new data into the structure/snippetfield.

I use the excellent Snippetfield plugin from @jenstornell as a structure field.

I’ve checked the cookbook and forum and I have copied/pasted the code of the function “addToStructure($page, $field, $data = array())” When I use it, it return “true” but I see nothing in my structure/snippetfield field when I go back to my panel in the subpage just created.

This is the code from my controller: (the array of $data is hard coded here)

/**
* Add a new element to a kirby structure field
* @param string $page
* @param string $field
* @param array $data
*/
function add_to_structure_speaker( $page, $field, $data = array() ) {
  $fieldData = page($page)->$field()->yaml();
  $fieldData[] = $data;
  $fieldData = yaml::encode( $fieldData );
  try {
    $update_structure = page($page)->update( array( $field => $fieldData ) );
    return $update_structure;
  } catch( Exception $e ) {
    return $e->getMessage();
  }
}



try {

        // Create a new presentation
        if ( !empty( $data['title'] ) && !empty( $data['description'] && !empty( $presentations_page_uri ) ) ):
          $new_presentation = $page->create( $presentations_page_uri . '/' . str::slug( $data['title'] ) , 'presentation' , $data );
        endif;

        // set the structure field "Speakers"
        $speakers_update = add_to_structure_speaker( $new_presentation->uri() , 'speakers' , array("speaker" => "gilles") );

When I var_dump($speakers_update) it return true, but there is no structure/snipetfield field value in my panel subpage just created :-/

Code of my blueprint:

presentation:
    label: Content
    type: headline
  title:
    label: Title
    type:  text
    required: true
  description:
    label: Description
    type:  textarea
    required: true
  ... etc
  speakers:
    label: Speakers
    required: false
    type: snippetfield
    snippet: speakers/speaker
    style: items
    fields:
      speaker:
        label: Speakers
        type: speaker
        placeholder: Enter a name ...
        help: The speaker must be registered to appear into this list.

I’ve tried to replace the snippetfield by a classic structure field but same result.
Any idea what could be wrong?

I don’t see any reason why your code should not work, apart from the fact that the catch part is missing from the try.

And I’d wrap the update into its own try-catch block.

Did not test with snippetfield, though.

The catch is present in my code but I’ve just coped/pasted a part of it, that is why you didn’t see it here.

Thanks for your tips, I’ve changed my code to wrap “create” subpage and “update” structure field in their one try/catch.

I’ve check the /content folder to see what happen after creating the subpage and update the structure field.

My code create two different txt files (that’s why I don’t see the structure field in my subpage in the panel):

  • one contain the subpage classic fields (title, description …) > /content/presentations/my_title_subpage/presentation.txt
  • and the other one contain the structure field data. > /content/presentations/my_title_subpage/my_title_subpage.txt

The structure field data should update the presentation.txt file but it doesn’t …

Either my $new_presentation->uri() is incomplete or maybe it’s something else :-/
I’m still investigating it!

As I said, in my test with a given data array it works. I would nevertheless change the page creation part to:

 $new_presentation = page($presentations_page_uri)->children()->create(str::slug( $data['title'] ) , 'presentation' , $data );

1 Like

Same result with

 $new_presentation = page($presentations_page_uri)->children()->create(str::slug( $data['title'] ) , 'presentation' , $data );

The problem is somewhere else …

1 Like

Yes, I know, that was just on a side note…

So the second text file is created in the same folder? That’s weird.

Edit: maybe you can post the complete template/controller?

And even more weird, the subpage is sometime created with the good “presentation” template which is ok, but sometime with the default template which is not ok. This seems to be completely random.

This problem make me crazy :-/

This is my snippet (which I include in my template):

<?php

?>


<style>
.honey {
  position: absolute;
  left: -9999px;
}
</style>



<form class="dashboard-proposalnew repeater" action="<?= $page->url() ?>" method="post" enctype="multipart/form-data">

  <div class="form-element">
    <label for="title">Title:</label>
    <input type="text" id="title" name="title" placeholder="Title" value="" required/>
    <?php if( $alert['title'] ) { echo '<span class="alert">' . html( $alert['title'] ) . '</span>'; } ?>
  </div>

  <div class="form-element">
    <label for="description">Description:</label>
    <textarea name="description" id="description" placeholder="Present your proposal in few words" required></textarea>
    <?php if( $alert['description'] ) { echo '<span class="alert">' . html( $alert['description'] ) . '</span>'; } ?>
  </div>

  <div class="form-element">
      <label for="type">Type:</label>
      <select name="type">
        <option value="" selected ></option>
        <?php
          foreach ( $types as $type_value ):
        ?>  
            <option value="<?= strtolower( $type_value['type'] ) ?>" <?php e( $proposal_types_key == strtolower( $type_value['type'] ), 'selected' , '' ) ?> ><?= $type_value['type'] ?></option>
        <?php
          endforeach;
        ?>
      </select>
  </div>

  <div class="form-element">
      <label for="thematic">Thematic:</label>
      <select name="thematic">
        <option value="" selected ></option>
        <?php
          foreach ( $thematics as $thematic_value ):
        ?>
            <option value="<?= strtolower( $thematic_value['thematic'] ) ?>" <?php e( $proposal_thematics_key == strtolower( $thematic_value['thematic'] ), 'selected' , '' ) ?> ><?= $thematic_value['thematic'] ?></option>
        <?php
          endforeach;
        ?>
      </select>
  </div>

  <div class="form-element" data-repeater-list="speakers">
      <div data-repeater-item>

        <label for="department">Speakers:</label>
        <select name="speaker">
          <option value="" selected></option>
          <?php
          foreach ( $speakers as $speaker_key => $speaker_value ):
          ?>
              <option value="<?= $speaker_key ?>" <?php e( $proposal_thematics_key == $speaker_value, 'selected' , '' ) ?> ><?= $speaker_value ?></option>
          <?php
            endforeach;
          ?>
        </select>

        <input data-repeater-delete type="button" value="Delete"/>

      </div>
  </div>
  <input data-repeater-create type="button" value="Add"/>
  


  <div class="honey">
     <label for="genre">If you are a human, leave this field empty</label>
     <input type="genre" name="genre" id="genre" placeholder="M./Me." value="<?= !empty($data['genre']) ? $data['genre'] : '' ?>"/>
  </div>

  <input type="hidden" name="csrf_token_proposalnew" value="<?php echo csrf() ?>">


  <button class="button" type="submit" name="proposalnew" value="proposalnew" >Save New Proposal</button>

</form>

This is my controller:

<?php

return function( $site, $pages, $page ) {

  $alert = array();
  $csrf = '';
  $types = array();
  $thematics = array();
  $speakers = array();
  $speakers_form = array();
  $presentations_page_uri = '';

  $types = !empty( $site->conferenceTypes()->yaml() ) ? $site->conferenceTypes()->yaml() : array();
  $thematics = !empty( $site->conferenceThematics()->yaml() ) ? $site->conferenceThematics()->yaml() : array();
 
  // get all users and build a $speakers array: key=usename / value=last + first name
  foreach ( $site->users() as $user ): 
    if ( !empty( $user->firstname() ) && !empty( $user->lastname() ) ):
      $name =  $user->lastname() . ' ' . $user->firstname();
    elseif ( !empty( $user->firstname() ) || !empty( $user->lastname() ) ):
      $name = $user->username() . ' - ' . 'Incomplete name, check the user firstname and lastname';
    endif; 
    $speakers[$user->username()] = $name;
  endforeach;

  // sort speakers by name
  asort( $speakers );

  // get speakers choosed from the form by the user
  $speakers_form = !empty( get('speakers') ) ? get('speakers') : array();

  // determine the presentations uri() even if the user changes it
  $presentation_objet = $pages->filterBy( 'template' , 'presentations' );
  // There is only one page set with the "presentations" template (and it MUST stay like this)
  foreach ( $presentation_objet as $presentation_page ):
    // The presentation page uri is nedeed to build the path when creating a presentation subpage (see below)
    $presentations_page_uri = $presentation_page->uri();
  endforeach;


  if ( r::is('post') && get('proposalnew') ) {
    if ( !empty(get('genre') ) ) {
      // lets tell the bot that everything is ok
      go( $page->url() );
      exit;
    }

    $data = array(
      'title'         => strip_tags( esc( get('title') ) ),
      'description'   => strip_tags( esc( get('description') ) ),
      'type'          => strip_tags( esc( get('type') ) ),
      'thematic'      => strip_tags( esc( get('thematic') ) ),
    );

    $rules = array(
      'title'         => array('required'),
      'description'   => array('required'),
      'type'          => array(),
      'thematic'      => array(),
    );
    $messages = array(
      'title'         => 'Please enter a title, this field is required',
      'description'   => 'Please enter a description, this field is required',
      'type'          => '',
      'thematic'      => '',
    );

    // some of the data is invalid
    if ( $invalid = invalid( $data, $rules, $messages ) ) {
      $alert = $invalid;
    } 

    // check the form token
    $csrf = get('csrf_token_proposalnew');

    if ( 
      !empty( $csrf ) && 
      csrf( $csrf ) &&
      empty( $alert )
      ) 
    {

      // everything is ok, let's try to create a new proposal

      try {

        // Create a new presentation
        if ( !empty( $data['title'] ) && !empty( $data['description'] && !empty( $presentations_page_uri ) ) ):
          //$new_presentation = $page->create( $presentations_page_uri . '/' . str::slug( $data['title'] ) , 'presentation' , $data );
          $new_presentation = page($presentations_page_uri)->children()->create(str::slug( $data['title'] ) , 'presentation' , $data );
        endif; 
        
        $data = array();

      } catch( Exception $e ) {
        echo 'Your proposal creation failed: ' . $e->getMessage();
      }


      try {

        // set the structure field "Speakers"
        $speakers_update = add_to_structure_speaker( $new_presentation , 'speakers' , $speakers_form );
  
        $success = 'New proposal saved';

      } catch( Exception $e ) {
        echo 'Your speakers structure field  failed: ' . $e->getMessage();
      }




      if ( $new_presentation && $speakers_update ):
        $success = 'New proposal saved';
      endif;


    }
  }

  return array(
    'types'       => $types,
    'thematics'   => $thematics,
    'speakers'    => $speakers,
    'alert'       => $alert,
    'data'        => $data,
    'success'     => $success,
  );

};


/**
* Add a new element to a kirby structure field
* @param string $page
* @param string $field
* @param array $data
*/
function add_to_structure_speaker( $page, $field, $data = array() ) {
  $fieldData = $page->$field()->yaml();
  $fieldData[] = $data;
  $fieldData = yaml::encode( $fieldData );
  try {
    $update_structure = $page->update( array( $field => $fieldData ) );
    return $update_structure;
  } catch( Exception $e ) {
    return $e->getMessage();
  }
}


There is just a little difference with my previous code post. page(uri) is replaced by $page in the “add_to_structure_speaker” function parameter, but the result is the same.

Hello,

I’ve made some new test to try to solve my problem.

My add_to_structure_speaker() function works if I replace the first parameter by a hardcoded uri.

So,
This doesn’t work and create two txt files (see explanation above):

$new_presentation = page($presentations_page_uri)->children()->create( str::slug( $data['title'] ) , 'presentation' , $data );
add_to_structure_speaker(  page( $new_presentation->uri() ) , 'speakers' , $speaker );

… and var_dump($new_presentation) > return a page object as expected.

This works and create only one txt file with structure field added:

// the "test" page  exist already
add_to_structure_speaker(  page( 'presentations/test' ) , 'speakers' , $speaker );

Can’t understand why :-/

If you want, you can send me a zipped version of your project, so that I can have a look: sonja@getkirby.com

Done :slight_smile: I’ve just sent you an email. Thx

I think we don’t need the $page->update() in your case and can pass the speaker data at page creation. I’ll send you an email later.

Edit:

We’ve boiled the problem down to using the $page->update() method right after page creation when the modules plugin is installed and a module is added to /site/modules.