boboy
boboy

Hello, I'm new to OctoberCMS, managed to create some plugins and override some other plugins partials but I have now a more complex problem to handle.

I'm using rainlab-page for building static pages and martin-forms for creating forms.

I need to display a contact form in the footer (of all pages) which includes a profile picture of a person to contact, that person being specific for every static pages, and should send the contact form to that person by email.

I set up a custom plugin that allow to create Contacts in the back-office, now I want to be able, for each static pages created with rainlab-page, to pick a Contact from that list, preferably by adding it to the top-level parameters (like Title and URL), as a dropdown list of Contacts.

If I edit the plugins/rainlab/pages/classes/page/fields.yaml I can add a "viewBag[contact]" and it will appear where it should, now how to make that happen by replacing the plugin by a custom plugin extending rainlab-pages and make the field generate a select list from my custom plugin's Contact model.

Next step, how to extend martin-forms plugin to set the Notification Settings -> Recipients to the Contact set in the Static page.

Do I even need to create custom plugins or can I just override everything needed from the theme?

The Empty AJAX Form component is inserted in the layout page, and referenced in the footer.htm partial.

Any lead would be greatly appreciated! Thanks

Last updated

boboy
boboy

I managed to do some of the requirements:

In my Contact plugin, in the Plugin class I add a boot method:

public function boot()
    {
        Page::extend(function ($model) {
            $model->addDynamicMethod('getContactOptions', function () {
                return Contact::all()->pluck('company_name');
            });
        });
        Event::listen('backend.form.extendFields', function (WidgetBase $widget) {
            // Only for the Pages controller
            if (!$widget->getController() instanceof \RainLab\Pages\Controllers\Index) {
                return;
            }

            $widget->addFields([
                'viewBag[contact]' => [
                    'label' => 'Contact',
                    'type' => 'dropdown',
                    'options' => 'getContactOptions',
                ]
            ]);
        });
    }

This adds the select field after the other fields (Title, URL and the actions like preview, save, etc.).

It is properly persisted in the page viewBag upon saving.

Then in /themes/mytheme/partials/contactForm/default.htm which is the partial I created to override martin-forms plugin's partial (the martin-forms plugin's component "Empty AJAX Form" inserted in my layout has the "contactForm" alias), I add a onStart function in the PHP section:

description = "contact-form-override"

==
function onStart()
{
    $this->contact = Boboy\Contact\Models\Contact::find($this->contact + 1);
}
==
<form data-request="{{ __SELF__ }}::onFormSubmit">
    {{ form_token() }}
    <input type="hidden" name="contact" value="{{ this.page.contact.email }}" />
    <div id="{{ __SELF__ }}_forms_flash"></div>

Then I'm able to insert a hidden input field that has the associated contact's email as its value.

So most of the work here is done but now I still don't know how to override martin-forms plugin to fetch the mail recipient from the form's content.

This seems to be the trickiest part, if any experienced OctoberCMS developer could shed some light on this for me that would make my day.

Cheers!

mjauvin
mjauvin

Just use "onFormSubmit" in the contact form partial for the ajax handler and create a dynamic method for this handler in your page to override the recipient in the component before calling the component onFormSubmit handler.

mjauvin
mjauvin

something like

function onFormSubmit()
{
   $this->contactForm->setProperty('mail_recipients', [$this->contact->email] ) ;
   return $this->contactForm->onFormSubmit();
}

Last updated

boboy
boboy

Thanks for your help! I actually managed to tap into the martin.forms.beforeSaveRecord in the boot method of the plugin, like so:

Event::listen('martin.forms.beforeSaveRecord', function ($post, $elt) {
            $contact = Contact::where('email', $post['contact'])->first();
            if ($contact) {
                if (!isset($elt->getProperties()['mail_recipients'])) {
                    $recipientList = [];
                } else {
                    $recipientList = $elt->getProperties()['mail_recipients'];
                }
                array_push($recipientList, $post['contact']);
                $elt->setProperty('mail_recipients', $recipientList);
            } else {
                throw new AjaxException([
                    '#' . $elt->alias . '_forms_flash' => $elt->renderPartial($elt->property('messages_partial', '@flash.htm'), [
                    'status'  => 'error',
                    'type'    => 'danger',
                    'content' => 'This contact does not exist',
                ])]);
            }

        });

But your way is more straightforward, I added the event handler to the layout's PHP section (because the component is inserted at the layout level):

function onFormSubmit()
{
    $contact = Boboy\Contact\Models\Contact::find($this->page->apiBag['staticPage']->viewBag['contact'] + 1);
    $defaultContacts = $this->contactForm->getProperties()['mail_recipients'];
    if (!in_array($contact, $defaultContacts)) {
        array_push($defaultContacts, $contact);
        $this->contactForm->setProperty('mail_recipients', $defaultContacts) ;
    }
    return $this->contactForm->onFormSubmit();
}

This is actually much better because I managed to tap into the viewBag of the page, which makes me able to directly fetch the Contact's id from the page viewBag, the email shouldn't have to go through the front-end back to the back-end (I had to check the existence of the email provided in the form to prevent injections of unrelated emails) and some staff members might want to keep their email secret, now it's perfectly safe.

Thanks again!

mjauvin
mjauvin

Instead of:

 $defaultContacts = $this->contactForm->getProperties()['mail_recipients'];

you can use:

$defaultContacts = $this->contactForm->property('mail_recipients');
boboy
boboy

Oh okay, I tried "getProperty" and "properties", then took me an hour to find my way there, dumping the object in the response gives a HUGE result. I also forgot to check if 'mail_recipients' is null (when no recipients are set from the component's snippet), here is final code:

function onFormSubmit()
{
    $contact = Boboy\Contact\Models\Contact::find($this->page->apiBag['staticPage']->viewBag['contact'] + 1);
    $defaultContacts = $this->contactForm->property('mail_recipients');
    if ($defaultContacts === null) {
        $defaultContacts = [];
    }
    if (!in_array($contact, $defaultContacts)) {
        array_push($defaultContacts, $contact);
        $this->contactForm->setProperty('mail_recipients', $defaultContacts) ;
    }
    return $this->contactForm->onFormSubmit();
}

Thanks for the feedback!

boboy
boboy

Hello,

I now have a new problem.

When I try to go into the rainlab-pages plugin, on the Menus page, I get an error saying:

The model class RainLab\Pages\Classes\Menu must define a method getContactOptions() returning options for the 'viewBag[contact]' form field.

In both those type of page the Widget received is of type Backend\Widgets\Form.

I don't know what seems to be the problem here, if I change my code to this:

        $contactOptions = function ($model) {
            $model->addDynamicMethod('getContactOptions', function () {
                return Contact::all()->pluck('company_name');
            });
        };
        Page::extend($contactOptions);
        Menu::extend($contactOptions);

In order to add the method to both classes, I now get the error about the MenuItem class, problem is that this class does not have a extend method.

Don't know what to do here.

mjauvin
mjauvin

make sure the model is a Page in your boot method when you add the event handler (Event::listen('backend.form.extendFields',...)

mjauvin
mjauvin

i. e.

if (!$widget->model instanceof \RainLab\Pages\Classes\Page) {
   return;
}

Last updated

boboy
boboy
if (!$widget->getController() instanceof \RainLab\Pages\Controllers\Index ||
    !$widget->model instanceof Page) {
        return;
}

Solved the issue, thanks for your reactivity!

mjauvin
mjauvin

glad to hear that!

1-12 of 12