Contact-form plugin

QA

  • In the BO of Singapore, set the site email to a valid test mailbox
    • Open the public site, go to the contact page, and send the contact form: check that the email is received
    • Try to send another contact form without the recaptcha: it should fail
  • Follow the instructions, install a new local website, then set the site email to a valid test mailbox
    • Open the public site, go to the contact page, and send the contact form: check that the email data is correct in the log

Implementation

Unlike the other plugins, this is not something for the BO. It is for the public site. Our plugin needs 3 new things:

  1. Expose assets for the public website;
  2. Insert tags in the <head> of liquid templates;
  3. Expose a public API.

Expose assets for the public website

New hook

In packages/plugin-lib/types/backend-plugin-types.d.ts, the PluginApi interface, add:

setPublicAssetsDirectory(directory: string): void;

In plugin-loader.ts, the Plugin interface:

  • declare the return value : void on other methods when missing
  • rename urlPath to publicDirName
  • add:
publicAssetsDir?: string;

In plugin-api.ts, the createPluginApi function must have a new parameter plugin: Plugin, and then a new API must be created for each plugin.

Save the directory in plugin.publicAssetsDir. The last call of setPublicAssetsDirectory overwrites previous calls.

The public assets middleware

In static-middlewares.ts:

  • rename themeAssetsMiddleware to publicAssetsMiddleware
  • append || !["GET", "HEAD"].includes(req.method) in the first if of publicAssetsMiddleware
  • in the same file, replace req.method !== "GET" by !["GET", "HEAD"].includes(req.method) (there are 2 occurrences)

Now, in publicAssetsMiddleware:

  • take example on site-app and add: else if (path.startsWith("/assets/plugin/"))
  • in the if implementation, take the pluginPublicDirName in /assets/plugin/-pluginPublicDirName-here-/...
  • get the plugin instance in siteContext.plugins using pluginPublicDirName as the key. If there isn't, it's a 404.
  • assign plugin.publicAssetsDir to baseDir. If there isn't, it's a 404.
  • for pathPrefix:
  pathPrefix = `/assets/plugin/${plugin.publicDirName}/${plugin.plugin.version}/`;

Insert tags in the <head> of liquid templates

New hook

In packages/plugin-lib/types/backend-plugin-types.d.ts, the PluginApi interface, add:

addHeadTag(...htmlTags: string[]): void;

In site-context.types.ts, in SiteContext, just after plugins, add:

  headTags: string[];

In create-site-context.ts, add the new headTags with an empty array.

In plugin-api.ts, implement addHeadTag by pushing the content into siteContext.headTags.

Use in the headTags liquid filter

In head-tags-filter.ts, at the end of the current implementation, append the content of siteContext.headTags.

Expose a public API

New hook

In packages/plugin-lib/types/backend-plugin-types.d.ts, the PluginApi interface, add:

setPublicApiHandler(handler: PublicApiHandler): void;

With:

import type { Request, Response } from "express";
// ...

export type PublicApiHandler = (pluginSiteContext: PluginSiteContext, req: Request, res: Response, relativePath: string) => Promise<void> | void;

Notice: PluginSiteContext is implemented in #84 (closed) . I suggest that we add it in the Plugin interface (plugin-loader.ts).

In plugin-loader.ts, the Plugin interface, add:

publicApiHandler?: PublicApiHandler;

In plugin-api.ts, in createPluginApi: save the handler in plugin.publicApiHandler. The last call of setPublicApiHandler overwrites previous calls.

A new middleware

In app/server/src/express/, add a new file public-api-middlewares.ts. Copy the publicAssetsMiddleware function and its imports. Then modify the code:

  • The function is named pluginPublicApiMiddleware
  • It doesn't check req.method (all HTTP methods are allowed)
  • the path must begin with /api/plugin/ (or call next() and return)
  • take the pluginPublicDirName in /api/plugin/-pluginPublicDirName-here-/...
  • get the plugin instance in siteContext.plugins using pluginPublicDirName as the key. If there isn't, it's a 404.
  • if plugin.publicApiHandler is undefined, then it's a 404.
  • take the relativePath in /api/plugin/:pluginPublicDirName/:relativePath
  • call await plugin.publicApiHandler(pluginSiteContext, req, res, relativePath)

Search where is registered publicAssetsMiddleware and register publicApiMiddleware in the application, the same way.

Implement the plugin

Create a plugin

Create a plugin @paroicms/plugin-contact-form in a new directory plugins/contact-form/.

Extract the UI from Site-App

  • Move the content of the app/site-app/src/contact-form/ folder in the plugin.
  • Take the "contactForm" section in the locales too
  • Ensure that the Site-App can be still compiled

Configure a SolidJS application in the new plugin directory and build it.

In plugin.cjs:

  • use api.setPublicAssetsDirectory in order to expose the bundled SolidJS application.
  • use api.addHeadTag in order to insert <script> and <link> (CSS file) tags to the themes

Extract the controller

Move the directory app/server/src/modules/public-api/mail into the plugin directory.

In public-api-controller.ts, search submit-contact-form. Delete this function.

In plugin.cjs, use api.setPublicApiHandler in order to replace the controller.

Our moved code will need several things:

  • Create a new TS sub-project with a tsconfig.backend.json like in this ticket: #84 (closed)
  • getSiteFieldValue and sendMail must be added in PluginSiteContext

Last things

Add "@paroicms/plugin-contact-form" in the site-schema.json and the package.json of Seoul and Singapore.

Edited by Nathan GNANKADJA