Adding frontend form POST data to a structure field

Hello there,

I am trying to implement a way to add new elements to a structure field from the frontend,
when logged-in users want to apply for a seminar our customer is offering.

I figured that it could be done by simply taking the existing structure field, turning it into an array and appending to it the new data.

Here’s the code from the controller:

$applications = $page->parent()->seminarapplications()->toStructure()->toArray();

$applications[] = [
                    'userid' => $kirby->user()->id(),
                    'firstname' => $kirby->user()->content()->firstname(),
                    'lastname' => $kirby->user()->content()->lastname(),
                    'birthday' => $kirby->user()->content()->birthday(),
                    'email' => $kirby->user()->email(),
        
                ];

                $kirby->impersonate('kirby');
                $page->parent()->update([
                    'seminarapplication' => $applications,
                ]);

but using this I get the following error.

I already increased the memory limit in my php.ini by 200%.
It might be a memory leak issue that I am unknowingly causing here.
But I am not sure how or where I am making the mistake.

Can anyone help me?

You have to yaml encode the data:

$page->parent()->update([
  'seminarapplication' => Data::encode($applications, 'yaml'),
]);

And instead of calling two methods on the field, you can directly convert the value to an array:

$applications = $page->parent()->seminarapplications()->yaml();

Instead of the repeated call to $kirby->user(), store the user in a variable:

$user = $kirby->user();
$applications[] = [
  'userid'    => $user->id(),
  'firstname' => $user->content()->firstname(),
  'lastname'  => $user->content()->lastname(),
  'birthday'  => $user->content()->birthday(),
  'email'     => $user->email(),
        
];

Thanks a lot for the quick reply!
I didn’t know that ->yaml() would turn structure data into an array. Thanks for the tip!

However I am still getting the allowed memory size exhausted error.

my controller until the part I am act

<?php

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


    $applications = $page->parent()->seminarapplications()->yaml();

    $error = false;
    $newParticipants = false;
    $authcode = '';
    $newApplication = [];

  if ($kirby->request()->is('POST') && get('seminarform')) {

    try {
      
        if($kirby->user()->role() == 'client' || $kirby->user()->role() == 'client-company'){

            if($kirby->user()->role()->id() == 'client'){
                
                $user = $kirby->user();
                $applications[] = [
                  'userid'    => $user->id(),
                  'firstname' => $user->content()->firstname(),
                  'lastname'  => $user->content()->lastname(),
                  'birthday'  => $user->content()->birthday(),
                  'email'     => $user->email(),
                        
                ];

                $kirby->impersonate('kirby');
                $page->parent()->update([
                    'seminarapplications' => Data::encode($applications, 'yaml')
                ]);

            }

Could something else be causing the memory issue?

Could you provide more information about your environment: Kirby version, PHP version etc.

Also, how many items are in the array?

Kirby version is 3.6
PHP is 8.0.8
Using MAMP version 6.6

The structure field I am getting and want to add to currently has only 1 item with 4 fields.

with each application I’d add another item with the same 4 entries.

Hm, I have no idea what could be causing this.

When I test the code in a Starterkit (3.6.1.1) with the social field in the about page, it works flawlessly.

Maybe you can test this as well, so that we know the code works in general. In the about template, add this code somewhere:

<?php

$social = $page->social()->yaml();

$social[] = [
  'url'      => 'https://facebook.com',
  'platform' => 'facebook',

];
$kirby->impersonate('kirby');
$page->update(['social' => Data::encode($social, 'yaml')]);

Yup it does seem to work just fine in the starterkit.
Then there might be some deeper issue here.

I still suspect that I have created a memory leak somewhere,
but I’ve got no idea how to even look for that :confused:

My full seminarpage template:

<?php snippet('header', array("default" => true)) ?>

	<article class="viewDefault viewSeminarpost">



		<header>
			<div>
				<figure style="background-position: <?= $page->posteralignment()->value() ?>" class="lazy employeeImg" <?php if ($img = $page->posterimage()->toFile()): ?> data-src="<?= $img->url() ?>"<?php endif; ?> ></figure>
				<div class="headlines">
					<h1><?= $page->title() ?></h1>
				</div>
			</div>
		</header>
		<div>
			<div class="tags">
				<?php
					if ($page->categories()):
				?>
					<small >
						<b>Tags:</b> 
						<?php 
							foreach ($page->categories()->split() as $category): 
								
							?> <a class="newsTags"><?= $site->find('news')->categories()->toStructure()->findBy("autoid", $category)->name() ?></a><?php endforeach; ?></small>
					<?= snippet("socialbuttons") ?>
				<?php
					endif;
				?>
			</div>

		<div class="seminarInfo">
			<div class="seminarDate">
				<strong>Findet statt:</strong> 
				<p><?= $page->date()->toDate('d.m.Y') ?> | <?php if($page->starttime()->isNotEmpty()): ?> Beginn: <?= $page->starttime()->toDate('H:i') ?> <?php endif; ?> <?php if($page->endtime()->isNotEmpty()): ?> Schluss: <?= $page->endtime()->toDate('H:i') ?> <?php endif; ?> </p>
				<?php if($page->moredatestoggle()->isTrue() && $page->moredates()->isNotEmpty()): ?>
					<?php foreach($page->moredates()->toStructure() as $date ): ?>
						<p><?= $date->date()->toDate('d.m.Y') ?> | <?php if($page->starttime()->isNotEmpty()): ?> Beginn: <?= $page->starttime()->toDate('H:i') ?> <?php endif; ?> <?php if($page->endtime()->isNotEmpty()): ?> Schluss: <?= $page->endtime()->toDate('H:i') ?> <?php endif; ?></p>
					<?php endforeach; ?>
				<?php endif; ?>	
			</div>

			<div class="seminarLocation">
				<strong>Veranstaltungsort:</strong> 
				<div>
					<?php $location = $site->addresses()->toStructure()->findBy("autoid", $page->location()->value()); ?>			
					<p><?= $location->street() ?> <?= $location->housenumber() ?></p>
					<p><?= $location->postal() ?> <?= $location->city() ?></p>
					<p><?= $page->room() ?></p>
				</div>
			</div>

			<div class="seminarMax">	
				<strong>Maximale Teilnehmerzahl:</strong> <?= $page->maxparticipants() ?>
			</div>
		</div>

		<?= snippet("seminarform") ?>

		<?php if($page->teaser()->isNotEmpty()): ?>
			<section class="viewPagebuilderText right even none noimage">
				<div class="text viewText left"><?= $page->teaser()->kt() ?></div>
			</section>
		<?php endif; ?>

		<section class="seminarContents blueBG">
			<h3><?= $page->contentsheadline()->html() ?></h3>
			<?= $page->contents()->list() ?>
		</section>

		<section class="seminarContents">
			<h3><?= $page->participantsheadline()->html() ?></h3>
			<?= $page->participants()->list() ?>
		</section>

		<section class="seminarContents blueBG">
			<h3><?= $page->speakerheadline()->html() ?></h3>
			<?php $speakers = $site->find('seminare')->speakers()->toStructure()->filterBy("autoid", "in", $page->speaker()->toArray()) ?>
			<?php foreach($speakers as $speaker): ?>
				<div class="seminarSpeaker">
				<div class="speakerPortrait">	
				<figure class="lazy speakerImg" <?php if ($img = $speaker->image()->toFile()): ?> data-src="<?= $img->url() ?>"<?php endif; ?> ></figure>
				<h4><?= $speaker->title() ?> <?= $speaker->firstname() ?> <?= $speaker->lastname() ?></h4>
				</div>
				<div><?= $speaker->qualifications()->list() ?></div>
					<?php if($speaker->link()->isNotEmpty()): ?> 
						<a href="<?= $speaker->link()->html() ?>"><?= $speaker->linktext()->html() ?></a>
					<?php endif; ?>
				</div>
			<?php endforeach; ?>
		</section>

		<?= snippet("pagebuilder") ?>


	</article>

<?php snippet('footer') ?>

the seminarform snippet:



<!-- TODO: Check whether registration has already happened for this user -->

<section class="viewSeminarform">
    <?php
        if($kirby->user()):
    ?>

        <?php 
            if($page->children()->findBy('userid', $kirby->user()->id()) && $kirby->user()->role()->id() == 'client-company' ):
        ?>
            <div class="alreadyapplied"><?= $page->parent()->alrappliedcomp()->kt() ?></div>
        
        <?php 
            elseif($page->children()->findBy('userid', $kirby->user()->id()) && $kirby->user()->role()->id() == 'client' ):
        ?>
            <div class="alreadyapplied"><?= $page->parent()->alrapplied()->kt() ?></div>
        <?php 
            endif; 
        ?>

        <form id="seminarform<?= $page->autoid()->html() ?>" name="seminarform" class="seminarform" method="post" action="<?= $page->url() ?>">
            
            <?php if($kirby->user()->role()->id() == "client-company"): ?>
                <div id="participantsContainer" name="participantsContainer">
                    <div class="participantFields">

                        <h3>Teilnehmer</h3>

                        <div class="seminarforminput">
                            <label for="participants[0][firstname]" ><?= $site->find('seminare')->firstnamefield()->html() ?></label>
                            <input required id="participants[0][firstname]" type="text" name="participants[0][firstname]"  />
                        </div>


                        <div class="seminarforminput">
                            <label for="participants[0][lastname]" ><?= $site->find('seminare')->lastnamefield()->html() ?></label>
                            <input required id="participants[0][lastname]" type="text" name="participants[0][lastname]"  />
                        </div>


                        <div class="seminarforminput">
                            <label for="participants[0][birthday]" ><?= $site->find('seminare')->birthday()->html() ?></label>
                            <input required id="participants[0][birthday]" type="date" name="participants[0][birthday]"  />
                        </div>


                        <div class="seminarforminput">
                            <label for="participants[0][email]" ><?= $site->find('seminare')->email()->html() ?></label>
                            <input id="participants[0][email]" type="email" name="participants[0][email]"  />
                        </div>


                    </div>
                </div>
            

            <input type="button" onclick="addParticipants();" id="addPartButton" value="<?= $site->find('seminare')->addbutton()->html() ?>" />
            
            <?php endif; ?>

            <div class="seminarformsend">
                <input type="submit" name="seminarform" value="<?= $site->find('seminare')->submitbutton()->html() ?>" onsubmit="console.log(<?= get('participantsContainer') ?>)">
            </div>

        </form>
    <?php
        else:
    ?>
        <div><?= $site->find('seminare')->registerinfo()->kt() ?></div>
    <?php
        endif
    ?>

</section>

<?php
    if($kirby->user()):
?>
    <?php if($kirby->user()->role()->id() == "client-company" || $kirby->user()->role()->id() == "admin"): ?>
        <script>

            var i = 1;
            
            function addParticipants(){

                var x = document.getElementById("participantsContainer");
   
                var new_field = document.createElement('div')
                
                new_field.classList.add('participantFields')
                new_field.innerHTML = `

                                <h3>Weiterer Teilnehmer ${i}</h3> 

                                <div class="seminarforminput">
                                    <label for="participants[${i}][firstname]" ><?= $site->find('seminare')->firstnamefield()->html() ?></label>
                                    <input required id="participants[${i}][firstname]" type="text" name="participants[${i}][firstname]"  />
                                </div>


                                <div class="seminarforminput">
                                    <label for="participants[${i}][lastname]" ><?= $site->find('seminare')->lastnamefield()->html() ?></label>
                                    <input required id="participants[${i}][lastname]" type="text" name="participants[${i}][lastname]"  />
                                </div>


                                <div class="seminarforminput">
                                    <label for="participants[${i}][birthday]" ><?= $site->find('seminare')->birthday()->html() ?></label>
                                    <input required id="participants[${i}][birthday]" type="date" name="participants[${i}][birthday]"  />
                                </div>


                                <div class="seminarforminput">
                                    <label for="participants[${i}][email]" ><?= $site->find('seminare')->email()->html() ?></label>
                                    <input required id="participants[${i}][email]" type="email" name="participants[${i}][email]"  />
                                </div>
                            `;
                            
                var pos = x.childElementCount;

                x.appendChild(new_field)

                i++ 
            }
        </script>
    <?php endif; ?>
<?php endif; ?>

My seminarpage controller:

<?php

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


    $applications = $page->parent()->seminarapplications()->yaml();

    $error = false;
    $newParticipants = false;
    $authcode = '';
    $newApplication = [];

  if ($kirby->request()->is('POST') && get('seminarform')) {

    try {
      
        if($kirby->user()->role() == 'client' || $kirby->user()->role() == 'client-company'){

            if($kirby->user()->role()->id() == 'client'){
                $user = $kirby->user();
                $applications[] = [
                'userid'    => $user->id(),
                'firstname' => $user->content()->firstname(),
                'lastname'  => $user->content()->lastname(),
                'birthday'  => $user->content()->birthday(),
                'email'     => $user->email(),
                        
                ];

                $kirby->impersonate('kirby');
                $page->parent()->update([
                    'seminarapplications' => Data::encode($applications, 'yaml')
                ]);

            }
        }
        else{
            $error = true;
        }

    } catch (Exception $e) {
      $error = true;
    } 

}
  

  return [
    'error' => $error,
    'authcode' => $authcode
  ];

};

The problem is in your controller, because you have to assign the field values not the field objects:

$applications[] = [
   'userid'    => $user->id(),
   'firstname' => $user->content()->firstname()->value(),
   'lastname'  => $user->content()->lastname()->value(),
   'birthday'  => $user->content()->birthday()->value(),
   'email'     => $user->email(),
];

An improvement:

Move the variable

$user = $kirby->user();

up to the other variables, then use it when comparing roles as well.

What I don’t understand is that you collect data via the form, but then don’t use the data from the form. But I don’t have to understand everything :wink:

1 Like

Oh what a simple mistake that turned out to be xD
Thanks a lot, now it works just as intended!

About collecting data via the form:
the controller and submitting of data isn’t complete yet.

The idea is for 2 user roles.
One who applies to my customer’s seminars himself and only himself.
for this user the form fields won’t appear, he’ll just get a “register now” button.
Since all of his data is already collected when he registers, hence the usage of $kirby->user() instead of the form data.

And the other is intended for companies and institutes to apply their employees.
They’ll get to fill out the form fields and for each of their employees,
I’ll create another element to the structure field, together with the userid of the company.
That’s where my next challenge will be.

Again, thank you very much for your time and help!

Sorry, I totally overlooked this error yesterday, which is quite common when assigning values to arrays in these contexts.

I’m sorry, I have one more question.
Since I am gathering multiple participants with the form fields that have to be filled out by company clients I’d like to iterate through each of the entries.
I collect all of them in an array called “participants”.
In the controller however I cannot seem to access $_POST[‘participants’]

at least this here won’t return me an array with all the added participants:

foreach($_POST['participants'] as $participant){
                $applications[] = [
                  'userid'    => $user->id(),
                  'firstname' => $participant->firstname,
                  'lastname'  => $participant->lastname,
                  'birthday'  => $participant->birthday,
                  'email'     => $participant->email,
                        
                ];
              }

              $kirby->impersonate('kirby');
                $page->update([
                    'seminarapplications' => Data::encode($applications, 'yaml')
                ]);

I don’t get any errors but the seminarapplications structure field remains the same.

What do you get if you dump $_POST?

Also, you shouldn’t never update the page for each array item in the loop, but add each item to the $applicants array and then update with all items once.

(And since you are not storing the updated page in a new variable, this won’t work anyway.)

I thought I was doing just that with the foreach?
I changed it a little just in case.

$postdata = $_POST;

elseif($user->role()->id() == 'client-company'){
              foreach($postdata['participants'] as $participant):
                $applications[] = [
                  'userid'    => $user->id(),
                  'firstname' => $participant->firstname,
                  'lastname'  => $participant->lastname,
                  'birthday'  => $participant->birthday,
                  'email'     => $participant->email,
                        
                ];
              endforeach;

              $kirby->impersonate('kirby');
                $updatedpage = $page->update([
                    'seminarapplications' => Data::encode($applications, 'yaml')
                ]);

dumping $postdata returns me my array

Array
(
    [participants] => Array
        (
            [0] => Array
                (
                    [firstname] => Daquan
                    [lastname] => Vasquez
                    [birthday] => 1988-04-11
                    [email] => cibopoxon@mailinator.com
                )

            [1] => Array
                (
                    [firstname] => Megan
                    [lastname] => David
                    [birthday] => 2019-05-08
                    [email] => qycaxigubi@mailinator.com
                )

            [2] => Array
                (
                    [firstname] => Gillian
                    [lastname] => Ortiz
                    [birthday] => 1987-09-24
                    [email] => vyhenape@mailinator.com
                )

            [3] => Array
                (
                    [firstname] => Ivan
                    [lastname] => Scott
                    [birthday] => 1992-11-10
                    [email] => wopefamad@mailinator.com
                )

        )

    [seminarform] => Jetzt anmelden
)

Do I have to return the $updatedpage as well?

You are using arrow syntax here to access array items which won’t work, of course. Should be

$participant['firstname']

etc.

So that was it!

Thanks again.
You’ve really been helping me out a lot these past few weeks.