Controller logic inside plugin page builder

I have created a plugin page builder for a client which includes a form which can be put on different templates, currently trying to get it to work on the main contact form template as a block which can be added, it will also be able to be added to other templates.

In the plugin index.php file I have

‘blueprints’ => [
‘blocks/main-contact-form’ => DIR . ‘/blueprints/blocks/main-contact-form/main-contact-form.yml’,

‘snippets’ => [

‘blocks/main-contact-form’ => DIR . ‘/snippets/blocks/main-contact-form/main-contact-form.php’,

I am trying to add a controller for the form logic, but keep getting undefined variables as the controller not loading.

This is the controller extension in the plugin

‘controllers’ => [
‘maincontactform’ => require ‘controllers/maincontactform.php’
],

Template file I am trying to load the block/controller into is

templates/maincontactform.php

Not sure what I am doing wrong

If the form is in a block and the block could be anywhere, it would make more sense to have the for handling logic in the block, rather than in a page controller that only applies to a page type.

A page controller is loaded when a template is loaded, so content page txt file name, controller name and template name must be identical, i.e. mypage.txt, mypage.php controller and mypage.php template.

If the variables you define in the controller should be used in the block snippet, you have to explicitely pass them as arguments when calling the block snippet, i.e. in that case, you would have to call the block snippets explicitly, as you can see here: Blocks | Kirby CMS

Having said that, a lot of the logic of the form block could probably go into a block model: Block models | Kirby CMS

Hi @texnixe, you mention here moving form logic in the block, and you say it like it is not a big deal.

That would be a great solution if possible.
Maybe I am missing something, but I don’t see how could that be possible, without really heavy strategy and coding. How could we move form logic from controller to block?

How would you merge these two (from your Cookbook recipe)

The form

<?php if($success): ?>
        <div class="alert success">
            <p><?= $success ?></p>
        </div>
        <?php else: ?>
        <?php if (isset($alert['error'])): ?>
            <div><?= $alert['error'] ?></div>
        <?php endif ?>
        <form method="post" action="<?= $page->url() ?>">
            <div class="honeypot">
                <label for="website">Website <abbr title="required">*</abbr></label>
                <input type="url" id="website" name="website" tabindex="-1">
            </div>
            <div class="field">
                <label for="name">
                    Name <abbr title="required">*</abbr>
                </label>
                <input type="text" id="name" name="name" value="<?= esc($data['name'] ?? '', 'attr') ?>" required>
                <?= isset($alert['name']) ? '<span class="alert error">' . esc($alert['name']) . '</span>' : '' ?>
            </div>
            <div class="field">
                <label for="email">
                    Email <abbr title="required">*</abbr>
                </label>
                <input type="email" id="email" name="email" value="<?= esc($data['email'] ?? '', 'attr') ?>" required>
                <?= isset($alert['email']) ? '<span class="alert error">' . esc($alert['email']) . '</span>' : '' ?>
            </div>
            <div class="field">
                <label for="text">
                    Text <abbr title="required">*</abbr>
                </label>
                <textarea id="text" name="text" required>
                    <?= esc($data['text'] ?? '') ?>
                </textarea>
                <?= isset($alert['text']) ? '<span class="alert error">' . esc($alert['text']) . '</span>' : '' ?>
            </div>
            <input type="submit" name="submit" value="Submit">
        </form>
        <?php endif ?>

and Controller

<?php
return function($kirby, $pages, $page) {

    $alert = null;

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

        // check the honeypot
        if(empty(get('website')) === false) {
            go($page->url());
            exit;
        }

        $data = [
            'name'  => get('name'),
            'email' => get('email'),
            'text'  => get('text')
        ];

        $rules = [
            'name'  => ['required', 'minLength' => 3],
            'email' => ['required', 'email'],
            'text'  => ['required', 'minLength' => 3, 'maxLength' => 3000],
        ];

        $messages = [
            'name'  => 'Please enter a valid name',
            'email' => 'Please enter a valid email address',
            'text'  => 'Please enter a text between 3 and 3000 characters'
        ];

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

            // the data is fine, let's send the email
        } else {
            try {
                $kirby->email([
                    'template' => 'email',
                    'from'     => 'yourcontactform@yourcompany.com',
                    'replyTo'  => $data['email'],
                    'to'       => 'you@yourcompany.com',
                    'subject'  => esc($data['name']) . ' sent you a message from your contact form',
                    'data'     => [
                        'text'   => esc($data['text']),
                        'sender' => esc($data['name'])
                    ]
                ]);

            } catch (Exception $error) {
                if(option('debug')):
                    $alert['error'] = 'The form could not be sent: <strong>' . $error->getMessage() . '</strong>';
                else:
                    $alert['error'] = 'The form could not be sent!';
                endif;
            }

            // no exception occurred, let's send a success message
            if (empty($alert) === true) {
                $success = 'Your message has been sent, thank you. We will get back to you soon!';
                $data = [];
            }
        }
    }

    return [
        'alert'   => $alert,
        'data'    => $data ?? false,
        'success' => $success ?? false
    ];
};

Into one block?

All in one snippet:

<?php
$alert   = null;
$success = false;

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

    // check the honeypot
    if(empty(get('website')) === false) {
        go($page->url());
        exit;
    }

    $data = [
        'name'  => get('name'),
        'email' => get('email'),
        'text'  => get('text')
    ];

    $rules = [
        'name'  => ['required', 'minLength' => 3],
        'email' => ['required', 'email'],
        'text'  => ['required', 'minLength' => 3, 'maxLength' => 3000],
    ];

    $messages = [
        'name'  => 'Please enter a valid name',
        'email' => 'Please enter a valid email address',
        'text'  => 'Please enter a text between 3 and 3000 characters'
    ];

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

        // the data is fine, let's send the email
    } else {
        try {
            $kirby->email([
                'template' => 'email',
                'from'     => 'yourcontactform@yourcompany.com',
                'replyTo'  => $data['email'],
                'to'       => 'you@yourcompany.com',
                'subject'  => esc($data['name']) . ' sent you a message from your contact form',
                'data'     => [
                    'text'   => esc($data['text']),
                    'sender' => esc($data['name'])
                ]
            ]);

        } catch (Exception $error) {
            if(option('debug')):
                $alert['error'] = 'The form could not be sent: <strong>' . $error->getMessage() . '</strong>';
            else:
                $alert['error'] = 'The form could not be sent!';
            endif;
        }

        // no exception occurred, let's send a success message
        if (empty($alert) === true) {
            $success = 'Your message has been sent, thank you. We will get back to you soon!';
            $data = [];
        }
    }
}
?>
<?php if($success): ?>
	<div class="alert success">
		<p><?= $success ?></p>
	</div>
<?php else: ?>
	<?php if (isset($alert['error'])): ?>
		<div><?= $alert['error'] ?></div>
	<?php endif ?>
	<form method="post" action="<?= $page->url() ?>">
    <!--- here are your form fields -->
	</form>
<?php endif ?>
1 Like

Wow! It is good I asked!

I will start then working on it!

Thank you! :dove:

Hm, in a fast test of the code in a block, I am getting this error

Block error: "Cannot use object of type Kirby\Cms\Block as array"

Now I remember I got that error a year ago when I tried doing this and gave up after a while.
Block errors are a bit tricky, because they don’t feature what specific code is making the problem. Do you have a hint at what exactly this error is trying to tell me?

Hm, I know what the error means, but have no idea what could be causing this. Could you post your block snippet?

Hi Sonja, here it is

<?php 

$alert = null;
$success = false;

if ($kirby->request()->is('POST') && get('submit')) {
    // check the honeypot
    if (empty(get('contact_me_by_fax_only')) === false) {
        go($page->url());
        exit;
    }

    // Import fields data
    $data = [
        'name'  => get('name'),
        'email' => get('email'),
        'text'  => get('text')
    ];

    // Field rules
    $rules = [
        'name'  => ['required', 'min' => 3],
        'email' => ['required', 'email'],
        'text'  => ['required', 'min' => 3, 'max' => 3000],
    ];

    // Alert messages for the rules
    $messages = [
        'name'  => esc($site->labelAlertName()->or('Please enter a valid name')),
        'email' => esc($site->labelAlertEmail()->or('Please enter a valid email address')),
        'text'  => esc($site->labelAlertMessage()->or('Please enter a text between 3 and 3000 characters'))
    ];

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

    // the data is fine, let's send the email
    } else {
        try {
            $kirby->email([
                'template' => 'email',
                'from'     => esc($site->email()),
                'replyTo'  => $data['email'],
                'to'       => esc($site->email()),
                'subject'  => esc($data['name']) . ' ' . esc($site->labelEmailSubject()->or('sent you a message from your contact form')),
                'data'     => [
                    'text'   => esc($data['text']),
                    'sender' => esc($data['name'])
                ]
            ]);
        } catch (Exception $error) {
            $alert['error'] = esc($site->labelFormError()->or('The form could not be sent'));
        }

        // no exception occured, let's send a success message
        if (empty($alert) === true) {
            $success = esc($site->labelFormSuccess()->or('Your message has been sent, thank you. We will get back to you soon!'));
            $data = [];
        }
    }
}

?>

<a class="tm-contact" id="contact"></a>
<?php if($success): ?>
<div class="uk-alert-success" uk-alert>
    <p><?= $success ?></p>
</div>
<?php else: ?>
<?php if (isset($alert['error'])): ?>
<div class="uk-alert-danger" uk-alert>
    <p><?= $alert['error'] ?></p>
</div>
<?php endif ?>
<form class="uk-grid-small" method="post" action="<?= $page->url() ?><?php e($page->isHomePage(), '/') ?>#contact" uk-grid>
    <div class="tm-hon">
        <input type="checkbox" name="contact_me_by_fax_only" value="1" tabindex="-1" autocomplete="off">
    </div>
    <div class="uk-width-1-2@s">
        <label for="name">
        <?= $site->labelName()->html() ?> <abbr title="required">*</abbr>
        </label>
        <input class="uk-input" type="text" id="name" name="name" value="<?= esc($data['name'] ?? '') ?>" required>
        <?= isset($alert['name']) ? '<span class="uk-text-danger">' . html($alert['name']) . '</span>' : '' ?>
    </div>
    <div class="uk-width-1-2@s">
        <label for="email">
        <?= $site->labelEmail()->html() ?> <abbr title="required">*</abbr>
        </label>
        <input class="uk-input" type="email" id="email" name="email" value="<?= esc($data['email'] ?? '') ?>" required>
        <?= isset($alert['email']) ? '<span class="uk-text-danger">' . html($alert['email']) . '</span>' : '' ?>
    </div>
    <div class="uk-width-1-1">
        <label for="text">
        <?= $site->labelMessage()->html() ?> <abbr title="required">*</abbr>
        </label>
        <textarea class="uk-textarea uk-height-small" id="text" name="text" required><?= esc($data['text']?? '') ?></textarea>
        <?= isset($alert['text']) ? '<span class="uk-text-danger">' . html($alert['text']) . '</span>' : '' ?>
    </div>
    <div class="uk-width-1-1">
        <label><input class="uk-checkbox" id="privacy" name="privacy" type="checkbox" checked required> <?= $site->labelFormPrivacy()->or('I agree to the privacy policy') ?></label>
    </div>

    <div class="uk-width-1-2@s">
    <button class="uk-button uk-button-primary uk-margin-top" type="submit" name="submit" value="Submit"><?= $site->labelSubmit()->html() ?></button>
    </div>
</form>
<?php endif ?>

I tested that code in the page template now, and it works.

I know what the problem is, the $data variable. $data is an alias for $block to make blocks work with the old builder.

If you replace $data with for example $formData, all will be fine.

Bravooo :partying_face:

You are right, just tested with $formData and it works!
That’s just awesome :blush:

Thank you! :pray: