Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 56 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ Allow guests to create entries from your site’s front end.

## Requirements

_Guest Entries_ requires Craft CMS 4.0.0+ or 5.0.0+.
_Guest Entries_ 5.x requires Craft CMS 6.0.0+ and PHP 8.5+. See [UPGRADE.md](UPGRADE.md) if you are upgrading from an earlier version.

## Installation

You can install _Guest Entries_ from the [Plugin Store](https://plugins.craftcms.com/guest-entries) or with Composer.

### From the Plugin Store

Go to the **Plugin Store** in your project’s control panel (in an environment that allows [admin changes](https://craftcms.com/docs/4.x/config/general.html#allowadminchanges)), search for “Guest Entries,” then click **Install**.
Go to the **Plugin Store** in your project’s control panel (in an environment that allows admin changes), search for “Guest Entries,” then click **Install**.

### With Composer

Expand All @@ -26,7 +26,7 @@ cd /path/to/my-project
composer require craftcms/guest-entries -w

# Install the plugin with Craft:
php craft plugin/install guest-entries
php artisan craft:plugin:install guest-entries
```

## Settings
Expand All @@ -48,9 +48,6 @@ A basic guest entry template should look something like this:
{% endif %}
{% endmacro %}

{# Default value for the `entry` variable: #}
{% set entry = entry ?? null %}

<form method="post" action="" accept-charset="UTF-8">
{# Hidden inputs required for the form to work: #}
{{ csrfInput() }}
Expand All @@ -64,16 +61,16 @@ A basic guest entry template should look something like this:

{# Entry properties and custom fields: #}
<label for="title">Title</label>
{{ input('text', 'title', entry ? entry.title, { id: 'title' }) }}
{{ entry ? _self.errorList(entry.getErrors('title')) }}
{{ input('text', 'title', old('title'), { id: 'title' }) }}
{{ _self.errorList(errors.get('title')) }}

<label for="body">Body</label>
{{ tag('textarea', {
text: entry ? entry.body,
text: old('fields.body'),
id: 'body',
name: 'fields[body]',
}) }}
{{ entry ? _self.errorList(entry.getErrors('body')) }}
{{ _self.errorList(errors.get('fields.body')) }}

{# ... #}

Expand All @@ -82,7 +79,7 @@ A basic guest entry template should look something like this:
```

> **Note**
> The process of submitting data and handling success and error states is outlined in the [controller actions](https://craftcms.com/docs/4.x/dev/controller-actions.html) documentation.
> A failed submission redirects back to the page with the posted data and validation errors flashed to the session. Use `old()`, `errors` and `session().get('error')` in your template, as shown above. A successful submission redirects to the `redirect` param, and the page can read `session().get('success')`.

### Supported Params

Expand All @@ -96,14 +93,13 @@ Name | Notes | Required
`typeId` | Entry type ID to use. This may affect which custom fields are required. When absent, the first configured type for the specified section is used. |
`title` | Optional if the section has automatic title formatting enabled. | ✓
`slug` | Explicitly sets the new entry’s slug. |
`postDate` | Value should be processable by [`DateTimeHelper::toDateTime()`][api:date-time-helper] |
`expiryDate` | Value should be processable by [`DateTimeHelper::toDateTime()`][api:date-time-helper] |
`postDate` | Value should be processable by `CraftCms\Cms\Support\DateTimeHelper::toDateTime()` |
`expiryDate` | Value should be processable by `CraftCms\Cms\Support\DateTimeHelper::toDateTime()` |
`parentId` | Nest this entry under another. Invalid for channels and structures with a maximum depth of `1`. |
`siteId` | Create the entry in a specific site. |
`enabledForSite` | Whether the entry should be enabled in this site. The global `enabled` setting is configurable by administrators, so this alone will not immediately publish something. |
`fields[...]` | Any [custom fields](#sending-custom-fields) you want guests to be able to populate. |

[api:date-time-helper]: https://docs.craftcms.com/api/v4/craft-helpers-datetimehelper.html#method-todatetime
[docs:field-types]: https://craftcms.com/docs/4.x/fields.html#field-types

### Form Tips
Expand All @@ -115,7 +111,7 @@ The plugin determines what section the new entry is created in by looking for a
Granted you will already have a section (or at least a section _handle_), the easiest way to do this is via the section model:

```twig
{% set targetSection = craft.app.sections.getSectionByHandle('resources') %}
{% set targetSection = Sections.getSectionByHandle('resources') %}
{% set entryTypes = targetSection.getEntryTypes() %}

{# Select a single type, identified by its handle: #}
Expand All @@ -133,7 +129,7 @@ Custom field data should be nested under the `fields` key, with the field name i
<input
type="text"
name="fields[myCustomFieldHandle]"
value="{{ entry ? entry.myCustomFieldHandle }}">
value="{{ old('fields.myCustomFieldHandle') }}">
```

If entries in the designated section are enabled by default, validation will occur on _all_ custom fields, meaning those marked as _required_ in the entry type’s field layout must be sent with the submission. Refer to the [field types][docs:field-types] documentation to learn about the kinds of values that Craft accepts.
Expand All @@ -143,33 +139,30 @@ If entries in the designated section are enabled by default, validation will occ

#### Validation Errors

If there are validation errors on the entry, the page will be reloaded with the populated [`craft\elements\Entry`][api:entry] object available under an `entry` variable. You can access the posted values from that object as though it were a normal entry—or display errors with [`getErrors()`][yii:base-model-getErrors], [`getFirstError()`][yii:base-model-getFirstError], or [`getFirstErrors()`][yii:base-model-getFirstErrors].
If there are validation errors on the entry, the user is redirected back to the page and the submitted data is flashed to the session. In your template:

> **Note**
> The `entry` variable can be renamed with the “Entry Variable Name” [setting](#settings) in the control panel. This might be necessary if you want to use a form on an entry page that already injects a variable of that name.
- `old('title')` and `old('fields.body')` return the submitted values, to repopulate the form;
- `errors.get('title')`, `errors.has('title')` and `errors.all()` return the validation errors, keyed by attribute ;
- `session().get('error')` is the error message.

[api:entry]: https://docs.craftcms.com/api/v4/craft-elements-entry.html
[yii:base-model-getErrors]: http://www.yiiframework.com/doc-2.0/yii-base-model.html#getErrors()-detail
[yii:base-model-getFirstError]: http://www.yiiframework.com/doc-2.0/yii-base-model.html#getFirstError()-detail
[yii:base-model-getFirstErrors]: http://www.yiiframework.com/doc-2.0/yii-base-model.html#getFirstErrors()-detail
The `entry` variable from earlier versions is no longer available. The “Entry Variable Name” [setting](#settings) now only names the key that holds the entry's data in an Ajax error response.

#### Redirection

Send a `redirect` param to send the user to a specific location upon successfully saving an entry. In the example above, this is handled via the [`redirectInput('...')` function](https://craftcms.com/docs/4.x/dev/functions.html#redirectinput). The path is evaluated as an [object template](https://craftcms.com/docs/4.x/dev/controller-actions.html#after-a-post-request), and can include properties of the saved entry in `{curlyBraces}`.
Send a `redirect` param to send the user to a specific location upon successfully saving an entry. In the example above, this is handled via the `redirectInput('...')` function. The path is evaluated as an object template, and can include properties of the saved entry in `{curlyBraces}` (for example `{id}`, `{uid}` or `{url}`).

### Submitting via Ajax

If you submit your form [via Ajax](https://craftcms.com/docs/4.x/dev/controller-actions.html#ajax) with an `Accept: application/json` header, a JSON response will be returned with the following keys:
If you submit your form via Ajax with an `Accept: application/json` header, a JSON response will be returned. A successful submission has a `200` status, and a failed one has a `400` status.

- `success` _(boolean)_ – Whether the entry was saved successfully
- `errors` _(object)_ – All of the validation errors indexed by field name (if not saved)
- `id` _(string)_ – the entry’s ID (if saved)
- `title` _(string)_ – the entry’s title (if saved)
- `authorUsername` _(string)_ – the entry’s author’s username (if saved)
- `dateCreated` _(string)_ – the entry’s creation date in ISO 8601 format (if saved)
- `dateUpdated` _(string)_ – the entry’s update date in ISO 8601 format (if saved)
- `postDate` _(string, null)_ – the entry’s post date in ISO 8601 format (if saved and enabled)
- `url` _(string, null)_ – the entry’s public URL (if saved, enabled, and in a section that has URLs)
Key | Notes
--- | -----
`message` | “Entry saved.” or “Couldn’t save entry.”
`modelName` | The key holding the entry data: `entry` on success, or the “Entry Variable Name” setting on failure.
`modelClass` | The entry’s class name.
`<modelName>` | The entry’s data, as an object.
`modelId` | The entry’s ID (success only).
`errors` | All of the validation errors, indexed by attribute name (failure only).

### Viewing Entries

Expand All @@ -188,7 +181,7 @@ Entries in sections with URLs can be viewed immediately, with this `redirect` pa

#### Disabled by Default

In order to display an entry that is _disabled_, you will need to set up a custom [route](https://craftcms.com/docs/4.x/routing.html#advanced-routing-with-url-rules)…
In order to display an entry that is _disabled_, you will need to set up a custom route in your project’s `config/craft/routes.php`…

```php
<?php
Expand Down Expand Up @@ -224,82 +217,49 @@ This query selects only `disabled` entries so that the “preview” is invalida

## Events

_Guest Entries_ augments the normal [events](https://craftcms.com/docs/4.x/extend/events.html) emitted during the entry lifecycle with a few of its own, allowing developers to customize the submission process.

The following snippets should be added to your plugin or module’s `init()` method, per the official [event usage instructions](https://craftcms.com/knowledge-base/custom-module-events).
_Guest Entries_ dispatches a few Laravel events of its own, allowing developers to customize the submission process. Register listeners in your plugin’s `boot()` method, a service provider (such as `App\Providers\AppServiceProvider`), or a plugin’s `$events` property. Listeners receive the event only: there is no `sender`.

### The `beforeSaveEntry` event
### `SavingGuestEntry`

Plugins can be notified _before_ a guest entry is saved, using the `beforeSaveEntry` event. This is also an opportunity to flag the submission as spam, and prevent it being saved:
Dispatched _before_ a guest entry is saved. This is an opportunity to flag the submission as spam (it is discarded, but the submitter sees a success response), or to reject it by setting `isValid` to `false`:

```php
use craft\helpers\StringHelper;
use craft\guestentries\controllers\SaveController;
use craft\guestentries\events\SaveEvent;
use yii\base\Event;

// ...

Event::on(
SaveController::class,
SaveController::EVENT_BEFORE_SAVE_ENTRY,
function(SaveEvent $e) {
// Get a reference to the entry object:
$entry = $e->entry;

// Perform spam detection logic of your own design:
if (StringHelper::contains($entry->title, 'synergy', false)) {
// Set the event property:
$e->isSpam = true;
}
use CraftCms\GuestEntries\Events\SavingGuestEntry;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;

Event::listen(function (SavingGuestEntry $event): void {
// Perform spam detection logic of your own design:
if (Str::contains($event->entry->title, 'synergy', ignoreCase: true)) {
$event->isSpam = true;
}
);
});
```

### The `afterSaveEntry` event
### `SavedGuestEntry`

Plugins can be notified _after_ a guest entry is saved, using the `afterSaveEntry` event:
Dispatched _after_ a guest entry is saved. It is also dispatched for submissions flagged as spam, in which case `isSpam` is `true`:

```php
use craft\guestentries\controllers\SaveController;
use craft\guestentries\events\SaveEvent;
use yii\base\Event;

// ...

Event::on(
SaveController::class,
SaveController::EVENT_AFTER_SAVE_ENTRY,
function(SaveEvent $e) {
// Grab the entry
$entry = $e->entry;
use CraftCms\GuestEntries\Events\SavedGuestEntry;
use Illuminate\Support\Facades\Event;

// Was it flagged as spam?
$isSpam = $e->isSpam;
}
);
Event::listen(function (SavedGuestEntry $event): void {
$entry = $event->entry;
$isSpam = $event->isSpam;
});
```

### The `afterError` event
### `GuestEntrySaveFailed`

Plugins can be notified right after a submission is determined to be invalid using the `afterError` event:
Dispatched right after a submission is rejected by a listener or fails to save:

```php
use craft\guestentries\controllers\SaveController;
use craft\guestentries\events\SaveEvent;
use yii\base\Event;

// ...
use CraftCms\GuestEntries\Events\GuestEntrySaveFailed;
use Illuminate\Support\Facades\Event;

Event::on(
SaveController::class,
SaveController::EVENT_AFTER_ERROR,
function(SaveEvent $e) {
// Grab the entry
$entry = $e->entry;

// Get any validation errors
$errors = $entry->getErrors();
}
);
Event::listen(function (GuestEntrySaveFailed $event): void {
$entry = $event->entry;
$errors = $entry->errors()->getMessages();
});
```
126 changes: 126 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Upgrading to Guest Entries 5.0 (Craft 6)

This guide lists what changed in 5.0 and what you need to update in your project, templates and custom code.

## Requirements

- Craft CMS 6.0 or later
- PHP 8.5 or later
- `craftcms/yii2-adapter` is **not** required. The plugin is fully native and works without it. Your project may still need the adapter if other plugins or your own code do.

## Upgrade steps

1. Upgrade your project to Craft 6 first.
2. Update the plugin: `composer require craftcms/guest-entries:^5.0`.
3. Your existing settings (project config `plugins.guest-entries.settings`) load unchanged. No migration is needed.
4. Review the breaking changes below and update your code.

## Breaking changes

### Events

The Yii-style events on the save controller were removed, along with `craft\guestentries\events\SaveEvent`. They are now plain Laravel event classes, dispatched with `event()`. There is no `sender`, so you listen for the event class.

| Before | After |
| --- | --- |
| `SaveController::EVENT_BEFORE_SAVE_ENTRY` | `CraftCms\GuestEntries\Events\SavingGuestEntry` |
| `SaveController::EVENT_AFTER_SAVE_ENTRY` | `CraftCms\GuestEntries\Events\SavedGuestEntry` |
| `SaveController::EVENT_AFTER_ERROR` | `CraftCms\GuestEntries\Events\GuestEntrySaveFailed` |

Before:

```php
use craft\guestentries\controllers\SaveController;
use craft\guestentries\events\SaveEvent;
use yii\base\Event;

Event::on(
SaveController::class,
SaveController::EVENT_BEFORE_SAVE_ENTRY,
function (SaveEvent $event) {
if ($event->entry->title === 'spam') {
$event->isSpam = true;
}
if ($event->entry->title === 'nope') {
$event->isValid = false;
}
}
);
```

After:

```php
use CraftCms\GuestEntries\Events\SavingGuestEntry;
use Illuminate\Support\Facades\Event;

Event::listen(function (SavingGuestEntry $event): void {
if ($event->entry->title === 'spam') {
$event->isSpam = true;
}
if ($event->entry->title === 'nope') {
$event->isValid = false;
}
});
```

- `SavingGuestEntry` has `$entry`, `$isValid` (set to `false` to reject the submission) and `$isSpam` (set to `true` to silently discard it while still reporting success).
- `SavedGuestEntry` has `$entry` and `$isSpam`. It is also dispatched for submissions flagged as spam, as `afterSaveEntry` was.
- `GuestEntrySaveFailed` has `$entry`. It is dispatched when a listener rejects the submission or the entry fails to save.
- The `afterSaveEntry`/`afterError` events used to fire only if a handler existed. The new events are always dispatched.

### Classes and namespaces

| Before | After |
| --- | --- |
| `craft\guestentries\Plugin` | `CraftCms\GuestEntries\Plugin` |
| `craft\guestentries\models\Settings` | `CraftCms\GuestEntries\Settings` |
| `craft\guestentries\models\SectionSettings` | `CraftCms\GuestEntries\Data\SectionSettings` |
| `craft\guestentries\controllers\SaveController` | `CraftCms\GuestEntries\Http\Controllers\SaveController` |
| `craft\guestentries\events\SaveEvent` | _removed, see Events_ |

### Settings and config

- `Settings` is now a `CraftCms\Cms\Plugin\PluginSettings` with real public properties (`enableCsrfProtection`, `entryVariable`, `sections`). `getSections()` and `setSections()` were removed. `$settings->sections` is an array keyed by section UID, holding only sections that allow guest submissions. `getSection($uid)` still returns a `SectionSettings`.
- `SectionSettings::$authorUid` is now `?string`, and `SectionSettings` is a `CraftCms\Cms\Component\Component`.
- The settings page is now built with the form builder instead of a Twig template.
- The "Enable CSRF Protection?" setting still works. When off, the `actions/guest-entries/save` route is exempted from Laravel's CSRF check. There is no longer a system-level CSRF setting to defer to.

### Templates and Twig

A failed submission now redirects back to the page and flashes the submitted data, instead of re-rendering the template with an `entry` variable. Update your form templates:

| Before | After |
| --- | --- |
| `{% set entry = entry ?? null %}` | _not needed_ |
| `entry.title` | `old('title')` |
| `entry.body` (custom field) | `old('fields.body')` |
| `entry.getErrors('title')` | `errors.get('title')` |
| `craft.app.sections.getSectionByHandle('x')` | `Sections.getSectionByHandle('x')` |
| _(no flash message)_ | `session().get('error')` after a failure, `session().get('success')` after a redirect on success |

The “Entry Variable Name” setting now only names the entry key in Ajax error responses.

Ajax responses (`Accept: application/json`) now use `message`, `modelName`, `modelClass`, `modelId` (success only), `errors` (failure only) and the entry data nested under `entry`, and the keys `success`, `id`, `title`, `authorUsername`, `dateCreated`, `dateUpdated`, `postDate` and `url` listed in earlier README versions are no longer top-level keys. Use the nested entry data instead.

Redirects with object templates (`{{ redirectInput('thanks?id={id}') }}`) still work.

### Routes and action URLs

`actions/guest-entries/save` is unchanged. It is registered from `routes/actions.php`.

Requests to `admin/actions/guest-entries/save` (the control panel prefix) return 404, as before.

### Database and migrations

The plugin has no tables. Two old settings migrations (`m170710_200301_tweak_settings`, `m181105_160000_craft_31_compatibility`) were removed. Sites must be on Guest Entries 2.1 or later (schema version 2.1.0) before upgrading.

## Removed

- `craft\guestentries\events\SaveEvent` and the `SaveController::EVENT_*` constants (see Events).
- `Settings::getSections()` and `Settings::setSections()`.
- Craft 4 and 5 support. There are no cross-compatible releases.

## Need help?

Open an issue at https://github.com/craftcms/guest-entries/issues.
Loading
Loading