Full Code of pixelandtonic/ContactForm for AI

3.x 83a6d70f2a6a cached
30 files
60.9 KB
16.3k tokens
20 symbols
1 requests
Download .txt
Repository: pixelandtonic/ContactForm
Branch: 3.x
Commit: 83a6d70f2a6a
Files: 30
Total size: 60.9 KB

Directory structure:
gitextract_mleti689/

├── .ddev/
│   └── config.yaml
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   └── workflows/
│       ├── ci.yml
│       ├── create-release.yml
│       └── issues.yml
├── .gitignore
├── .lintstagedrc.json
├── .prettierignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── ecs.php
├── package.json
├── phpstan.neon
└── src/
    ├── Mailer.php
    ├── Plugin.php
    ├── controllers/
    │   └── SendController.php
    ├── events/
    │   └── SendEvent.php
    ├── models/
    │   ├── Settings.php
    │   └── Submission.php
    ├── templates/
    │   └── _settings.twig
    └── translations/
        ├── ar/
        │   └── contact-form.php
        ├── de/
        │   └── contact-form.php
        ├── es/
        │   └── contact-form.php
        ├── fr/
        │   └── contact-form.php
        ├── it/
        │   └── contact-form.php
        └── nl/
            └── contact-form.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .ddev/config.yaml
================================================
name: contact-form
type: php
docroot: ''
php_version: '8.0'
webserver_type: nginx-fpm
router_http_port: '80'
router_https_port: '443'
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
database:
  type: mariadb
  version: '10.4'
nfs_mount_enabled: false
mutagen_enabled: false
use_dns_when_possible: true
composer_version: '2'
web_environment: []
nodejs_version: '16'
# Key features of ddev's config.yaml:

# name: <projectname> # Name of the project, automatically provides
#   http://projectname.ddev.site and https://projectname.ddev.site

# type: <projecttype>  # drupal6/7/8, backdrop, typo3, wordpress, php

# docroot: <relative_path> # Relative path to the directory containing index.php.

# php_version: "7.4"  # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2"

# You can explicitly specify the webimage but this
# is not recommended, as the images are often closely tied to ddev's' behavior,
# so this can break upgrades.

# webimage: <docker_image>  # nginx/php docker image.

# database:
#   type: <dbtype> # mysql, mariadb
#   version: <version> # database version, like "10.3" or "8.0"
# Note that mariadb_version or mysql_version from v1.18 and earlier
# will automatically be converted to this notation with just a "ddev config --auto"

# router_http_port: <port>  # Port to be used for http (defaults to port 80)
# router_https_port: <port> # Port for https (defaults to 443)

# xdebug_enabled: false  # Set to true to enable xdebug and "ddev start" or "ddev restart"
# Note that for most people the commands
# "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better,
# as leaving xdebug enabled all the time is a big performance hit.

# xhprof_enabled: false  # Set to true to enable xhprof and "ddev start" or "ddev restart"
# Note that for most people the commands
# "ddev xhprof" to enable xhprof and "ddev xhprof off" to disable it work better,
# as leaving xhprof enabled all the time is a big performance hit.

# webserver_type: nginx-fpm  # or apache-fpm

# timezone: Europe/Berlin
# This is the timezone used in the containers and by PHP;
# it can be set to any valid timezone,
# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# For example Europe/Dublin or MST7MDT

# composer_root: <relative_path>
# Relative path to the composer root directory from the project root. This is
# the directory which contains the composer.json and where all Composer related
# commands are executed.

# composer_version: "2"
# You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1
# to use the latest major version available at the time your container is built.
# It is also possible to use each other Composer version channel. This includes:
#   - 2.2 (latest Composer LTS version)
#   - stable
#   - preview
#   - snapshot
# Alternatively, an explicit Composer version may be specified, for example "2.2.18".
# To reinstall Composer after the image was built, run "ddev debug refresh".

# nodejs_version: "16"
# change from the default system Node.js version to another supported version, like 12, 14, 17, 18.
# Note that you can use 'ddev nvm' or nvm inside the web container to provide nearly any
# Node.js version, including v6, etc.

# additional_hostnames:
#  - somename
#  - someothername
# would provide http and https URLs for "somename.ddev.site"
# and "someothername.ddev.site".

# additional_fqdns:
#  - example.com
#  - sub1.example.com
# would provide http and https URLs for "example.com" and "sub1.example.com"
# Please take care with this because it can cause great confusion.

# upload_dir: custom/upload/dir
# would set the destination path for ddev import-files to <docroot>/custom/upload/dir
# When mutagen is enabled this path is bind-mounted so that all the files
# in the upload_dir don't have to be synced into mutagen

# working_dir:
#   web: /var/www/html
#   db: /home
# would set the default working directory for the web and db services.
# These values specify the destination directory for ddev ssh and the
# directory in which commands passed into ddev exec are run.

# omit_containers: [db, dba, ddev-ssh-agent]
# Currently only these containers are supported. Some containers can also be
# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit
# the "db" container, several standard features of ddev that access the
# database container will be unusable. In the global configuration it is also
# possible to omit ddev-router, but not here.

# nfs_mount_enabled: false
# Great performance improvement but requires host configuration first.
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs

# mutagen_enabled: false
# Performance improvement using mutagen asynchronous updates.
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen

# fail_on_hook_fail: False
# Decide whether 'ddev start' should be interrupted by a failing hook

# host_https_port: "59002"
# The host port binding for https can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.

# host_webserver_port: "59001"
# The host port binding for the ddev-webserver can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.

# host_db_port: "59002"
# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic
# unless explicitly specified.

# phpmyadmin_port: "8036"
# phpmyadmin_https_port: "8037"
# The PHPMyAdmin ports can be changed from the default 8036 and 8037

# host_phpmyadmin_port: "8036"
# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed
# through ddev-router, but it can be specified and bound.

# mailhog_port: "8025"
# mailhog_https_port: "8026"
# The MailHog ports can be changed from the default 8025 and 8026

# host_mailhog_port: "8025"
# The mailhog port is not normally bound on the host at all, instead being routed
# through ddev-router, but it can be bound directly to localhost if specified here.

# webimage_extra_packages: [php7.4-tidy, php-bcmath]
# Extra Debian packages that are needed in the webimage can be added here

# dbimage_extra_packages: [telnet,netcat]
# Extra Debian packages that are needed in the dbimage can be added here

# use_dns_when_possible: true
# If the host has internet access and the domain configured can
# successfully be looked up, DNS will be used for hostname resolution
# instead of editing /etc/hosts
# Defaults to true

# project_tld: ddev.site
# The top-level domain used for project URLs
# The default "ddev.site" allows DNS lookup via a wildcard
# If you prefer you can change this to "ddev.local" to preserve
# pre-v1.9 behavior.

# ngrok_args: --basic-auth username:pass1234
# Provide extra flags to the "ngrok http" command, see
# https://ngrok.com/docs#http or run "ngrok http -h"

# disable_settings_management: false
# If true, ddev will not create CMS-specific settings files like
# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php
# In this case the user must provide all such settings.

# You can inject environment variables into the web container with:
# web_environment:
# - SOMEENV=somevalue
# - SOMEOTHERENV=someothervalue

# no_project_mount: false
# (Experimental) If true, ddev will not mount the project into the web container;
# the user is responsible for mounting it manually or via a script.
# This is to enable experimentation with alternate file mounting strategies.
# For advanced users only!

# bind_all_interfaces: false
# If true, host ports will be bound on all network interfaces,
# not just the localhost interface. This means that ports
# will be available on the local network if the host firewall
# allows it.

# default_container_timeout: 120
# The default time that ddev waits for all containers to become ready can be increased from
# the default 120. This helps in importing huge databases, for example.

#web_extra_exposed_ports:
#- name: nodejs
#  container_port: 3000
#  http_port: 2999
#  https_port: 3000
#- name: something
#  container_port: 4000
#  https_port: 4000
#  http_port: 3999
# Allows a set of extra ports to be exposed via ddev-router
# The port behavior on the ddev-webserver must be arranged separately, for example
# using web_extra_daemons.
# For example, with a web app on port 3000 inside the container, this config would
# expose that web app on https://<project>.ddev.site:9999 and http://<project>.ddev.site:9998
# web_extra_exposed_ports:
#  - container_port: 3000
#    http_port: 9998
#    https_port: 9999

#web_extra_daemons:
#- name: "http-1"
#  command: "/var/www/html/node_modules/.bin/http-server -p 3000"
#  directory: /var/www/html
#- name: "http-2"
#  command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000"
#  directory: /var/www/html

# override_config: false
# By default, config.*.yaml files are *merged* into the configuration
# But this means that some things can't be overridden
# For example, if you have 'nfs_mount_enabled: true'' you can't override it with a merge
# and you can't erase existing hooks or all environment variables.
# However, with "override_config: true" in a particular config.*.yaml file,
# 'nfs_mount_enabled: false' can override the existing values, and
# hooks:
#   post-start: []
# or
# web_environment: []
# or
# additional_hostnames: []
# can have their intended affect. 'override_config' affects only behavior of the
# config.*.yaml file it exists in.

# Many ddev commands can be extended to run tasks before or after the
# ddev command is executed, for example "post-start", "post-import-db",
# "pre-composer", "post-composer"
# See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more
# information on the commands that can be extended and the tasks you can define
# for them. Example:
#hooks:


================================================
FILE: .gitattributes
================================================
# Do not export those files in the Composer archive (lighter dependency)
/.browserslistrc export-ignore
/.codecov.yml export-ignore
/.env.example export-ignore
/.git-blame-ignore-revs export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.lintstagedrc.json export-ignore
/.nvmrc export-ignore
/crowdin.yml export-ignore
/CHANGELOG.md export-ignore
/README.md export-ignore
/SECURITY.md export-ignore
/codeception.yml export-ignore
/composer.lock export-ignore
/ecs.php export-ignore
/phpstan.neon export-ignore
/.prettierrc.json export-ignore
/.prettierignore export-ignore
/gruntfile.js export-ignore
/gulpfile.js export-ignore
/lerna.json export-ignore
/package.json export-ignore
/package-lock.json export-ignore
/tsconfig.json export-ignore
/webpack.config.js export-ignore
/stubs/ export-ignore
/tests/ export-ignore
/packages/ export-ignore
/.ddev/ export-ignore
/.github/ export-ignore
/.husky/ export-ignore

# Auto detect text files and perform LF normalization
* text=auto

================================================
FILE: .github/CODEOWNERS
================================================
* @brandonkelly

================================================
FILE: .github/workflows/ci.yml
================================================
name: ci
on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:
permissions:
  contents: read
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
jobs:
  ci:
    name: ci
    uses: craftcms/.github/.github/workflows/ci.yml@v3
    with:
      craft_version: '4'
      jobs: '["ecs", "phpstan", "prettier"]'
      notify_slack: true
      slack_subteam: <!subteam^SGFL9NKNZ>
    secrets:
      token: ${{ secrets.GITHUB_TOKEN }}
      slack_webhook_url: ${{ secrets.SLACK_PLUGIN_WEBHOOK_URL }}


================================================
FILE: .github/workflows/create-release.yml
================================================
name: Create Release
run-name: Create release for ${{ github.event.client_payload.version }}

on:
  repository_dispatch:
    types:
      - craftcms/new-release

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: ncipollo/release-action@v1
        with:
          body: ${{ github.event.client_payload.notes }}
          makeLatest: ${{ github.event.client_payload.latest }}
          name: ${{ github.event.client_payload.version }}
          prerelease: ${{ github.event.client_payload.prerelease }}
          tag: ${{ github.event.client_payload.tag }}


================================================
FILE: .github/workflows/issues.yml
================================================
name: Add issues to project

on:
  issues:
    types:
      - opened
      - transferred

jobs:
  add-to-project:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/add-to-project@v0.4.0
        with:
          project-url: https://github.com/orgs/craftcms/projects/16
          github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}


================================================
FILE: .gitignore
================================================
*.idea/*
*.log
*.DS_Store
*Thumbs.db
/vendor
node_modules


================================================
FILE: .lintstagedrc.json
================================================
{
  "**/*.php": [
    "./vendor/bin/ecs check --ansi --fix",
    "./vendor/bin/phpstan analyse"
  ],
  "*": "prettier --ignore-unknown --write"
}


================================================
FILE: .prettierignore
================================================
*.md
*.php
composer.lock
cpresources/*
lib/*
src/templates/*
src/web/assets/**/dist/*
tests/_craft/*
vendor/*
.ddev/*


================================================
FILE: .prettierrc.json
================================================
{
  "singleQuote": true,
  "bracketSpacing": false,
  "vueIndentScriptAndStyle": true
}


================================================
FILE: CHANGELOG.md
================================================
# Release Notes for Contact Form

## 3.1.0 - 2024-03-11

- Added Craft 5 compatibility.
- Added a missing Dutch translation. ([#257](https://github.com/craftcms/contact-form/issues/257))
- Fixed a bug where it wasn’t possible to upload a single file with the `attachment` param. ([#254](https://github.com/craftcms/contact-form/issues/254))

## 3.0.1 - 2023-03-16

- Added translations for `Email` and `Name`. ([#235](https://github.com/craftcms/contact-form/issues/235))
- Fixed an error that occurred if `fromName` was not specified on submissions. ([#228](https://github.com/craftcms/contact-form/issues/228))

## 3.0.0 - 2022-05-02

### Added
- Added Craft 4 compatibility.
- Added the `allowedMessageFields` setting, which can be used to restrict which `message` fields are allowed to be submitted.

### Changed
- Failed submissions are now passed back to the template as a `submission` variable, instead of `message`.
- The `contact-form/send` action now returns a 400 status on failure for Ajax requests.

## 2.5.2 - 2023-03-16

- Added translations for `Email` and `Name`. ([#235](https://github.com/craftcms/contact-form/issues/235))

## 2.5.1 - 2022-05-02

### Fixed
- Fixed a bug where newlines were getting replaced with double newlines in message bodies. ([#214](https://github.com/craftcms/contact-form/issues/214))

## 2.5.0 - 2022-04-15

### Added
- Added the `allowedMessageFields` setting, which can be used to restrict which `message` fields are allowed to be submitted.

## 2.4.1 - 2022-04-12

### Fixed
- Fixed a potential PHP error.

## 2.4.0 - 2022-04-11

### Changed
- Custom message fields’ labels can now be translated using the `site` translation category. ([#161](https://github.com/craftcms/contact-form/pull/161))

## 2.3.0 - 2022-01-21

### Changed
- Craft 3.4 or later is now required.
- The success flash message is now returned in the response for AJAX calls.
- `craft\contactform\models\Submission` now supports `EVENT_DEFINE_RULES`. ([#196](https://github.com/craftcms/contact-form/pull/196))

## 2.2.7 - 2020-05-04

### Changed
- Added case-insensitive extension check for attachments.

## 2.2.6 - 2019-12-17

### Changed
- The Contact Form “To Email” setting can now be set to environment variables (e.g. `$CONTACT_TO_EMAIL`). ([#179](https://github.com/craftcms/contact-form/pull/179))
- Contact Form now requires Craft 3.1 or later.

## 2.2.5 - 2019-05-31

### Changed
- Contact Form now respects Craft’s [allowedFileExtensions](https://docs.craftcms.com/v3/config/config-settings.html#allowedfileextensions) config setting.
- Contact Form now logs a `warning` instead of `info` to the log files when an email is flagged as spam. ([#163](https://github.com/craftcms/contact-form/issues/163))

## 2.2.4 - 2019-04-03

### Fixed
- Fixed an issue with Japanese Characters. ([#158](https://github.com/craftcms/contact-form/pull/158))

## 2.2.3 - 2018-11-13

### Added
- Contact Form is now translated into Dutch. ([#139](https://github.com/craftcms/contact-form/pull/139))

### Fixed
- Fixed a bug where the submission email address was not being validated. ([#145](https://github.com/craftcms/contact-form/issues/145))

## 2.2.2 - 2018-07-19

### Fixed
- Fixed a PHP error introduced in 2.2.1 that broke submissions that were using a single `message` form input.

## 2.2.1 - 2018-07-18

### Fixed
- Fixed a bug where blank messages wouldn’t fail validation if the message was split into multiple fields.

## 2.2.0 - 2018-07-18

### Added
- Contact Form is now translated into Arabic. ([#125](https://github.com/craftcms/contact-form/pull/125))

### Changed
- Contact emails no longer list the Name field if none was provided. ([#126](https://github.com/craftcms/contact-form/issues/126))
- Event listeners for `craft\contactform\Mailer::EVENT_BEFORE_SEND` can now make changes to `craft\contactform\events\SendEvent::$toEmails`, and they will be respected. ([#112](https://github.com/craftcms/contact-form/pull/112))  

### Fixed
- Fixed a bug where single carriage returns in email message bodies were being ignored. ([#118](https://github.com/craftcms/contact-form/issues/118))
- Fixed a bug where HTML in email bodies wasn’t getting escaped. ([#104](https://github.com/craftcms/contact-form/issues/104))
- Fixed an error that occurred when submitting a contact form with an empty file attachment field. ([#116](https://github.com/craftcms/contact-form/pull/116))

## 2.1.1 - 2017-12-04

### Changed
- Loosened the Craft CMS version requirement to allow any 3.x version.

## 2.1.0 - 2017-10-12

### Added
- Added German translations.

### Changed
- Email message bodies now include the sender’s name and email. ([#97](https://github.com/craftcms/contact-form/pull/97))

## 2.0.3 - 2017-09-15

### Added
- Craft 3 Beta 27 compatibility.

## 2.0.2 - 2017-07-07

### Added
- Craft 3 Beta 20 compatibility.

## 2.0.1 - 2017-06-12

### Fixed
- Fixed a bug where the `message` variable was not available to contact form templates when the submission contained validation errors.

## 2.0.0 - 2017-05-16

### Added
- Added Craft 3 compatibility.
- Added the `afterSend` event.

### Changed
- The `beforeSend` event now has `$submission` and `$message` properties, set to the user submission model and the compiled email message, respectively.
- The `contactForm/sendMessage` action is now `contact-form/send`.

### Removed
- Removed honeypot field support. (Moved to the [contact-form-honeypot](https://github.com/craftcms/contact-form-honeypot) plugin.)
- Removed the `beforeMessageCompile` event.
- Removed the `$isValid` property from the `beforeSend` event. Use the `beforeValidate` event on the `Submission` model to prevent submissions from going through.

## 1.8.1 - 2016-09-02

### Fixed
- Fixed a bug where the HTML body of an email was being escaped displaying HTML entities in the email.

## 1.8.0 - 2016-08-18

### Added
- Added the ability for plugins to modify the email's plain text and HTML body via the `contactForm.beforeMessageCompile` event.

### Fixed
- Fixed a bug where Twig code that was entered in the email body or subject was getting parsed.

## 1.7.0 - 2016-01-18

### Added
- Added the ability to access individual message fields values via `message.messageFields` when a validation error occurred. For example, the value of the input `message[Phone]` can now be accessed via `message.messageFields['Phone']`.

### Changed
- Custom message field values only have a single line break between them in the generated email body now, rather than two.

## 1.6.0 - 2015-12-20

### Added
- Added the ability to attach multiple files to the contact email.
- Added the ability to change the flash success message via the "successFlashMessage" setting.
- Added the ability to override plugin settings via a `craft/config/contactform.php` config setting.

### Changed
- The "prependSender" and "prependSubject" settings can now be empty strings.

### Fixed
- Fixed a bug where the "allowAttachments" config setting wasn't being respected.

## 1.5.0 - 2015-12-20

### Added
- Added support for some Craft 2.5 features.

## 1.4.0 - 2014-06-01

### Added
- Added support for passing `{fromName}`, `{fromEmail}`, and `{subject}` in the ‘redirect’ URL.

## 1.3.0 - 2014-01-07

### Added
- Added support for multiple email addresses
- Added the ContactFormService
- Added the `contactForm.beforeSend` event, allowing third party plugins to add extra validation

## 1.2.0 - 2013-12-30

### Added
- Added honeypot captcha support

## 1.1.0 - 2013-12-04

### Added
- Added the ability to submit attachments
- Added the ability to submit the form over Ajax
- Added the ability to submit checkbox lists, which get compiled into comma-separated lists in the email

## 1.0.0 - 2013-10-03

- Initial release


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) 2017 Pixel & Tonic, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Contact Form for Craft CMS

This plugin allows you to add an email contact form to your website.

## Requirements

This plugin requires Craft CMS 4.0.0+ or 5.0.0+.

## Installation

You can install this plugin from the Plugin Store or with Composer.

#### From the Plugin Store

Go to the Plugin Store in your project’s Control Panel and search for “Contact Form”. Then click on the “Install” button in its modal window.

#### With Composer

Open your terminal and run the following commands:

```bash
# go to the project directory
cd /path/to/my-project.test

# tell Composer to load the plugin
composer require craftcms/contact-form

# tell Craft to install the plugin
php craft plugin/install contact-form
```

## Usage

Your contact form template can look something like this:

```twig
{% macro errorList(errors) %}
    {% if errors %}
        {{ ul(errors, {class: 'errors'}) }}
    {% endif %}
{% endmacro %}

{% set submission = submission ?? null %}

<form method="post" action="" accept-charset="UTF-8">
    {{ csrfInput() }}
    {{ actionInput('contact-form/send') }}
    {{ redirectInput('contact/thanks') }}

    <h3><label for="from-name">Your Name</label></h3>
    {{ input('text', 'fromName', submission.fromName ?? '', {
        id: 'from-name',
        autocomplete: 'name',
    }) }}
    {{ submission ? _self.errorList(submission.getErrors('fromName')) }}

    <h3><label for="from-email">Your Email</label></h3>
    {{ input('email', 'fromEmail', submission.fromEmail ?? '', {
        id: 'from-email',
        autocomplete: 'email',
    }) }}
    {{ submission ? _self.errorList(submission.getErrors('fromEmail')) }}

    <h3><label for="subject">Subject</label></h3>
    {{ input('text', 'subject', submission.subject ?? '', {
        id: 'subject',
    }) }}
    {{ submission ? _self.errorList(submission.getErrors('subject')) }}

    <h3><label for="message">Message</label></h3>
    {{ tag('textarea', {
        text: submission.message ?? '',
        id: 'message',
        name: 'message',
        rows: 10,
        cols: 40,
    }) }}
    {{ submission ? _self.errorList(submission.getErrors('message')) }}

    <button type="submit">Send</button>
</form>
```

The only required fields are `fromEmail` and `message`. Everything else is optional.

### Redirecting after submit

If you have a `redirect` hidden input, the user will get redirected to it upon successfully sending the email. The following variables can be used within the URL/path you set:

- `{fromName}`
- `{fromEmail}`
- `{subject}`

For example, if you wanted to redirect to a `contact/thanks` page and pass the sender’s name to it, you could set the input like this:

```twig
{{ redirectInput('contact/thanks?from={fromName}') }}
```

On your `contact/thanks.html` template, you can access that `from` parameter using `craft.app.request.getQueryParam()`:

```twig
<p>Thanks for sending that in, {{ craft.app.request.getQueryParam('from') }}!</p>
```

Note that if you don’t include a `redirect` input, the current page will get reloaded.

### Displaying flash messages

When a contact form is submitted, the plugin will set a `notice` (Craft 4) or `success` (Craft 5+) flash message on the user session. You can display it in your template like this:

```twig
{% if craft.app.session.hasFlash('success') %}
    <p class="message success">{{ craft.app.session.getFlash('success') }}</p>
{% elseif craft.app.session.hasFlash('error') %}
    <p class="message error">{{ craft.app.session.getFlash('error') }}</p>
{% endif %}
```

### Adding additional fields

You can add additional fields to your form by splitting your `message` field into multiple fields, using an array syntax for the input names:

```twig
<h3><label for="message">Message</label></h3>
<textarea rows="10" cols="40" id="message" name="message[body]">{{ submission.message.body ?? '' }}</textarea>

<h3><label for="phone">Your phone number</label></h3>
<input id="phone" type="text" name="message[Phone]" value="">

<h3>What services are you interested in?</h3>
<label><input type="checkbox" name="message[Services][]" value="Design"> Design</label>
<label><input type="checkbox" name="message[Services][]" value="Development"> Development</label>
<label><input type="checkbox" name="message[Services][]" value="Strategy"> Strategy</label>
<label><input type="checkbox" name="message[Services][]" value="Marketing"> Marketing</label>
```

If you have a primary “Message” field, you should name it `message[body]`, like in that example.

An email sent with the above form might result in the following message:

    • Name: John Doe
    • Email: example@email.com
    • Phone: (555) 123-4567
    • Services: Design, Development

    Hey guys, I really loved this simple contact form (I'm so tired of agencies
    asking for everything but my social security number up front), so I trust
    you guys know a thing or two about usability.
    
    I run a small coffee shop and we want to start attracting more freelancer-
    types to spend their days working from our shop (and sipping fine coffee!).
    A clean new website with lots of social media integration would probably
    help us out quite a bit there. Can you help us with that?
    
    Hope to hear from you soon.

    Cathy Chino

By default, there’s no restriction on which keys can be included on `message`. You can limit which fields are allowed using the `allowedMessageFields` setting in `config/contact-form.php`:

```php
<?php

return [
    'allowedMessageFields' => ['Phone', 'Services'],
];
```

### Overriding plugin settings

If you create a [config file](https://craftcms.com/docs/4.x/config/) in your `config/` folder called `contact-form.php`, you can override
the plugin’s settings in the Control Panel.  Since that config file is fully [multi-environment](https://craftcms.com/docs/4.x/config/#multi-environment-configs) aware, this is
a handy way to have different settings across multiple environments.

Here’s what that config file might look like along with a list of all of the possible values you can override.

```php
<?php

return [
    'toEmail'             => 'bond@007.com',
    'prependSubject'      => '',
    'prependSender'       => '',
    'allowAttachments'    => false,
    'successFlashMessage' => 'Message sent!'
];
```

### Dynamically adding email recipients

You can programmatically add email recipients from your template by adding a hidden input field named `toEmail` like so:

```twig
<input type="hidden" name="toEmail" value="{{ 'me@example.com'|hash }}">
```

If you want to add multiple recipients, you can provide a comma separated list of emails like so:

```twig
<input type="hidden" name="toEmail" value="{{ 'me@example.com,me2@example.com'|hash }}">
```

Then from your `config/contact-form.php` config file, you’ll need to add a bit of logic:

```php
<?php

$config = [];
$request = Craft::$app->request;

if (
    !$request->getIsConsoleRequest() &&
    ($toEmail = $request->getValidatedBodyParam('toEmail')) !== null
) {
    $config['toEmail'] = $toEmail;
}

return $config;
```

In this example if `toEmail` does not exist or fails validation (it was tampered with), the plugin will fallback to the “To Email” defined in the plugin settings, so you must have that defined as well.

### File attachments

If you would like your contact form to accept file attachments, follow these steps:

1. Go to Settings → Contact Form in the Control Panel, and make sure the plugin is set to allow attachments.
2. Make sure your opening HTML `<form>` tag contains `enctype="multipart/form-data"`.
3. Add a `<input type="file" name="attachment">` to your form.
4. If you want to allow multiple file attachments, use multiple `<input type="file" name="attachment[]" multiple>` inputs.


### Ajax form submissions

You can optionally post contact form submissions over Ajax if you’d like. Just send a POST request to your site with all of the same data that would normally be sent:

```js
$('#my-form').submit(function(ev) {
    // Prevent the form from actually submitting
    ev.preventDefault();

    // Send it to the server
    $.post({
        url: '/',
        dataType: 'json',
        data: $(this).serialize(),
        success: function(response) {
            $('#thanks').text(response.message).fadeIn();
        },
        error: function(jqXHR) {
          // The response body will be an object containing the following keys:
          // - `message` – A high level message for the response
          // - `submission` – An object containing data from the attempted submission
          // - `errors` – An object containing validation errors from the submission, indexed by attribute name
          alert(jqXHR.responseJSON.message);
        }
    });
});
```

### The `afterValidate` event

Modules and plugins can be notified when a submission is being validated, providing their own custom validation logic, using the `afterValidate` event on the `Submission` model:

```php
use craft\contactform\models\Submission;
use yii\base\Event;

// ...

Event::on(Submission::class, Submission::EVENT_AFTER_VALIDATE, function(Event $e) {
    /** @var Submission $submission */
    $submission = $e->sender;
    
    // Make sure that `message[Phone]` was filled in
    if (empty($submission->message['Phone'])) {
        // Add the error
        // (This will be accessible via `message.getErrors('message.phone')` in the template.)
        $submission->addError('message.phone', 'A phone number is required.');
    }
});
```


### The `beforeSend` event

Modules and plugins can be notified right before a message is sent out to the recipients using the `beforeSend` event. This is also an opportunity to flag the message as spam, preventing it from getting sent:

```php
use craft\contactform\events\SendEvent;
use craft\contactform\Mailer;
use yii\base\Event;

// ...

Event::on(Mailer::class, Mailer::EVENT_BEFORE_SEND, function(SendEvent $e) {
    $isSpam = // custom spam detection logic...

    if ($isSpam) {
        $e->isSpam = true;
    }
});
```


### The `afterSend` event

Modules and plugins can be notified right after a message is sent out to the recipients using the `afterSend` event.

```php
use craft\contactform\events\SendEvent;
use craft\contactform\Mailer;
use yii\base\Event;

// ...

Event::on(Mailer::class, Mailer::EVENT_AFTER_SEND, function(SendEvent $e) {
    // custom logic...
});
```

### Using a “Honeypot” field

Support for the [honeypot captcha technique](https://haacked.com/archive/2007/09/11/honeypot-captcha.aspx/) to fight spam has been moved to a [separate plugin](https://github.com/craftcms/contact-form-honeypot) that complements this one.


================================================
FILE: composer.json
================================================
{
  "name": "craftcms/contact-form",
  "description": "Add a simple contact form to your Craft CMS site",
  "type": "craft-plugin",
  "keywords": [
    "cms",
    "craftcms",
    "contact",
    "form",
    "yii2"
  ],
  "license": "MIT",
  "authors": [
    {
      "name": "Pixel & Tonic",
      "homepage": "https://pixelandtonic.com/"
    }
  ],
  "support": {
    "email": "support@craftcms.com",
    "issues": "https://github.com/craftcms/contact-form/issues?state=open",
    "source": "https://github.com/craftcms/contact-form",
    "docs": "https://github.com/craftcms/contact-form",
    "rss": "https://github.com/craftcms/contact-form/commits/3.x.atom"
  },
  "minimum-stability": "dev",
  "prefer-stable": true,
  "require": {
    "php": "^8.0.2",
    "craftcms/cms": "^4.0.0-beta.1|^5.0.0-beta.1"
  },
  "require-dev": {
    "craftcms/ecs": "dev-main",
    "craftcms/phpstan": "dev-main",
    "craftcms/rector": "dev-main"
  },
  "autoload": {
    "psr-4": {
      "craft\\contactform\\": "src/"
    }
  },
  "extra": {
    "name": "Contact Form",
    "handle": "contact-form",
    "documentationUrl": "https://github.com/craftcms/contact-form/blob/3.x/README.md",
    "components": {
      "mailer": "craft\\contactform\\Mailer"
    }
  },
  "scripts": {
    "check-cs": "ecs check --ansi",
    "fix-cs": "ecs check --ansi --fix",
    "phpstan": "phpstan --memory-limit=1G"
  },
  "config": {
    "platform": {
      "php": "8.0.2"
    },
    "allow-plugins": {
      "yiisoft/yii2-composer": true,
      "craftcms/plugin-installer": true
    }
  }
}


================================================
FILE: ecs.php
================================================
<?php

use craft\ecs\SetList;
use Symplify\EasyCodingStandard\Config\ECSConfig;

return static function(ECSConfig $ecsConfig): void {
    $ecsConfig->paths([
        __DIR__ . '/src',
        __FILE__,
    ]);

    $ecsConfig->parallel();
    $ecsConfig->sets([SetList::CRAFT_CMS_4]);
};


================================================
FILE: package.json
================================================
{
  "name": "@craftcms/contact-form",
  "private": true,
  "devDependencies": {
    "husky": "^7.0.4",
    "lint-staged": "^12.4.0",
    "prettier": "^2.7.1"
  },
  "scripts": {
    "prepare": "husky install"
  }
}


================================================
FILE: phpstan.neon
================================================
includes:
    - vendor/craftcms/phpstan/phpstan.neon

parameters:
    level: 4
    paths:
        - src


================================================
FILE: src/Mailer.php
================================================
<?php

namespace craft\contactform;

use Craft;
use craft\contactform\events\SendEvent;
use craft\contactform\models\Submission;
use craft\elements\User;
use craft\helpers\App;
use craft\helpers\ArrayHelper;
use craft\helpers\FileHelper;
use craft\helpers\StringHelper;
use craft\mail\Message;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\Markdown;

class Mailer extends Component
{
    /**
     * @event SubmissionEvent The event that is triggered before a message is sent
     */
    public const EVENT_BEFORE_SEND = 'beforeSend';

    /**
     * @event SubmissionEvent The event that is triggered after a message is sent
     */
    public const EVENT_AFTER_SEND = 'afterSend';

    /**
     * Sends an email submitted through a contact form.
     *
     * @param Submission $submission
     * @param bool $runValidation Whether the section should be validated
     * @throws InvalidConfigException if the plugin settings don't validate
     * @return bool
     */
    public function send(Submission $submission, bool $runValidation = true): bool
    {
        // Get the plugin settings and make sure they validate before doing anything
        $settings = Plugin::getInstance()->getSettings();
        if (!$settings->validate()) {
            throw new InvalidConfigException('The Contact Form settings don’t validate.');
        }

        if ($runValidation && !$submission->validate()) {
            Craft::info('Contact form submission not saved due to validation error.', __METHOD__);
            return false;
        }

        $mailer = Craft::$app->getMailer();

        // Prep the message
        $fromEmail = $this->getFromEmail($mailer->from);
        $fromName = $this->compileFromName($submission->fromName);
        $subject = $this->compileSubject($submission->subject);
        $textBody = $this->compileTextBody($submission);
        $htmlBody = $this->compileHtmlBody($textBody);

        // Flag for file attachment validation.
        $validAttachments = true;

        $message = (new Message())
            ->setFrom([$fromEmail => $fromName])
            ->setReplyTo([$submission->fromEmail => (string)$submission->fromName])
            ->setSubject($subject)
            ->setTextBody($textBody)
            ->setHtmlBody($htmlBody);

        if ($submission->attachment !== null) {
            $allowedFileTypes = Craft::$app->getConfig()->getGeneral()->allowedFileExtensions;

            if (!is_array($submission->attachment)) {
                $submission->attachment = [$submission->attachment];
            }

            foreach ($submission->attachment as $attachment) {
                if (!$attachment) {
                    continue;
                }

                // Validate that the file is safe to send by e-mail
                $extension = pathinfo($attachment->name, PATHINFO_EXTENSION);

                if (!in_array(strtolower($extension), $allowedFileTypes)) {
                    $validAttachments = false;
                }

                $message->attach($attachment->tempName, [
                    'fileName' => $attachment->name,
                    'contentType' => FileHelper::getMimeType($attachment->tempName),
                ]);
            }
        }

        // Grab any "to" emails set in the plugin settings.
        $toEmails = App::parseEnv($settings->toEmail);
        $toEmails = is_string($toEmails) ? StringHelper::split($toEmails) : $toEmails;

        // Fire a 'beforeSend' event
        $event = new SendEvent([
            'submission' => $submission,
            'message' => $message,
            'toEmails' => $toEmails,
        ]);
        $this->trigger(self::EVENT_BEFORE_SEND, $event);

        if ($event->isSpam) {
            Craft::warning('Contact form submission suspected to be spam.', __METHOD__);
            return true;
        }

        if ($validAttachments === false) {
            Craft::error('Contact form submission contains a disallowed filetype.', __METHOD__);
            return false;
        }

        foreach ($event->toEmails as $toEmail) {
            $message->setTo($toEmail);
            $mailer->send($message);
        }

        // Fire an 'afterSend' event
        if ($this->hasEventHandlers(self::EVENT_AFTER_SEND)) {
            $this->trigger(self::EVENT_AFTER_SEND, new SendEvent([
                'submission' => $submission,
                'message' => $message,
                'toEmails' => $event->toEmails,
            ]));
        }

        return true;
    }

    /**
     * Returns the "From" email value on the given mailer $from property object.
     *
     * @param string|array|User|User[]|null $from
     * @return string
     * @throws InvalidConfigException if it can’t be determined
     */
    public function getFromEmail($from): string
    {
        if (is_string($from)) {
            return $from;
        }
        if ($from instanceof User) {
            return $from->email;
        }
        if (is_array($from)) {
            $first = reset($from);
            $key = key($from);
            if (is_numeric($key)) {
                return $this->getFromEmail($first);
            }
            return $key;
        }
        throw new InvalidConfigException('Can\'t determine "From" email from email config settings.');
    }

    /**
     * Compiles the "From" name value from the submitted name.
     *
     * @param string|null $fromName
     * @return string
     */
    public function compileFromName(string $fromName = null): string
    {
        $settings = Plugin::getInstance()->getSettings();
        return $settings->prependSender . ($settings->prependSender && $fromName ? ' ' : '') . $fromName;
    }

    /**
     * Compiles the real email subject from the submitted subject.
     *
     * @param string|null $subject
     * @return string
     */
    public function compileSubject(string $subject = null): string
    {
        $settings = Plugin::getInstance()->getSettings();
        return $settings->prependSubject . ($settings->prependSubject && $subject ? ' - ' : '') . $subject;
    }

    /**
     * Compiles the real email textual body from the submitted message.
     *
     * @param Submission $submission
     * @return string
     */
    public function compileTextBody(Submission $submission): string
    {
        $fields = [];

        if ($submission->fromName) {
            $fields[Craft::t('contact-form', 'Name')] = $submission->fromName;
        }

        $fields[Craft::t('contact-form', 'Email')] = $submission->fromEmail;

        if (is_array($submission->message)) {
            $settings = Plugin::getInstance()->getSettings();
            $messageFields = array_merge($submission->message);
            $body = ArrayHelper::remove($messageFields, 'body', '');
            foreach ($messageFields as $key => $value) {
                if ($settings->allowedMessageFields === null || in_array($key, $settings->allowedMessageFields)) {
                    $label = Craft::t('site', $key);
                    $fields[$label] = $value;
                }
            }
        } else {
            $body = (string)$submission->message;
        }

        $text = '';

        foreach ($fields as $key => $value) {
            $text .= ($text ? "\n" : '') . "- **{$key}:** ";
            if (is_array($value)) {
                $text .= implode(', ', $value);
            } else {
                $text .= $value;
            }
        }

        if ($body !== '') {
            $body = preg_replace('/\R/u', "\n", $body);
            $text .= "\n\n" . $body;
        }

        return $text;
    }

    /**
     * Compiles the real email HTML body from the compiled textual body.
     *
     * @param string $textBody
     * @return string
     */
    public function compileHtmlBody(string $textBody): string
    {
        $html = Html::encode($textBody);
        $html = Markdown::process($html);

        return $html;
    }
}


================================================
FILE: src/Plugin.php
================================================
<?php
/**
 * @link https://craftcms.com/
 * @copyright Copyright (c) Pixel & Tonic, Inc.
 * @license MIT
 */

namespace craft\contactform;

use Craft;
use craft\contactform\models\Settings;

/**
 * Class Plugin
 *
 * @property Settings $settings
 * @property Mailer $mailer
 * @method Settings getSettings()
 */
class Plugin extends \craft\base\Plugin
{
    /**
     * @inheritdoc
     */
    public bool $hasCpSettings = true;

    /**
     * @return Mailer
     */
    public function getMailer(): Mailer
    {
        return $this->get('mailer');
    }

    /**
     * @inheritdoc
     */
    protected function createSettingsModel(): ?Settings
    {
        return new Settings();
    }

    /**
     * @inheritdoc
     */
    protected function settingsHtml(): ?string
    {
        // Get and pre-validate the settings
        $settings = $this->getSettings();
        $settings->validate();

        // Get the settings that are being defined by the config file
        $overrides = Craft::$app->getConfig()->getConfigFromFile(strtolower($this->handle));

        return Craft::$app->view->renderTemplate('contact-form/_settings', [
            'settings' => $settings,
            'overrides' => array_keys($overrides),
        ]);
    }
}


================================================
FILE: src/controllers/SendController.php
================================================
<?php

namespace craft\contactform\controllers;

use Craft;
use craft\contactform\models\Submission;
use craft\contactform\Plugin;
use craft\web\Controller;
use craft\web\UploadedFile;
use yii\web\Response;

class SendController extends Controller
{
    /**
     * @inheritdoc
     */
    public array|bool|int $allowAnonymous = true;

    /**
     * Sends a contact form submission.
     *
     * @return Response|null
     */
    public function actionIndex()
    {
        $this->requirePostRequest();
        $request = Craft::$app->getRequest();
        $plugin = Plugin::getInstance();
        $settings = $plugin->getSettings();

        $submission = new Submission();
        $submission->fromEmail = $request->getBodyParam('fromEmail');
        $submission->fromName = $request->getBodyParam('fromName');
        $submission->subject = $request->getBodyParam('subject');

        $message = $request->getBodyParam('message');
        if (is_array($message)) {
            $submission->message = array_filter($message, function($value) {
                return $value !== '';
            });
        } else {
            $submission->message = $message;
        }

        if ($settings->allowAttachments && isset($_FILES['attachment']) && isset($_FILES['attachment']['name'])) {
            if (is_array($_FILES['attachment']['name'])) {
                $submission->attachment = UploadedFile::getInstancesByName('attachment');
            } else {
                $submission->attachment = UploadedFile::getInstanceByName('attachment');
            }
        }

        if (!$plugin->getMailer()->send($submission)) {
            return $this->asModelFailure(
                $submission,
                Craft::t('contact-form', 'There was a problem with your submission, please check the form and try again!'),
                'submission',
                [
                    'errors' => $submission->getErrors(),
                ],
            );
        }

        return $this->asModelSuccess(
            $submission,
            $settings->successFlashMessage,
            'submission',
        );
    }
}


================================================
FILE: src/events/SendEvent.php
================================================
<?php
/**
 * @link https://craftcms.com/
 * @copyright Copyright (c) Pixel & Tonic, Inc.
 * @license MIT
 */

namespace craft\contactform\events;

use craft\contactform\models\Submission;
use craft\mail\Message;
use yii\base\Event;

class SendEvent extends Event
{
    /**
     * @var Submission The user submission.
     */
    public $submission;

    /**
     * @var Message The message about to be sent.
     */
    public $message;

    /**
     * @var string[] The email address(es) the submission will get sent to.
     */
    public $toEmails;

    /**
     * @var bool Whether the message appears to be spam, and should not really be sent.
     */
    public $isSpam = false;
}


================================================
FILE: src/models/Settings.php
================================================
<?php
/**
 * @link https://craftcms.com/
 * @copyright Copyright (c) Pixel & Tonic, Inc.
 * @license MIT
 */

namespace craft\contactform\models;

use craft\base\Model;

class Settings extends Model
{
    /**
     * @var string|string[]|null
     */
    public $toEmail;

    /**
     * @var string|null
     */
    public $prependSender;

    /**
     * @var string|null
     */
    public $prependSubject;

    /**
     * @var bool
     */
    public $allowAttachments = false;

    /**
     * @var string|null
     */
    public $successFlashMessage;

    /**
     * @var string[]|null List of allowed `message` sub-keys that can be posted to `contact-form/send` (besides `body`).
     * @since 2.5.0
     */
    public $allowedMessageFields;

    /**
     * @inheritdoc
     */
    public function init(): void
    {
        parent::init();

        if ($this->prependSender === null) {
            $this->prependSender = \Craft::t('contact-form', 'On behalf of');
        }

        if ($this->prependSubject === null) {
            $this->prependSubject = \Craft::t('contact-form', 'New message from {siteName}', [
                'siteName' => \Craft::$app->getSites()->getCurrentSite()->name,
            ]);
        }

        if ($this->successFlashMessage === null) {
            $this->successFlashMessage = \Craft::t('contact-form', 'Your message has been sent.');
        }
    }

    /**
     * @inheritdoc
     */
    protected function defineRules(): array
    {
        return [
            [['toEmail', 'successFlashMessage'], 'required'],
            [['toEmail', 'prependSender', 'prependSubject', 'successFlashMessage'], 'string'],
        ];
    }
}


================================================
FILE: src/models/Submission.php
================================================
<?php
/**
 * @link https://craftcms.com/
 * @copyright Copyright (c) Pixel & Tonic, Inc.
 * @license MIT
 */

namespace craft\contactform\models;

use craft\base\Model;
use craft\web\UploadedFile;

/**
 * Class Submission
 *
 * @package craft\contactform
 */
class Submission extends Model
{
    /**
     * @var string|null
     */
    public $fromName;

    /**
     * @var string|null
     */
    public $fromEmail;

    /**
     * @var string|null
     */
    public $subject;

    /**
     * @var string|string[]|string[][]|null
     * @phpstan-var string|array<string|string[]>|null
     */
    public $message;

    /**
     * @var UploadedFile|UploadedFile[]|null[]|null
     * @phpstan-var UploadedFile|array<UploadedFile|null>|null
     */
    public $attachment;

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'fromName' => \Craft::t('contact-form', 'Your Name'),
            'fromEmail' => \Craft::t('contact-form', 'Your Email'),
            'message' => \Craft::t('contact-form', 'Message'),
            'subject' => \Craft::t('contact-form', 'Subject'),
        ];
    }

    /**
     * @inheritdoc
     */
    protected function defineRules(): array
    {
        return [
            [['fromEmail', 'message'], 'required'],
            [['fromEmail'], 'email'],
        ];
    }
}


================================================
FILE: src/templates/_settings.twig
================================================
{% import "_includes/forms" as forms %}

{% macro configWarning(setting) -%}
  {% set setting = '<code>'~setting~'</code>' %}
  {{ "This is being overridden by the {setting} config setting in your {file} config file."|t('contact-form', {
    setting: setting,
    file: 'contact-form.php'
  })|raw }}
{%- endmacro %}

{% from _self import configWarning %}

{{ forms.autosuggestField({
  first:        true,
  label:        "To Email"|t('contact-form'),
  required:     true,
  id:           'toEmail',
  name:         'toEmail',
  instructions: "The email address(es) that the contact form will send to. Separate multiple email addresses with commas."|t('contact-form'),
  value:        settings.toEmail,
  suggestEnvVars: true,
  autofocus:    true,
  disabled:     'toEmail' in overrides,
  warning:      'toEmail' in overrides ? configWarning('toEmail'),
  errors:       settings.getErrors('toEmail')
}) }}

{{ forms.textField({
  label:        "Sender Text"|t('contact-form'),
  id:           'prependSender',
  name:         'prependSender',
  instructions: "Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by."|t('contact-form'),
  value:        (settings.prependSender ? settings.prependSender : ""),
  disabled:     'prependSender' in overrides,
  warning:      'prependSender' in overrides ? configWarning('prependSender'),
  errors:       settings.getErrors('prependSender')
}) }}

{{ forms.textField({
  label:        "Subject Text"|t('contact-form'),
  id:           'prependSubject',
  name:         'prependSubject',
  instructions: "Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form."|t('contact-form'),
  value:        (settings.prependSubject ? settings.prependSubject : ""),
  disabled:     'prependSubject' in overrides,
  warning:      'prependSubject' in overrides ? configWarning('prependSubject'),
  errors:       settings.getErrors('prependSubject')
}) }}

{{ forms.lightswitchField({
  label:        "Allow attachments?"|t('contact-form'),
  id:           'allowAttachments',
  name:         'allowAttachments',
  on:           settings.allowAttachments,
  disabled:     'allowAttachments' in overrides,
  warning:      'allowAttachments' in overrides ? configWarning('allowAttachments'),
  errors:       settings.getErrors('allowAttachments')
}) }}

{{ forms.textField({
  label:        "Success Flash Message"|t('contact-form'),
  id:           "successFlashMessage",
  name:         "successFlashMessage",
  instructions: "The flash message displayed after successfully sending a message."|t('contact-form'),
  value:        settings.successFlashMessage,
  disabled:     'successFlashMessage' in overrides,
  warning:      'successFlashMessage' in overrides ? configWarning('successFlashMessage'),
  errors:       settings.getErrors('successFlashMessage')
}) }}


================================================
FILE: src/translations/ar/contact-form.php
================================================
<?php

return [
    // model
    "To Email" => "البريد الإلكتروني للمستلم",
    'Your Name' => "الاسم",
    'Your Email' => "بريدك الإلكتروني",
    'Message' => "الرسالة",
    'Subject' => "الموضوع",

    // email body
    'Email' => 'البريد',
    'Name' => 'الاسم',

    // error messages
    //'There was a problem with your submission, please check the form and try again!'
    //=> '',

    // plugin settings
    "The email address(es) that the contact form will send to. Separate multiple email addresses with commas."
    => "عنوان (عناوين) البريد الإلكتروني التي سيرسل اليها نموذج الاتصال. يجب فصل عناوين البريد الإلكتروني المتعددة باستخدام الفواصل.",
    "Sender Text"
    => "نص المرسل",
    "Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by."
    => "النص الذي سيتم إلحاقه قبل اسم مرسل رسالة البريد الإلكتروني للإشارة إلى المرسل الفعلي لنموذج الاتصال.",
    "On behalf of"
    => "نيابة عن",
    "Subject Text"
    => "نص الموضوع",
    "Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form."
    => "النص الذي سيتم إلحاقه بموضوع البريد الإلكتروني للإشارة عن أنه أتي من نموذج الاتصال.",
    "Allow attachments?"
    => "السماح بالمرفقات؟",
    "Success Flash Message"
    => "رسالة النجاح",
    "The flash message displayed after successfully sending a message."
    => "رسالة الإخطار التي يتم عرضها بعد إرسال رسالة بنجاح.",
    "Your message has been sent."
    => "تم ارسال رسالتك.",
];


================================================
FILE: src/translations/de/contact-form.php
================================================
<?php

return [
    // model
    "To Email" => "Empfängeradresse",
    'Your Name' => "Ihr Name",
    'Your Email' => "Ihre Email",
    'Message' => "Nachricht",
    'Subject' => "Betreff",

    // email body
    'Email' => 'E-Mail',
    'Name' => 'Name',

    // error messages
    'There was a problem with your submission, please check the form and try again!'
    => 'Es gab ein Problem beim Abschicken des Formulars, bitte Eingaben prüfen und erneut versuchen!',

    // plugin settings
    "The email address(es) that the contact form will send to. Separate multiple email addresses with commas."
    => "Die Email-Adresse(n) an welche das Kontaktformular senden wird. Trennen Sie mehrere Email-Adressen mit Kommas.",
    "Sender Text"
    => "Absendertext",
    "Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by."
    => "Text, welcher dem Absendernamen der Email vorangestellt wird, um aufzuzeigen von wem das Kontaktformular abgesendet wurde.",
    "On behalf of"
    => "Mitteilung von",
    "Subject Text"
    => "Betreffzeile",
    "Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form."
    => "Text, welcher der Betreffzeile vorangestellt wird, um aufzuzeigen dass die Mail vom Kontaktformular stammt",
    "Allow attachments?"
    => "Anlagen erlauben?",
    "Success Flash Message"
    => "Erfolgsnachricht",
    "The flash message displayed after successfully sending a message."
    => "Die Statusnachricht welche angezeigt wird, wenn eine Nachricht erfolgreich versendet wurde.",
    "Your message has been sent."
    => "Ihre Nachricht wurde versendet.",
];


================================================
FILE: src/translations/es/contact-form.php
================================================
<?php

return [
    // model
    "To Email" => "Destinatario",
    'Your Name' => "Su Nombre",
    'Your Email' => "Su email",
    'Message' => "Mensaje",
    'Subject' => "Asunto",

    // email body
    'Email' => 'Correo electrónico',
    'Name' => 'Nombre',

    // error messages
    'There was a problem with your submission, please check the form and try again!'
    => 'Hubo un problema con su envío, por favor revise el formulario y vuelva a intentarlo!',

    // plugin settings
    "The email address(es) that the contact form will send to. Separate multiple email addresses with commas."
    => "La(s) direccion(es) de email a las que se enviará el email de contacto. Separa las distintas direcciones de email con comas.",
    "Sender Text"
    => "Texto del remitente",
    "Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by."
    => "Texto que se antepondrá al Nombre del remitente del email para informar a quién se envió realmente el correo de contacto.",
    "On behalf of"
    => "A nombre de",
    "Subject Text"
    => "Texto del asunto",
    "Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form."
    => "Texto que se agregará previamente al asunto del correo para informar que proviene del formulario de contacto.",
    "Allow attachments?"
    => "Permitir adjuntos?",
    "Success Flash Message"
    => "Mensaje flash para correos enviados",
    "The flash message displayed after successfully sending a message."
    => "El mensaje flash que se muestra después de enviar un mensaje correctamente.",
    "Your message has been sent."
    => "Su mensaje ha sido enviado.",
];


================================================
FILE: src/translations/fr/contact-form.php
================================================
<?php

return [
    // model
    "To Email" => "Destinataire",
    'Your Name' => "Votre nom",
    'Your Email' => "Votre email",
    'Message' => "Message",
    'Subject' => "Sujet",

    // email body
    'Email' => 'E-mail',
    'Name' => 'Nom',

    // error messages
    'There was a problem with your submission, please check the form and try again!'
    => 'Une erreur est survenue lors de la soumission, vérifiez le formulaire et essayez à nouveau !',

    // plugin settings
    "The email address(es) that the contact form will send to. Separate multiple email addresses with commas."
    => "La ou les adresses qui recevront les emails. Séparez les adresses multiples par des virgules.",
    "Sender Text"
    => "Texte expéditeur",
    "Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by."
    => "Texte qui sera ajouté avant le nom de l'expéditeur pour indiquer par qui le formulaire de contact a été envoyé.",
    "On behalf of"
    => "De la part de",
    "Subject Text"
    => "Texte sujet",
    "Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form."
    => "Texte qui sera ajouté au début du sujet du mail pour indiquer qu'il a été envoyé depuis le formulaire de contact.",
    "Allow attachments?"
    => "Autoriser les pièces jointes ?",
    "Success Flash Message"
    => "Message de succès",
    "The flash message displayed after successfully sending a message."
    => "Le message affiché après avoir envoyé un message avec succès.",
    "Your message has been sent."
    => "Votre message a été envoyé.",
];


================================================
FILE: src/translations/it/contact-form.php
================================================
<?php

return [
    // model
    "To Email" => "Destinatario",
    'Your Name' => "Nominativo",
    'Your Email' => "Email",
    'Message' => "Messaggio",
    'Subject' => "Oggetto",

    // email body
    'Email' => 'Email',
    'Name' => 'Nome',

    // error messages
    'There was a problem with your submission, please check the form and try again!'
    => "Si è verificato un problema con l'invio, controlla il modulo e riprova!",

    // plugin settings
    "The email address(es) that the contact form will send to. Separate multiple email addresses with commas."
    => "Gli indirizzi che riceveranno le email. Separa gli indirizzi multipli con le virgole.",
    "Sender Text"
    => "Testo del mittente",
    "Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by."
    => "Testo che verrà anteposto al mittente dell'email per indicare da chi è stato effettivamente inviato il modulo contatti.",
    "On behalf of"
    => "Per conto di",
    "Subject Text"
    => "Oggetto",
    "Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form."
    => "Testo che verrà anteposto all'oggetto dell'email per segnalare che proviene dal modulo contatti.",
    "Allow attachments?"
    => "Consentire allegati?",
    "Success Flash Message"
    => "Messaggio di successo",
    "The flash message displayed after successfully sending a message."
    => "Il messaggio flash visualizzato dopo aver inviato con successo un messaggio.",
    "Your message has been sent."
    => "Il tuo messaggio è stato inviato.",
];


================================================
FILE: src/translations/nl/contact-form.php
================================================
<?php

return [
    // model
    "To Email" => "E-mail naar",
    'Your Name' => "Jouw naam",
    'Your Email' => "Jouw e-mailadres",
    'Message' => "Bericht",
    'Subject' => "Onderwerp",

    // email body
    'Email' => 'E-mail',
    'Name' => 'Naam',

    // error messages
    "There was a problem with your submission, please check the form and try again!"
    => "Er was een probleem met je inzending, controleer het formulier en probeer het opnieuw!",

    // plugin settings
    "The email address(es) that the contact form will send to. Separate multiple email addresses with commas."
    => "Het e-mailadres of de e-mailadressen naar waar het contactformulier dient te verzenden. Scheid meerdere e-mailadressen met komma's.",
    "Sender Text"
    => "Afzendersbericht",
    "Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by."
    => "Tekst die wordt toegevoegd aan de naam van de verzender om te informeren door wie het contactformulier is verzonden.",
    "On behalf of"
    => "Namens",
    "Subject Text"
    => "Onderwerp tekst",
    "Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form."
    => "Tekst die zal worden toegevoegd aan het onderwerp om te tonen dat het afkomstig is van het contactformulier.",
    "Allow attachments?"
    => "Bijlagen toestaan?",
    "Success Flash Message"
    => "Statusbericht geslaagde verzending",
    "The flash message displayed after successfully sending a message."
    => "Het statusbericht dat wordt getoond na het succesvol versturen van een bericht.",
    "Your message has been sent."
    => "Jouw bericht is verstuurd.",
];
Download .txt
gitextract_mleti689/

├── .ddev/
│   └── config.yaml
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   └── workflows/
│       ├── ci.yml
│       ├── create-release.yml
│       └── issues.yml
├── .gitignore
├── .lintstagedrc.json
├── .prettierignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── ecs.php
├── package.json
├── phpstan.neon
└── src/
    ├── Mailer.php
    ├── Plugin.php
    ├── controllers/
    │   └── SendController.php
    ├── events/
    │   └── SendEvent.php
    ├── models/
    │   ├── Settings.php
    │   └── Submission.php
    ├── templates/
    │   └── _settings.twig
    └── translations/
        ├── ar/
        │   └── contact-form.php
        ├── de/
        │   └── contact-form.php
        ├── es/
        │   └── contact-form.php
        ├── fr/
        │   └── contact-form.php
        ├── it/
        │   └── contact-form.php
        └── nl/
            └── contact-form.php
Download .txt
SYMBOL INDEX (20 symbols across 6 files)

FILE: src/Mailer.php
  class Mailer (line 19) | class Mailer extends Component
    method send (line 39) | public function send(Submission $submission, bool $runValidation = tru...
    method getFromEmail (line 143) | public function getFromEmail($from): string
    method compileFromName (line 168) | public function compileFromName(string $fromName = null): string
    method compileSubject (line 180) | public function compileSubject(string $subject = null): string
    method compileTextBody (line 192) | public function compileTextBody(Submission $submission): string
    method compileHtmlBody (line 241) | public function compileHtmlBody(string $textBody): string

FILE: src/Plugin.php
  class Plugin (line 20) | class Plugin extends \craft\base\Plugin
    method getMailer (line 30) | public function getMailer(): Mailer
    method createSettingsModel (line 38) | protected function createSettingsModel(): ?Settings
    method settingsHtml (line 46) | protected function settingsHtml(): ?string

FILE: src/controllers/SendController.php
  class SendController (line 12) | class SendController extends Controller
    method actionIndex (line 24) | public function actionIndex()

FILE: src/events/SendEvent.php
  class SendEvent (line 14) | class SendEvent extends Event

FILE: src/models/Settings.php
  class Settings (line 12) | class Settings extends Model
    method init (line 48) | public function init(): void
    method defineRules (line 70) | protected function defineRules(): array

FILE: src/models/Submission.php
  class Submission (line 18) | class Submission extends Model
    method attributeLabels (line 50) | public function attributeLabels()
    method defineRules (line 63) | protected function defineRules(): array
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (67K chars).
[
  {
    "path": ".ddev/config.yaml",
    "chars": 10037,
    "preview": "name: contact-form\ntype: php\ndocroot: ''\nphp_version: '8.0'\nwebserver_type: nginx-fpm\nrouter_http_port: '80'\nrouter_http"
  },
  {
    "path": ".gitattributes",
    "chars": 999,
    "preview": "# Do not export those files in the Composer archive (lighter dependency)\n/.browserslistrc export-ignore\n/.codecov.yml ex"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 15,
    "preview": "* @brandonkelly"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 531,
    "preview": "name: ci\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n  pull_request:\npermissions:\n  contents: read\nconcu"
  },
  {
    "path": ".github/workflows/create-release.yml",
    "chars": 611,
    "preview": "name: Create Release\nrun-name: Create release for ${{ github.event.client_payload.version }}\n\non:\n  repository_dispatch:"
  },
  {
    "path": ".github/workflows/issues.yml",
    "chars": 336,
    "preview": "name: Add issues to project\n\non:\n  issues:\n    types:\n      - opened\n      - transferred\n\njobs:\n  add-to-project:\n    ru"
  },
  {
    "path": ".gitignore",
    "chars": 58,
    "preview": "*.idea/*\n*.log\n*.DS_Store\n*Thumbs.db\n/vendor\nnode_modules\n"
  },
  {
    "path": ".lintstagedrc.json",
    "chars": 146,
    "preview": "{\n  \"**/*.php\": [\n    \"./vendor/bin/ecs check --ansi --fix\",\n    \"./vendor/bin/phpstan analyse\"\n  ],\n  \"*\": \"prettier --"
  },
  {
    "path": ".prettierignore",
    "chars": 118,
    "preview": "*.md\n*.php\ncomposer.lock\ncpresources/*\nlib/*\nsrc/templates/*\nsrc/web/assets/**/dist/*\ntests/_craft/*\nvendor/*\n.ddev/*\n"
  },
  {
    "path": ".prettierrc.json",
    "chars": 88,
    "preview": "{\n  \"singleQuote\": true,\n  \"bracketSpacing\": false,\n  \"vueIndentScriptAndStyle\": true\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7741,
    "preview": "# Release Notes for Contact Form\n\n## 3.1.0 - 2024-03-11\n\n- Added Craft 5 compatibility.\n- Added a missing Dutch translat"
  },
  {
    "path": "LICENSE.md",
    "chars": 1086,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Pixel & Tonic, Inc.\n\nPermission is hereby granted, free of charge, to any pers"
  },
  {
    "path": "README.md",
    "chars": 10666,
    "preview": "# Contact Form for Craft CMS\n\nThis plugin allows you to add an email contact form to your website.\n\n## Requirements\n\nThi"
  },
  {
    "path": "composer.json",
    "chars": 1562,
    "preview": "{\n  \"name\": \"craftcms/contact-form\",\n  \"description\": \"Add a simple contact form to your Craft CMS site\",\n  \"type\": \"cra"
  },
  {
    "path": "ecs.php",
    "chars": 288,
    "preview": "<?php\n\nuse craft\\ecs\\SetList;\nuse Symplify\\EasyCodingStandard\\Config\\ECSConfig;\n\nreturn static function(ECSConfig $ecsCo"
  },
  {
    "path": "package.json",
    "chars": 215,
    "preview": "{\n  \"name\": \"@craftcms/contact-form\",\n  \"private\": true,\n  \"devDependencies\": {\n    \"husky\": \"^7.0.4\",\n    \"lint-staged\""
  },
  {
    "path": "phpstan.neon",
    "chars": 104,
    "preview": "includes:\n    - vendor/craftcms/phpstan/phpstan.neon\n\nparameters:\n    level: 4\n    paths:\n        - src\n"
  },
  {
    "path": "src/Mailer.php",
    "chars": 7993,
    "preview": "<?php\n\nnamespace craft\\contactform;\n\nuse Craft;\nuse craft\\contactform\\events\\SendEvent;\nuse craft\\contactform\\models\\Sub"
  },
  {
    "path": "src/Plugin.php",
    "chars": 1248,
    "preview": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace "
  },
  {
    "path": "src/controllers/SendController.php",
    "chars": 2127,
    "preview": "<?php\n\nnamespace craft\\contactform\\controllers;\n\nuse Craft;\nuse craft\\contactform\\models\\Submission;\nuse craft\\contactfo"
  },
  {
    "path": "src/events/SendEvent.php",
    "chars": 687,
    "preview": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace "
  },
  {
    "path": "src/models/Settings.php",
    "chars": 1673,
    "preview": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace "
  },
  {
    "path": "src/models/Submission.php",
    "chars": 1359,
    "preview": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace "
  },
  {
    "path": "src/templates/_settings.twig",
    "chars": 2889,
    "preview": "{% import \"_includes/forms\" as forms %}\n\n{% macro configWarning(setting) -%}\n  {% set setting = '<code>'~setting~'</code"
  },
  {
    "path": "src/translations/ar/contact-form.php",
    "chars": 1497,
    "preview": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"البريد الإلكتروني للمستلم\",\n    'Your Name' => \"الاسم\",\n    'Your Email'"
  },
  {
    "path": "src/translations/de/contact-form.php",
    "chars": 1676,
    "preview": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Empfängeradresse\",\n    'Your Name' => \"Ihr Name\",\n    'Your Email' => \"I"
  },
  {
    "path": "src/translations/es/contact-form.php",
    "chars": 1697,
    "preview": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Destinatario\",\n    'Your Name' => \"Su Nombre\",\n    'Your Email' => \"Su e"
  },
  {
    "path": "src/translations/fr/contact-form.php",
    "chars": 1629,
    "preview": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Destinataire\",\n    'Your Name' => \"Votre nom\",\n    'Your Email' => \"Votr"
  },
  {
    "path": "src/translations/it/contact-form.php",
    "chars": 1602,
    "preview": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Destinatario\",\n    'Your Name' => \"Nominativo\",\n    'Your Email' => \"Ema"
  },
  {
    "path": "src/translations/nl/contact-form.php",
    "chars": 1689,
    "preview": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"E-mail naar\",\n    'Your Name' => \"Jouw naam\",\n    'Your Email' => \"Jouw "
  }
]

About this extraction

This page contains the full source code of the pixelandtonic/ContactForm GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (60.9 KB), approximately 16.3k tokens, and a symbol index with 20 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!