[
  {
    "path": ".ddev/config.yaml",
    "content": "name: contact-form\ntype: php\ndocroot: ''\nphp_version: '8.0'\nwebserver_type: nginx-fpm\nrouter_http_port: '80'\nrouter_https_port: '443'\nxdebug_enabled: false\nadditional_hostnames: []\nadditional_fqdns: []\ndatabase:\n  type: mariadb\n  version: '10.4'\nnfs_mount_enabled: false\nmutagen_enabled: false\nuse_dns_when_possible: true\ncomposer_version: '2'\nweb_environment: []\nnodejs_version: '16'\n# Key features of ddev's config.yaml:\n\n# name: <projectname> # Name of the project, automatically provides\n#   http://projectname.ddev.site and https://projectname.ddev.site\n\n# type: <projecttype>  # drupal6/7/8, backdrop, typo3, wordpress, php\n\n# docroot: <relative_path> # Relative path to the directory containing index.php.\n\n# 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\"\n\n# You can explicitly specify the webimage but this\n# is not recommended, as the images are often closely tied to ddev's' behavior,\n# so this can break upgrades.\n\n# webimage: <docker_image>  # nginx/php docker image.\n\n# database:\n#   type: <dbtype> # mysql, mariadb\n#   version: <version> # database version, like \"10.3\" or \"8.0\"\n# Note that mariadb_version or mysql_version from v1.18 and earlier\n# will automatically be converted to this notation with just a \"ddev config --auto\"\n\n# router_http_port: <port>  # Port to be used for http (defaults to port 80)\n# router_https_port: <port> # Port for https (defaults to 443)\n\n# xdebug_enabled: false  # Set to true to enable xdebug and \"ddev start\" or \"ddev restart\"\n# Note that for most people the commands\n# \"ddev xdebug\" to enable xdebug and \"ddev xdebug off\" to disable it work better,\n# as leaving xdebug enabled all the time is a big performance hit.\n\n# xhprof_enabled: false  # Set to true to enable xhprof and \"ddev start\" or \"ddev restart\"\n# Note that for most people the commands\n# \"ddev xhprof\" to enable xhprof and \"ddev xhprof off\" to disable it work better,\n# as leaving xhprof enabled all the time is a big performance hit.\n\n# webserver_type: nginx-fpm  # or apache-fpm\n\n# timezone: Europe/Berlin\n# This is the timezone used in the containers and by PHP;\n# it can be set to any valid timezone,\n# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n# For example Europe/Dublin or MST7MDT\n\n# composer_root: <relative_path>\n# Relative path to the composer root directory from the project root. This is\n# the directory which contains the composer.json and where all Composer related\n# commands are executed.\n\n# composer_version: \"2\"\n# You can set it to \"\" or \"2\" (default) for Composer v2 or \"1\" for Composer v1\n# to use the latest major version available at the time your container is built.\n# It is also possible to use each other Composer version channel. This includes:\n#   - 2.2 (latest Composer LTS version)\n#   - stable\n#   - preview\n#   - snapshot\n# Alternatively, an explicit Composer version may be specified, for example \"2.2.18\".\n# To reinstall Composer after the image was built, run \"ddev debug refresh\".\n\n# nodejs_version: \"16\"\n# change from the default system Node.js version to another supported version, like 12, 14, 17, 18.\n# Note that you can use 'ddev nvm' or nvm inside the web container to provide nearly any\n# Node.js version, including v6, etc.\n\n# additional_hostnames:\n#  - somename\n#  - someothername\n# would provide http and https URLs for \"somename.ddev.site\"\n# and \"someothername.ddev.site\".\n\n# additional_fqdns:\n#  - example.com\n#  - sub1.example.com\n# would provide http and https URLs for \"example.com\" and \"sub1.example.com\"\n# Please take care with this because it can cause great confusion.\n\n# upload_dir: custom/upload/dir\n# would set the destination path for ddev import-files to <docroot>/custom/upload/dir\n# When mutagen is enabled this path is bind-mounted so that all the files\n# in the upload_dir don't have to be synced into mutagen\n\n# working_dir:\n#   web: /var/www/html\n#   db: /home\n# would set the default working directory for the web and db services.\n# These values specify the destination directory for ddev ssh and the\n# directory in which commands passed into ddev exec are run.\n\n# omit_containers: [db, dba, ddev-ssh-agent]\n# Currently only these containers are supported. Some containers can also be\n# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit\n# the \"db\" container, several standard features of ddev that access the\n# database container will be unusable. In the global configuration it is also\n# possible to omit ddev-router, but not here.\n\n# nfs_mount_enabled: false\n# Great performance improvement but requires host configuration first.\n# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs\n\n# mutagen_enabled: false\n# Performance improvement using mutagen asynchronous updates.\n# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen\n\n# fail_on_hook_fail: False\n# Decide whether 'ddev start' should be interrupted by a failing hook\n\n# host_https_port: \"59002\"\n# The host port binding for https can be explicitly specified. It is\n# dynamic unless otherwise specified.\n# This is not used by most people, most people use the *router* instead\n# of the localhost port.\n\n# host_webserver_port: \"59001\"\n# The host port binding for the ddev-webserver can be explicitly specified. It is\n# dynamic unless otherwise specified.\n# This is not used by most people, most people use the *router* instead\n# of the localhost port.\n\n# host_db_port: \"59002\"\n# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic\n# unless explicitly specified.\n\n# phpmyadmin_port: \"8036\"\n# phpmyadmin_https_port: \"8037\"\n# The PHPMyAdmin ports can be changed from the default 8036 and 8037\n\n# host_phpmyadmin_port: \"8036\"\n# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed\n# through ddev-router, but it can be specified and bound.\n\n# mailhog_port: \"8025\"\n# mailhog_https_port: \"8026\"\n# The MailHog ports can be changed from the default 8025 and 8026\n\n# host_mailhog_port: \"8025\"\n# The mailhog port is not normally bound on the host at all, instead being routed\n# through ddev-router, but it can be bound directly to localhost if specified here.\n\n# webimage_extra_packages: [php7.4-tidy, php-bcmath]\n# Extra Debian packages that are needed in the webimage can be added here\n\n# dbimage_extra_packages: [telnet,netcat]\n# Extra Debian packages that are needed in the dbimage can be added here\n\n# use_dns_when_possible: true\n# If the host has internet access and the domain configured can\n# successfully be looked up, DNS will be used for hostname resolution\n# instead of editing /etc/hosts\n# Defaults to true\n\n# project_tld: ddev.site\n# The top-level domain used for project URLs\n# The default \"ddev.site\" allows DNS lookup via a wildcard\n# If you prefer you can change this to \"ddev.local\" to preserve\n# pre-v1.9 behavior.\n\n# ngrok_args: --basic-auth username:pass1234\n# Provide extra flags to the \"ngrok http\" command, see\n# https://ngrok.com/docs#http or run \"ngrok http -h\"\n\n# disable_settings_management: false\n# If true, ddev will not create CMS-specific settings files like\n# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php\n# In this case the user must provide all such settings.\n\n# You can inject environment variables into the web container with:\n# web_environment:\n# - SOMEENV=somevalue\n# - SOMEOTHERENV=someothervalue\n\n# no_project_mount: false\n# (Experimental) If true, ddev will not mount the project into the web container;\n# the user is responsible for mounting it manually or via a script.\n# This is to enable experimentation with alternate file mounting strategies.\n# For advanced users only!\n\n# bind_all_interfaces: false\n# If true, host ports will be bound on all network interfaces,\n# not just the localhost interface. This means that ports\n# will be available on the local network if the host firewall\n# allows it.\n\n# default_container_timeout: 120\n# The default time that ddev waits for all containers to become ready can be increased from\n# the default 120. This helps in importing huge databases, for example.\n\n#web_extra_exposed_ports:\n#- name: nodejs\n#  container_port: 3000\n#  http_port: 2999\n#  https_port: 3000\n#- name: something\n#  container_port: 4000\n#  https_port: 4000\n#  http_port: 3999\n# Allows a set of extra ports to be exposed via ddev-router\n# The port behavior on the ddev-webserver must be arranged separately, for example\n# using web_extra_daemons.\n# For example, with a web app on port 3000 inside the container, this config would\n# expose that web app on https://<project>.ddev.site:9999 and http://<project>.ddev.site:9998\n# web_extra_exposed_ports:\n#  - container_port: 3000\n#    http_port: 9998\n#    https_port: 9999\n\n#web_extra_daemons:\n#- name: \"http-1\"\n#  command: \"/var/www/html/node_modules/.bin/http-server -p 3000\"\n#  directory: /var/www/html\n#- name: \"http-2\"\n#  command: \"/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000\"\n#  directory: /var/www/html\n\n# override_config: false\n# By default, config.*.yaml files are *merged* into the configuration\n# But this means that some things can't be overridden\n# For example, if you have 'nfs_mount_enabled: true'' you can't override it with a merge\n# and you can't erase existing hooks or all environment variables.\n# However, with \"override_config: true\" in a particular config.*.yaml file,\n# 'nfs_mount_enabled: false' can override the existing values, and\n# hooks:\n#   post-start: []\n# or\n# web_environment: []\n# or\n# additional_hostnames: []\n# can have their intended affect. 'override_config' affects only behavior of the\n# config.*.yaml file it exists in.\n\n# Many ddev commands can be extended to run tasks before or after the\n# ddev command is executed, for example \"post-start\", \"post-import-db\",\n# \"pre-composer\", \"post-composer\"\n# See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more\n# information on the commands that can be extended and the tasks you can define\n# for them. Example:\n#hooks:\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Do not export those files in the Composer archive (lighter dependency)\n/.browserslistrc export-ignore\n/.codecov.yml export-ignore\n/.env.example export-ignore\n/.git-blame-ignore-revs export-ignore\n/.gitattributes export-ignore\n/.gitignore export-ignore\n/.lintstagedrc.json export-ignore\n/.nvmrc export-ignore\n/crowdin.yml export-ignore\n/CHANGELOG.md export-ignore\n/README.md export-ignore\n/SECURITY.md export-ignore\n/codeception.yml export-ignore\n/composer.lock export-ignore\n/ecs.php export-ignore\n/phpstan.neon export-ignore\n/.prettierrc.json export-ignore\n/.prettierignore export-ignore\n/gruntfile.js export-ignore\n/gulpfile.js export-ignore\n/lerna.json export-ignore\n/package.json export-ignore\n/package-lock.json export-ignore\n/tsconfig.json export-ignore\n/webpack.config.js export-ignore\n/stubs/ export-ignore\n/tests/ export-ignore\n/packages/ export-ignore\n/.ddev/ export-ignore\n/.github/ export-ignore\n/.husky/ export-ignore\n\n# Auto detect text files and perform LF normalization\n* text=auto"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @brandonkelly"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n  pull_request:\npermissions:\n  contents: read\nconcurrency:\n  group: ci-${{ github.ref }}\n  cancel-in-progress: true\njobs:\n  ci:\n    name: ci\n    uses: craftcms/.github/.github/workflows/ci.yml@v3\n    with:\n      craft_version: '4'\n      jobs: '[\"ecs\", \"phpstan\", \"prettier\"]'\n      notify_slack: true\n      slack_subteam: <!subteam^SGFL9NKNZ>\n    secrets:\n      token: ${{ secrets.GITHUB_TOKEN }}\n      slack_webhook_url: ${{ secrets.SLACK_PLUGIN_WEBHOOK_URL }}\n"
  },
  {
    "path": ".github/workflows/create-release.yml",
    "content": "name: Create Release\nrun-name: Create release for ${{ github.event.client_payload.version }}\n\non:\n  repository_dispatch:\n    types:\n      - craftcms/new-release\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: ncipollo/release-action@v1\n        with:\n          body: ${{ github.event.client_payload.notes }}\n          makeLatest: ${{ github.event.client_payload.latest }}\n          name: ${{ github.event.client_payload.version }}\n          prerelease: ${{ github.event.client_payload.prerelease }}\n          tag: ${{ github.event.client_payload.tag }}\n"
  },
  {
    "path": ".github/workflows/issues.yml",
    "content": "name: Add issues to project\n\non:\n  issues:\n    types:\n      - opened\n      - transferred\n\njobs:\n  add-to-project:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/add-to-project@v0.4.0\n        with:\n          project-url: https://github.com/orgs/craftcms/projects/16\n          github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.idea/*\n*.log\n*.DS_Store\n*Thumbs.db\n/vendor\nnode_modules\n"
  },
  {
    "path": ".lintstagedrc.json",
    "content": "{\n  \"**/*.php\": [\n    \"./vendor/bin/ecs check --ansi --fix\",\n    \"./vendor/bin/phpstan analyse\"\n  ],\n  \"*\": \"prettier --ignore-unknown --write\"\n}\n"
  },
  {
    "path": ".prettierignore",
    "content": "*.md\n*.php\ncomposer.lock\ncpresources/*\nlib/*\nsrc/templates/*\nsrc/web/assets/**/dist/*\ntests/_craft/*\nvendor/*\n.ddev/*\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"singleQuote\": true,\n  \"bracketSpacing\": false,\n  \"vueIndentScriptAndStyle\": true\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Release Notes for Contact Form\n\n## 3.1.0 - 2024-03-11\n\n- Added Craft 5 compatibility.\n- Added a missing Dutch translation. ([#257](https://github.com/craftcms/contact-form/issues/257))\n- 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))\n\n## 3.0.1 - 2023-03-16\n\n- Added translations for `Email` and `Name`. ([#235](https://github.com/craftcms/contact-form/issues/235))\n- Fixed an error that occurred if `fromName` was not specified on submissions. ([#228](https://github.com/craftcms/contact-form/issues/228))\n\n## 3.0.0 - 2022-05-02\n\n### Added\n- Added Craft 4 compatibility.\n- Added the `allowedMessageFields` setting, which can be used to restrict which `message` fields are allowed to be submitted.\n\n### Changed\n- Failed submissions are now passed back to the template as a `submission` variable, instead of `message`.\n- The `contact-form/send` action now returns a 400 status on failure for Ajax requests.\n\n## 2.5.2 - 2023-03-16\n\n- Added translations for `Email` and `Name`. ([#235](https://github.com/craftcms/contact-form/issues/235))\n\n## 2.5.1 - 2022-05-02\n\n### Fixed\n- Fixed a bug where newlines were getting replaced with double newlines in message bodies. ([#214](https://github.com/craftcms/contact-form/issues/214))\n\n## 2.5.0 - 2022-04-15\n\n### Added\n- Added the `allowedMessageFields` setting, which can be used to restrict which `message` fields are allowed to be submitted.\n\n## 2.4.1 - 2022-04-12\n\n### Fixed\n- Fixed a potential PHP error.\n\n## 2.4.0 - 2022-04-11\n\n### Changed\n- Custom message fields’ labels can now be translated using the `site` translation category. ([#161](https://github.com/craftcms/contact-form/pull/161))\n\n## 2.3.0 - 2022-01-21\n\n### Changed\n- Craft 3.4 or later is now required.\n- The success flash message is now returned in the response for AJAX calls.\n- `craft\\contactform\\models\\Submission` now supports `EVENT_DEFINE_RULES`. ([#196](https://github.com/craftcms/contact-form/pull/196))\n\n## 2.2.7 - 2020-05-04\n\n### Changed\n- Added case-insensitive extension check for attachments.\n\n## 2.2.6 - 2019-12-17\n\n### Changed\n- 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))\n- Contact Form now requires Craft 3.1 or later.\n\n## 2.2.5 - 2019-05-31\n\n### Changed\n- Contact Form now respects Craft’s [allowedFileExtensions](https://docs.craftcms.com/v3/config/config-settings.html#allowedfileextensions) config setting.\n- 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))\n\n## 2.2.4 - 2019-04-03\n\n### Fixed\n- Fixed an issue with Japanese Characters. ([#158](https://github.com/craftcms/contact-form/pull/158))\n\n## 2.2.3 - 2018-11-13\n\n### Added\n- Contact Form is now translated into Dutch. ([#139](https://github.com/craftcms/contact-form/pull/139))\n\n### Fixed\n- Fixed a bug where the submission email address was not being validated. ([#145](https://github.com/craftcms/contact-form/issues/145))\n\n## 2.2.2 - 2018-07-19\n\n### Fixed\n- Fixed a PHP error introduced in 2.2.1 that broke submissions that were using a single `message` form input.\n\n## 2.2.1 - 2018-07-18\n\n### Fixed\n- Fixed a bug where blank messages wouldn’t fail validation if the message was split into multiple fields.\n\n## 2.2.0 - 2018-07-18\n\n### Added\n- Contact Form is now translated into Arabic. ([#125](https://github.com/craftcms/contact-form/pull/125))\n\n### Changed\n- Contact emails no longer list the Name field if none was provided. ([#126](https://github.com/craftcms/contact-form/issues/126))\n- 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))  \n\n### Fixed\n- Fixed a bug where single carriage returns in email message bodies were being ignored. ([#118](https://github.com/craftcms/contact-form/issues/118))\n- Fixed a bug where HTML in email bodies wasn’t getting escaped. ([#104](https://github.com/craftcms/contact-form/issues/104))\n- 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))\n\n## 2.1.1 - 2017-12-04\n\n### Changed\n- Loosened the Craft CMS version requirement to allow any 3.x version.\n\n## 2.1.0 - 2017-10-12\n\n### Added\n- Added German translations.\n\n### Changed\n- Email message bodies now include the sender’s name and email. ([#97](https://github.com/craftcms/contact-form/pull/97))\n\n## 2.0.3 - 2017-09-15\n\n### Added\n- Craft 3 Beta 27 compatibility.\n\n## 2.0.2 - 2017-07-07\n\n### Added\n- Craft 3 Beta 20 compatibility.\n\n## 2.0.1 - 2017-06-12\n\n### Fixed\n- Fixed a bug where the `message` variable was not available to contact form templates when the submission contained validation errors.\n\n## 2.0.0 - 2017-05-16\n\n### Added\n- Added Craft 3 compatibility.\n- Added the `afterSend` event.\n\n### Changed\n- The `beforeSend` event now has `$submission` and `$message` properties, set to the user submission model and the compiled email message, respectively.\n- The `contactForm/sendMessage` action is now `contact-form/send`.\n\n### Removed\n- Removed honeypot field support. (Moved to the [contact-form-honeypot](https://github.com/craftcms/contact-form-honeypot) plugin.)\n- Removed the `beforeMessageCompile` event.\n- Removed the `$isValid` property from the `beforeSend` event. Use the `beforeValidate` event on the `Submission` model to prevent submissions from going through.\n\n## 1.8.1 - 2016-09-02\n\n### Fixed\n- Fixed a bug where the HTML body of an email was being escaped displaying HTML entities in the email.\n\n## 1.8.0 - 2016-08-18\n\n### Added\n- Added the ability for plugins to modify the email's plain text and HTML body via the `contactForm.beforeMessageCompile` event.\n\n### Fixed\n- Fixed a bug where Twig code that was entered in the email body or subject was getting parsed.\n\n## 1.7.0 - 2016-01-18\n\n### Added\n- 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']`.\n\n### Changed\n- Custom message field values only have a single line break between them in the generated email body now, rather than two.\n\n## 1.6.0 - 2015-12-20\n\n### Added\n- Added the ability to attach multiple files to the contact email.\n- Added the ability to change the flash success message via the \"successFlashMessage\" setting.\n- Added the ability to override plugin settings via a `craft/config/contactform.php` config setting.\n\n### Changed\n- The \"prependSender\" and \"prependSubject\" settings can now be empty strings.\n\n### Fixed\n- Fixed a bug where the \"allowAttachments\" config setting wasn't being respected.\n\n## 1.5.0 - 2015-12-20\n\n### Added\n- Added support for some Craft 2.5 features.\n\n## 1.4.0 - 2014-06-01\n\n### Added\n- Added support for passing `{fromName}`, `{fromEmail}`, and `{subject}` in the ‘redirect’ URL.\n\n## 1.3.0 - 2014-01-07\n\n### Added\n- Added support for multiple email addresses\n- Added the ContactFormService\n- Added the `contactForm.beforeSend` event, allowing third party plugins to add extra validation\n\n## 1.2.0 - 2013-12-30\n\n### Added\n- Added honeypot captcha support\n\n## 1.1.0 - 2013-12-04\n\n### Added\n- Added the ability to submit attachments\n- Added the ability to submit the form over Ajax\n- Added the ability to submit checkbox lists, which get compiled into comma-separated lists in the email\n\n## 1.0.0 - 2013-10-03\n\n- Initial release\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Pixel & Tonic, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Contact Form for Craft CMS\n\nThis plugin allows you to add an email contact form to your website.\n\n## Requirements\n\nThis plugin requires Craft CMS 4.0.0+ or 5.0.0+.\n\n## Installation\n\nYou can install this plugin from the Plugin Store or with Composer.\n\n#### From the Plugin Store\n\nGo 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.\n\n#### With Composer\n\nOpen your terminal and run the following commands:\n\n```bash\n# go to the project directory\ncd /path/to/my-project.test\n\n# tell Composer to load the plugin\ncomposer require craftcms/contact-form\n\n# tell Craft to install the plugin\nphp craft plugin/install contact-form\n```\n\n## Usage\n\nYour contact form template can look something like this:\n\n```twig\n{% macro errorList(errors) %}\n    {% if errors %}\n        {{ ul(errors, {class: 'errors'}) }}\n    {% endif %}\n{% endmacro %}\n\n{% set submission = submission ?? null %}\n\n<form method=\"post\" action=\"\" accept-charset=\"UTF-8\">\n    {{ csrfInput() }}\n    {{ actionInput('contact-form/send') }}\n    {{ redirectInput('contact/thanks') }}\n\n    <h3><label for=\"from-name\">Your Name</label></h3>\n    {{ input('text', 'fromName', submission.fromName ?? '', {\n        id: 'from-name',\n        autocomplete: 'name',\n    }) }}\n    {{ submission ? _self.errorList(submission.getErrors('fromName')) }}\n\n    <h3><label for=\"from-email\">Your Email</label></h3>\n    {{ input('email', 'fromEmail', submission.fromEmail ?? '', {\n        id: 'from-email',\n        autocomplete: 'email',\n    }) }}\n    {{ submission ? _self.errorList(submission.getErrors('fromEmail')) }}\n\n    <h3><label for=\"subject\">Subject</label></h3>\n    {{ input('text', 'subject', submission.subject ?? '', {\n        id: 'subject',\n    }) }}\n    {{ submission ? _self.errorList(submission.getErrors('subject')) }}\n\n    <h3><label for=\"message\">Message</label></h3>\n    {{ tag('textarea', {\n        text: submission.message ?? '',\n        id: 'message',\n        name: 'message',\n        rows: 10,\n        cols: 40,\n    }) }}\n    {{ submission ? _self.errorList(submission.getErrors('message')) }}\n\n    <button type=\"submit\">Send</button>\n</form>\n```\n\nThe only required fields are `fromEmail` and `message`. Everything else is optional.\n\n### Redirecting after submit\n\nIf 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:\n\n- `{fromName}`\n- `{fromEmail}`\n- `{subject}`\n\nFor 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:\n\n```twig\n{{ redirectInput('contact/thanks?from={fromName}') }}\n```\n\nOn your `contact/thanks.html` template, you can access that `from` parameter using `craft.app.request.getQueryParam()`:\n\n```twig\n<p>Thanks for sending that in, {{ craft.app.request.getQueryParam('from') }}!</p>\n```\n\nNote that if you don’t include a `redirect` input, the current page will get reloaded.\n\n### Displaying flash messages\n\nWhen 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:\n\n```twig\n{% if craft.app.session.hasFlash('success') %}\n    <p class=\"message success\">{{ craft.app.session.getFlash('success') }}</p>\n{% elseif craft.app.session.hasFlash('error') %}\n    <p class=\"message error\">{{ craft.app.session.getFlash('error') }}</p>\n{% endif %}\n```\n\n### Adding additional fields\n\nYou can add additional fields to your form by splitting your `message` field into multiple fields, using an array syntax for the input names:\n\n```twig\n<h3><label for=\"message\">Message</label></h3>\n<textarea rows=\"10\" cols=\"40\" id=\"message\" name=\"message[body]\">{{ submission.message.body ?? '' }}</textarea>\n\n<h3><label for=\"phone\">Your phone number</label></h3>\n<input id=\"phone\" type=\"text\" name=\"message[Phone]\" value=\"\">\n\n<h3>What services are you interested in?</h3>\n<label><input type=\"checkbox\" name=\"message[Services][]\" value=\"Design\"> Design</label>\n<label><input type=\"checkbox\" name=\"message[Services][]\" value=\"Development\"> Development</label>\n<label><input type=\"checkbox\" name=\"message[Services][]\" value=\"Strategy\"> Strategy</label>\n<label><input type=\"checkbox\" name=\"message[Services][]\" value=\"Marketing\"> Marketing</label>\n```\n\nIf you have a primary “Message” field, you should name it `message[body]`, like in that example.\n\nAn email sent with the above form might result in the following message:\n\n    • Name: John Doe\n    • Email: example@email.com\n    • Phone: (555) 123-4567\n    • Services: Design, Development\n\n    Hey guys, I really loved this simple contact form (I'm so tired of agencies\n    asking for everything but my social security number up front), so I trust\n    you guys know a thing or two about usability.\n    \n    I run a small coffee shop and we want to start attracting more freelancer-\n    types to spend their days working from our shop (and sipping fine coffee!).\n    A clean new website with lots of social media integration would probably\n    help us out quite a bit there. Can you help us with that?\n    \n    Hope to hear from you soon.\n\n    Cathy Chino\n\nBy 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`:\n\n```php\n<?php\n\nreturn [\n    'allowedMessageFields' => ['Phone', 'Services'],\n];\n```\n\n### Overriding plugin settings\n\nIf you create a [config file](https://craftcms.com/docs/4.x/config/) in your `config/` folder called `contact-form.php`, you can override\nthe 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\na handy way to have different settings across multiple environments.\n\nHere’s what that config file might look like along with a list of all of the possible values you can override.\n\n```php\n<?php\n\nreturn [\n    'toEmail'             => 'bond@007.com',\n    'prependSubject'      => '',\n    'prependSender'       => '',\n    'allowAttachments'    => false,\n    'successFlashMessage' => 'Message sent!'\n];\n```\n\n### Dynamically adding email recipients\n\nYou can programmatically add email recipients from your template by adding a hidden input field named `toEmail` like so:\n\n```twig\n<input type=\"hidden\" name=\"toEmail\" value=\"{{ 'me@example.com'|hash }}\">\n```\n\nIf you want to add multiple recipients, you can provide a comma separated list of emails like so:\n\n```twig\n<input type=\"hidden\" name=\"toEmail\" value=\"{{ 'me@example.com,me2@example.com'|hash }}\">\n```\n\nThen from your `config/contact-form.php` config file, you’ll need to add a bit of logic:\n\n```php\n<?php\n\n$config = [];\n$request = Craft::$app->request;\n\nif (\n    !$request->getIsConsoleRequest() &&\n    ($toEmail = $request->getValidatedBodyParam('toEmail')) !== null\n) {\n    $config['toEmail'] = $toEmail;\n}\n\nreturn $config;\n```\n\nIn 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.\n\n### File attachments\n\nIf you would like your contact form to accept file attachments, follow these steps:\n\n1. Go to Settings → Contact Form in the Control Panel, and make sure the plugin is set to allow attachments.\n2. Make sure your opening HTML `<form>` tag contains `enctype=\"multipart/form-data\"`.\n3. Add a `<input type=\"file\" name=\"attachment\">` to your form.\n4. If you want to allow multiple file attachments, use multiple `<input type=\"file\" name=\"attachment[]\" multiple>` inputs.\n\n\n### Ajax form submissions\n\nYou 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:\n\n```js\n$('#my-form').submit(function(ev) {\n    // Prevent the form from actually submitting\n    ev.preventDefault();\n\n    // Send it to the server\n    $.post({\n        url: '/',\n        dataType: 'json',\n        data: $(this).serialize(),\n        success: function(response) {\n            $('#thanks').text(response.message).fadeIn();\n        },\n        error: function(jqXHR) {\n          // The response body will be an object containing the following keys:\n          // - `message` – A high level message for the response\n          // - `submission` – An object containing data from the attempted submission\n          // - `errors` – An object containing validation errors from the submission, indexed by attribute name\n          alert(jqXHR.responseJSON.message);\n        }\n    });\n});\n```\n\n### The `afterValidate` event\n\nModules 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:\n\n```php\nuse craft\\contactform\\models\\Submission;\nuse yii\\base\\Event;\n\n// ...\n\nEvent::on(Submission::class, Submission::EVENT_AFTER_VALIDATE, function(Event $e) {\n    /** @var Submission $submission */\n    $submission = $e->sender;\n    \n    // Make sure that `message[Phone]` was filled in\n    if (empty($submission->message['Phone'])) {\n        // Add the error\n        // (This will be accessible via `message.getErrors('message.phone')` in the template.)\n        $submission->addError('message.phone', 'A phone number is required.');\n    }\n});\n```\n\n\n### The `beforeSend` event\n\nModules 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:\n\n```php\nuse craft\\contactform\\events\\SendEvent;\nuse craft\\contactform\\Mailer;\nuse yii\\base\\Event;\n\n// ...\n\nEvent::on(Mailer::class, Mailer::EVENT_BEFORE_SEND, function(SendEvent $e) {\n    $isSpam = // custom spam detection logic...\n\n    if ($isSpam) {\n        $e->isSpam = true;\n    }\n});\n```\n\n\n### The `afterSend` event\n\nModules and plugins can be notified right after a message is sent out to the recipients using the `afterSend` event.\n\n```php\nuse craft\\contactform\\events\\SendEvent;\nuse craft\\contactform\\Mailer;\nuse yii\\base\\Event;\n\n// ...\n\nEvent::on(Mailer::class, Mailer::EVENT_AFTER_SEND, function(SendEvent $e) {\n    // custom logic...\n});\n```\n\n### Using a “Honeypot” field\n\nSupport 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.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"craftcms/contact-form\",\n  \"description\": \"Add a simple contact form to your Craft CMS site\",\n  \"type\": \"craft-plugin\",\n  \"keywords\": [\n    \"cms\",\n    \"craftcms\",\n    \"contact\",\n    \"form\",\n    \"yii2\"\n  ],\n  \"license\": \"MIT\",\n  \"authors\": [\n    {\n      \"name\": \"Pixel & Tonic\",\n      \"homepage\": \"https://pixelandtonic.com/\"\n    }\n  ],\n  \"support\": {\n    \"email\": \"support@craftcms.com\",\n    \"issues\": \"https://github.com/craftcms/contact-form/issues?state=open\",\n    \"source\": \"https://github.com/craftcms/contact-form\",\n    \"docs\": \"https://github.com/craftcms/contact-form\",\n    \"rss\": \"https://github.com/craftcms/contact-form/commits/3.x.atom\"\n  },\n  \"minimum-stability\": \"dev\",\n  \"prefer-stable\": true,\n  \"require\": {\n    \"php\": \"^8.0.2\",\n    \"craftcms/cms\": \"^4.0.0-beta.1|^5.0.0-beta.1\"\n  },\n  \"require-dev\": {\n    \"craftcms/ecs\": \"dev-main\",\n    \"craftcms/phpstan\": \"dev-main\",\n    \"craftcms/rector\": \"dev-main\"\n  },\n  \"autoload\": {\n    \"psr-4\": {\n      \"craft\\\\contactform\\\\\": \"src/\"\n    }\n  },\n  \"extra\": {\n    \"name\": \"Contact Form\",\n    \"handle\": \"contact-form\",\n    \"documentationUrl\": \"https://github.com/craftcms/contact-form/blob/3.x/README.md\",\n    \"components\": {\n      \"mailer\": \"craft\\\\contactform\\\\Mailer\"\n    }\n  },\n  \"scripts\": {\n    \"check-cs\": \"ecs check --ansi\",\n    \"fix-cs\": \"ecs check --ansi --fix\",\n    \"phpstan\": \"phpstan --memory-limit=1G\"\n  },\n  \"config\": {\n    \"platform\": {\n      \"php\": \"8.0.2\"\n    },\n    \"allow-plugins\": {\n      \"yiisoft/yii2-composer\": true,\n      \"craftcms/plugin-installer\": true\n    }\n  }\n}\n"
  },
  {
    "path": "ecs.php",
    "content": "<?php\n\nuse craft\\ecs\\SetList;\nuse Symplify\\EasyCodingStandard\\Config\\ECSConfig;\n\nreturn static function(ECSConfig $ecsConfig): void {\n    $ecsConfig->paths([\n        __DIR__ . '/src',\n        __FILE__,\n    ]);\n\n    $ecsConfig->parallel();\n    $ecsConfig->sets([SetList::CRAFT_CMS_4]);\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@craftcms/contact-form\",\n  \"private\": true,\n  \"devDependencies\": {\n    \"husky\": \"^7.0.4\",\n    \"lint-staged\": \"^12.4.0\",\n    \"prettier\": \"^2.7.1\"\n  },\n  \"scripts\": {\n    \"prepare\": \"husky install\"\n  }\n}\n"
  },
  {
    "path": "phpstan.neon",
    "content": "includes:\n    - vendor/craftcms/phpstan/phpstan.neon\n\nparameters:\n    level: 4\n    paths:\n        - src\n"
  },
  {
    "path": "src/Mailer.php",
    "content": "<?php\n\nnamespace craft\\contactform;\n\nuse Craft;\nuse craft\\contactform\\events\\SendEvent;\nuse craft\\contactform\\models\\Submission;\nuse craft\\elements\\User;\nuse craft\\helpers\\App;\nuse craft\\helpers\\ArrayHelper;\nuse craft\\helpers\\FileHelper;\nuse craft\\helpers\\StringHelper;\nuse craft\\mail\\Message;\nuse yii\\base\\Component;\nuse yii\\base\\InvalidConfigException;\nuse yii\\helpers\\Html;\nuse yii\\helpers\\Markdown;\n\nclass Mailer extends Component\n{\n    /**\n     * @event SubmissionEvent The event that is triggered before a message is sent\n     */\n    public const EVENT_BEFORE_SEND = 'beforeSend';\n\n    /**\n     * @event SubmissionEvent The event that is triggered after a message is sent\n     */\n    public const EVENT_AFTER_SEND = 'afterSend';\n\n    /**\n     * Sends an email submitted through a contact form.\n     *\n     * @param Submission $submission\n     * @param bool $runValidation Whether the section should be validated\n     * @throws InvalidConfigException if the plugin settings don't validate\n     * @return bool\n     */\n    public function send(Submission $submission, bool $runValidation = true): bool\n    {\n        // Get the plugin settings and make sure they validate before doing anything\n        $settings = Plugin::getInstance()->getSettings();\n        if (!$settings->validate()) {\n            throw new InvalidConfigException('The Contact Form settings don’t validate.');\n        }\n\n        if ($runValidation && !$submission->validate()) {\n            Craft::info('Contact form submission not saved due to validation error.', __METHOD__);\n            return false;\n        }\n\n        $mailer = Craft::$app->getMailer();\n\n        // Prep the message\n        $fromEmail = $this->getFromEmail($mailer->from);\n        $fromName = $this->compileFromName($submission->fromName);\n        $subject = $this->compileSubject($submission->subject);\n        $textBody = $this->compileTextBody($submission);\n        $htmlBody = $this->compileHtmlBody($textBody);\n\n        // Flag for file attachment validation.\n        $validAttachments = true;\n\n        $message = (new Message())\n            ->setFrom([$fromEmail => $fromName])\n            ->setReplyTo([$submission->fromEmail => (string)$submission->fromName])\n            ->setSubject($subject)\n            ->setTextBody($textBody)\n            ->setHtmlBody($htmlBody);\n\n        if ($submission->attachment !== null) {\n            $allowedFileTypes = Craft::$app->getConfig()->getGeneral()->allowedFileExtensions;\n\n            if (!is_array($submission->attachment)) {\n                $submission->attachment = [$submission->attachment];\n            }\n\n            foreach ($submission->attachment as $attachment) {\n                if (!$attachment) {\n                    continue;\n                }\n\n                // Validate that the file is safe to send by e-mail\n                $extension = pathinfo($attachment->name, PATHINFO_EXTENSION);\n\n                if (!in_array(strtolower($extension), $allowedFileTypes)) {\n                    $validAttachments = false;\n                }\n\n                $message->attach($attachment->tempName, [\n                    'fileName' => $attachment->name,\n                    'contentType' => FileHelper::getMimeType($attachment->tempName),\n                ]);\n            }\n        }\n\n        // Grab any \"to\" emails set in the plugin settings.\n        $toEmails = App::parseEnv($settings->toEmail);\n        $toEmails = is_string($toEmails) ? StringHelper::split($toEmails) : $toEmails;\n\n        // Fire a 'beforeSend' event\n        $event = new SendEvent([\n            'submission' => $submission,\n            'message' => $message,\n            'toEmails' => $toEmails,\n        ]);\n        $this->trigger(self::EVENT_BEFORE_SEND, $event);\n\n        if ($event->isSpam) {\n            Craft::warning('Contact form submission suspected to be spam.', __METHOD__);\n            return true;\n        }\n\n        if ($validAttachments === false) {\n            Craft::error('Contact form submission contains a disallowed filetype.', __METHOD__);\n            return false;\n        }\n\n        foreach ($event->toEmails as $toEmail) {\n            $message->setTo($toEmail);\n            $mailer->send($message);\n        }\n\n        // Fire an 'afterSend' event\n        if ($this->hasEventHandlers(self::EVENT_AFTER_SEND)) {\n            $this->trigger(self::EVENT_AFTER_SEND, new SendEvent([\n                'submission' => $submission,\n                'message' => $message,\n                'toEmails' => $event->toEmails,\n            ]));\n        }\n\n        return true;\n    }\n\n    /**\n     * Returns the \"From\" email value on the given mailer $from property object.\n     *\n     * @param string|array|User|User[]|null $from\n     * @return string\n     * @throws InvalidConfigException if it can’t be determined\n     */\n    public function getFromEmail($from): string\n    {\n        if (is_string($from)) {\n            return $from;\n        }\n        if ($from instanceof User) {\n            return $from->email;\n        }\n        if (is_array($from)) {\n            $first = reset($from);\n            $key = key($from);\n            if (is_numeric($key)) {\n                return $this->getFromEmail($first);\n            }\n            return $key;\n        }\n        throw new InvalidConfigException('Can\\'t determine \"From\" email from email config settings.');\n    }\n\n    /**\n     * Compiles the \"From\" name value from the submitted name.\n     *\n     * @param string|null $fromName\n     * @return string\n     */\n    public function compileFromName(string $fromName = null): string\n    {\n        $settings = Plugin::getInstance()->getSettings();\n        return $settings->prependSender . ($settings->prependSender && $fromName ? ' ' : '') . $fromName;\n    }\n\n    /**\n     * Compiles the real email subject from the submitted subject.\n     *\n     * @param string|null $subject\n     * @return string\n     */\n    public function compileSubject(string $subject = null): string\n    {\n        $settings = Plugin::getInstance()->getSettings();\n        return $settings->prependSubject . ($settings->prependSubject && $subject ? ' - ' : '') . $subject;\n    }\n\n    /**\n     * Compiles the real email textual body from the submitted message.\n     *\n     * @param Submission $submission\n     * @return string\n     */\n    public function compileTextBody(Submission $submission): string\n    {\n        $fields = [];\n\n        if ($submission->fromName) {\n            $fields[Craft::t('contact-form', 'Name')] = $submission->fromName;\n        }\n\n        $fields[Craft::t('contact-form', 'Email')] = $submission->fromEmail;\n\n        if (is_array($submission->message)) {\n            $settings = Plugin::getInstance()->getSettings();\n            $messageFields = array_merge($submission->message);\n            $body = ArrayHelper::remove($messageFields, 'body', '');\n            foreach ($messageFields as $key => $value) {\n                if ($settings->allowedMessageFields === null || in_array($key, $settings->allowedMessageFields)) {\n                    $label = Craft::t('site', $key);\n                    $fields[$label] = $value;\n                }\n            }\n        } else {\n            $body = (string)$submission->message;\n        }\n\n        $text = '';\n\n        foreach ($fields as $key => $value) {\n            $text .= ($text ? \"\\n\" : '') . \"- **{$key}:** \";\n            if (is_array($value)) {\n                $text .= implode(', ', $value);\n            } else {\n                $text .= $value;\n            }\n        }\n\n        if ($body !== '') {\n            $body = preg_replace('/\\R/u', \"\\n\", $body);\n            $text .= \"\\n\\n\" . $body;\n        }\n\n        return $text;\n    }\n\n    /**\n     * Compiles the real email HTML body from the compiled textual body.\n     *\n     * @param string $textBody\n     * @return string\n     */\n    public function compileHtmlBody(string $textBody): string\n    {\n        $html = Html::encode($textBody);\n        $html = Markdown::process($html);\n\n        return $html;\n    }\n}\n"
  },
  {
    "path": "src/Plugin.php",
    "content": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace craft\\contactform;\n\nuse Craft;\nuse craft\\contactform\\models\\Settings;\n\n/**\n * Class Plugin\n *\n * @property Settings $settings\n * @property Mailer $mailer\n * @method Settings getSettings()\n */\nclass Plugin extends \\craft\\base\\Plugin\n{\n    /**\n     * @inheritdoc\n     */\n    public bool $hasCpSettings = true;\n\n    /**\n     * @return Mailer\n     */\n    public function getMailer(): Mailer\n    {\n        return $this->get('mailer');\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function createSettingsModel(): ?Settings\n    {\n        return new Settings();\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function settingsHtml(): ?string\n    {\n        // Get and pre-validate the settings\n        $settings = $this->getSettings();\n        $settings->validate();\n\n        // Get the settings that are being defined by the config file\n        $overrides = Craft::$app->getConfig()->getConfigFromFile(strtolower($this->handle));\n\n        return Craft::$app->view->renderTemplate('contact-form/_settings', [\n            'settings' => $settings,\n            'overrides' => array_keys($overrides),\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/controllers/SendController.php",
    "content": "<?php\n\nnamespace craft\\contactform\\controllers;\n\nuse Craft;\nuse craft\\contactform\\models\\Submission;\nuse craft\\contactform\\Plugin;\nuse craft\\web\\Controller;\nuse craft\\web\\UploadedFile;\nuse yii\\web\\Response;\n\nclass SendController extends Controller\n{\n    /**\n     * @inheritdoc\n     */\n    public array|bool|int $allowAnonymous = true;\n\n    /**\n     * Sends a contact form submission.\n     *\n     * @return Response|null\n     */\n    public function actionIndex()\n    {\n        $this->requirePostRequest();\n        $request = Craft::$app->getRequest();\n        $plugin = Plugin::getInstance();\n        $settings = $plugin->getSettings();\n\n        $submission = new Submission();\n        $submission->fromEmail = $request->getBodyParam('fromEmail');\n        $submission->fromName = $request->getBodyParam('fromName');\n        $submission->subject = $request->getBodyParam('subject');\n\n        $message = $request->getBodyParam('message');\n        if (is_array($message)) {\n            $submission->message = array_filter($message, function($value) {\n                return $value !== '';\n            });\n        } else {\n            $submission->message = $message;\n        }\n\n        if ($settings->allowAttachments && isset($_FILES['attachment']) && isset($_FILES['attachment']['name'])) {\n            if (is_array($_FILES['attachment']['name'])) {\n                $submission->attachment = UploadedFile::getInstancesByName('attachment');\n            } else {\n                $submission->attachment = UploadedFile::getInstanceByName('attachment');\n            }\n        }\n\n        if (!$plugin->getMailer()->send($submission)) {\n            return $this->asModelFailure(\n                $submission,\n                Craft::t('contact-form', 'There was a problem with your submission, please check the form and try again!'),\n                'submission',\n                [\n                    'errors' => $submission->getErrors(),\n                ],\n            );\n        }\n\n        return $this->asModelSuccess(\n            $submission,\n            $settings->successFlashMessage,\n            'submission',\n        );\n    }\n}\n"
  },
  {
    "path": "src/events/SendEvent.php",
    "content": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace craft\\contactform\\events;\n\nuse craft\\contactform\\models\\Submission;\nuse craft\\mail\\Message;\nuse yii\\base\\Event;\n\nclass SendEvent extends Event\n{\n    /**\n     * @var Submission The user submission.\n     */\n    public $submission;\n\n    /**\n     * @var Message The message about to be sent.\n     */\n    public $message;\n\n    /**\n     * @var string[] The email address(es) the submission will get sent to.\n     */\n    public $toEmails;\n\n    /**\n     * @var bool Whether the message appears to be spam, and should not really be sent.\n     */\n    public $isSpam = false;\n}\n"
  },
  {
    "path": "src/models/Settings.php",
    "content": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace craft\\contactform\\models;\n\nuse craft\\base\\Model;\n\nclass Settings extends Model\n{\n    /**\n     * @var string|string[]|null\n     */\n    public $toEmail;\n\n    /**\n     * @var string|null\n     */\n    public $prependSender;\n\n    /**\n     * @var string|null\n     */\n    public $prependSubject;\n\n    /**\n     * @var bool\n     */\n    public $allowAttachments = false;\n\n    /**\n     * @var string|null\n     */\n    public $successFlashMessage;\n\n    /**\n     * @var string[]|null List of allowed `message` sub-keys that can be posted to `contact-form/send` (besides `body`).\n     * @since 2.5.0\n     */\n    public $allowedMessageFields;\n\n    /**\n     * @inheritdoc\n     */\n    public function init(): void\n    {\n        parent::init();\n\n        if ($this->prependSender === null) {\n            $this->prependSender = \\Craft::t('contact-form', 'On behalf of');\n        }\n\n        if ($this->prependSubject === null) {\n            $this->prependSubject = \\Craft::t('contact-form', 'New message from {siteName}', [\n                'siteName' => \\Craft::$app->getSites()->getCurrentSite()->name,\n            ]);\n        }\n\n        if ($this->successFlashMessage === null) {\n            $this->successFlashMessage = \\Craft::t('contact-form', 'Your message has been sent.');\n        }\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function defineRules(): array\n    {\n        return [\n            [['toEmail', 'successFlashMessage'], 'required'],\n            [['toEmail', 'prependSender', 'prependSubject', 'successFlashMessage'], 'string'],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/models/Submission.php",
    "content": "<?php\n/**\n * @link https://craftcms.com/\n * @copyright Copyright (c) Pixel & Tonic, Inc.\n * @license MIT\n */\n\nnamespace craft\\contactform\\models;\n\nuse craft\\base\\Model;\nuse craft\\web\\UploadedFile;\n\n/**\n * Class Submission\n *\n * @package craft\\contactform\n */\nclass Submission extends Model\n{\n    /**\n     * @var string|null\n     */\n    public $fromName;\n\n    /**\n     * @var string|null\n     */\n    public $fromEmail;\n\n    /**\n     * @var string|null\n     */\n    public $subject;\n\n    /**\n     * @var string|string[]|string[][]|null\n     * @phpstan-var string|array<string|string[]>|null\n     */\n    public $message;\n\n    /**\n     * @var UploadedFile|UploadedFile[]|null[]|null\n     * @phpstan-var UploadedFile|array<UploadedFile|null>|null\n     */\n    public $attachment;\n\n    /**\n     * @inheritdoc\n     */\n    public function attributeLabels()\n    {\n        return [\n            'fromName' => \\Craft::t('contact-form', 'Your Name'),\n            'fromEmail' => \\Craft::t('contact-form', 'Your Email'),\n            'message' => \\Craft::t('contact-form', 'Message'),\n            'subject' => \\Craft::t('contact-form', 'Subject'),\n        ];\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function defineRules(): array\n    {\n        return [\n            [['fromEmail', 'message'], 'required'],\n            [['fromEmail'], 'email'],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/templates/_settings.twig",
    "content": "{% import \"_includes/forms\" as forms %}\n\n{% macro configWarning(setting) -%}\n  {% set setting = '<code>'~setting~'</code>' %}\n  {{ \"This is being overridden by the {setting} config setting in your {file} config file.\"|t('contact-form', {\n    setting: setting,\n    file: 'contact-form.php'\n  })|raw }}\n{%- endmacro %}\n\n{% from _self import configWarning %}\n\n{{ forms.autosuggestField({\n  first:        true,\n  label:        \"To Email\"|t('contact-form'),\n  required:     true,\n  id:           'toEmail',\n  name:         'toEmail',\n  instructions: \"The email address(es) that the contact form will send to. Separate multiple email addresses with commas.\"|t('contact-form'),\n  value:        settings.toEmail,\n  suggestEnvVars: true,\n  autofocus:    true,\n  disabled:     'toEmail' in overrides,\n  warning:      'toEmail' in overrides ? configWarning('toEmail'),\n  errors:       settings.getErrors('toEmail')\n}) }}\n\n{{ forms.textField({\n  label:        \"Sender Text\"|t('contact-form'),\n  id:           'prependSender',\n  name:         'prependSender',\n  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'),\n  value:        (settings.prependSender ? settings.prependSender : \"\"),\n  disabled:     'prependSender' in overrides,\n  warning:      'prependSender' in overrides ? configWarning('prependSender'),\n  errors:       settings.getErrors('prependSender')\n}) }}\n\n{{ forms.textField({\n  label:        \"Subject Text\"|t('contact-form'),\n  id:           'prependSubject',\n  name:         'prependSubject',\n  instructions: \"Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form.\"|t('contact-form'),\n  value:        (settings.prependSubject ? settings.prependSubject : \"\"),\n  disabled:     'prependSubject' in overrides,\n  warning:      'prependSubject' in overrides ? configWarning('prependSubject'),\n  errors:       settings.getErrors('prependSubject')\n}) }}\n\n{{ forms.lightswitchField({\n  label:        \"Allow attachments?\"|t('contact-form'),\n  id:           'allowAttachments',\n  name:         'allowAttachments',\n  on:           settings.allowAttachments,\n  disabled:     'allowAttachments' in overrides,\n  warning:      'allowAttachments' in overrides ? configWarning('allowAttachments'),\n  errors:       settings.getErrors('allowAttachments')\n}) }}\n\n{{ forms.textField({\n  label:        \"Success Flash Message\"|t('contact-form'),\n  id:           \"successFlashMessage\",\n  name:         \"successFlashMessage\",\n  instructions: \"The flash message displayed after successfully sending a message.\"|t('contact-form'),\n  value:        settings.successFlashMessage,\n  disabled:     'successFlashMessage' in overrides,\n  warning:      'successFlashMessage' in overrides ? configWarning('successFlashMessage'),\n  errors:       settings.getErrors('successFlashMessage')\n}) }}\n"
  },
  {
    "path": "src/translations/ar/contact-form.php",
    "content": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"البريد الإلكتروني للمستلم\",\n    'Your Name' => \"الاسم\",\n    'Your Email' => \"بريدك الإلكتروني\",\n    'Message' => \"الرسالة\",\n    'Subject' => \"الموضوع\",\n\n    // email body\n    'Email' => 'البريد',\n    'Name' => 'الاسم',\n\n    // error messages\n    //'There was a problem with your submission, please check the form and try again!'\n    //=> '',\n\n    // plugin settings\n    \"The email address(es) that the contact form will send to. Separate multiple email addresses with commas.\"\n    => \"عنوان (عناوين) البريد الإلكتروني التي سيرسل اليها نموذج الاتصال. يجب فصل عناوين البريد الإلكتروني المتعددة باستخدام الفواصل.\",\n    \"Sender Text\"\n    => \"نص المرسل\",\n    \"Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by.\"\n    => \"النص الذي سيتم إلحاقه قبل اسم مرسل رسالة البريد الإلكتروني للإشارة إلى المرسل الفعلي لنموذج الاتصال.\",\n    \"On behalf of\"\n    => \"نيابة عن\",\n    \"Subject Text\"\n    => \"نص الموضوع\",\n    \"Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form.\"\n    => \"النص الذي سيتم إلحاقه بموضوع البريد الإلكتروني للإشارة عن أنه أتي من نموذج الاتصال.\",\n    \"Allow attachments?\"\n    => \"السماح بالمرفقات؟\",\n    \"Success Flash Message\"\n    => \"رسالة النجاح\",\n    \"The flash message displayed after successfully sending a message.\"\n    => \"رسالة الإخطار التي يتم عرضها بعد إرسال رسالة بنجاح.\",\n    \"Your message has been sent.\"\n    => \"تم ارسال رسالتك.\",\n];\n"
  },
  {
    "path": "src/translations/de/contact-form.php",
    "content": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Empfängeradresse\",\n    'Your Name' => \"Ihr Name\",\n    'Your Email' => \"Ihre Email\",\n    'Message' => \"Nachricht\",\n    'Subject' => \"Betreff\",\n\n    // email body\n    'Email' => 'E-Mail',\n    'Name' => 'Name',\n\n    // error messages\n    'There was a problem with your submission, please check the form and try again!'\n    => 'Es gab ein Problem beim Abschicken des Formulars, bitte Eingaben prüfen und erneut versuchen!',\n\n    // plugin settings\n    \"The email address(es) that the contact form will send to. Separate multiple email addresses with commas.\"\n    => \"Die Email-Adresse(n) an welche das Kontaktformular senden wird. Trennen Sie mehrere Email-Adressen mit Kommas.\",\n    \"Sender Text\"\n    => \"Absendertext\",\n    \"Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by.\"\n    => \"Text, welcher dem Absendernamen der Email vorangestellt wird, um aufzuzeigen von wem das Kontaktformular abgesendet wurde.\",\n    \"On behalf of\"\n    => \"Mitteilung von\",\n    \"Subject Text\"\n    => \"Betreffzeile\",\n    \"Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form.\"\n    => \"Text, welcher der Betreffzeile vorangestellt wird, um aufzuzeigen dass die Mail vom Kontaktformular stammt\",\n    \"Allow attachments?\"\n    => \"Anlagen erlauben?\",\n    \"Success Flash Message\"\n    => \"Erfolgsnachricht\",\n    \"The flash message displayed after successfully sending a message.\"\n    => \"Die Statusnachricht welche angezeigt wird, wenn eine Nachricht erfolgreich versendet wurde.\",\n    \"Your message has been sent.\"\n    => \"Ihre Nachricht wurde versendet.\",\n];\n"
  },
  {
    "path": "src/translations/es/contact-form.php",
    "content": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Destinatario\",\n    'Your Name' => \"Su Nombre\",\n    'Your Email' => \"Su email\",\n    'Message' => \"Mensaje\",\n    'Subject' => \"Asunto\",\n\n    // email body\n    'Email' => 'Correo electrónico',\n    'Name' => 'Nombre',\n\n    // error messages\n    'There was a problem with your submission, please check the form and try again!'\n    => 'Hubo un problema con su envío, por favor revise el formulario y vuelva a intentarlo!',\n\n    // plugin settings\n    \"The email address(es) that the contact form will send to. Separate multiple email addresses with commas.\"\n    => \"La(s) direccion(es) de email a las que se enviará el email de contacto. Separa las distintas direcciones de email con comas.\",\n    \"Sender Text\"\n    => \"Texto del remitente\",\n    \"Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by.\"\n    => \"Texto que se antepondrá al Nombre del remitente del email para informar a quién se envió realmente el correo de contacto.\",\n    \"On behalf of\"\n    => \"A nombre de\",\n    \"Subject Text\"\n    => \"Texto del asunto\",\n    \"Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form.\"\n    => \"Texto que se agregará previamente al asunto del correo para informar que proviene del formulario de contacto.\",\n    \"Allow attachments?\"\n    => \"Permitir adjuntos?\",\n    \"Success Flash Message\"\n    => \"Mensaje flash para correos enviados\",\n    \"The flash message displayed after successfully sending a message.\"\n    => \"El mensaje flash que se muestra después de enviar un mensaje correctamente.\",\n    \"Your message has been sent.\"\n    => \"Su mensaje ha sido enviado.\",\n];\n"
  },
  {
    "path": "src/translations/fr/contact-form.php",
    "content": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Destinataire\",\n    'Your Name' => \"Votre nom\",\n    'Your Email' => \"Votre email\",\n    'Message' => \"Message\",\n    'Subject' => \"Sujet\",\n\n    // email body\n    'Email' => 'E-mail',\n    'Name' => 'Nom',\n\n    // error messages\n    'There was a problem with your submission, please check the form and try again!'\n    => 'Une erreur est survenue lors de la soumission, vérifiez le formulaire et essayez à nouveau !',\n\n    // plugin settings\n    \"The email address(es) that the contact form will send to. Separate multiple email addresses with commas.\"\n    => \"La ou les adresses qui recevront les emails. Séparez les adresses multiples par des virgules.\",\n    \"Sender Text\"\n    => \"Texte expéditeur\",\n    \"Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by.\"\n    => \"Texte qui sera ajouté avant le nom de l'expéditeur pour indiquer par qui le formulaire de contact a été envoyé.\",\n    \"On behalf of\"\n    => \"De la part de\",\n    \"Subject Text\"\n    => \"Texte sujet\",\n    \"Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form.\"\n    => \"Texte qui sera ajouté au début du sujet du mail pour indiquer qu'il a été envoyé depuis le formulaire de contact.\",\n    \"Allow attachments?\"\n    => \"Autoriser les pièces jointes ?\",\n    \"Success Flash Message\"\n    => \"Message de succès\",\n    \"The flash message displayed after successfully sending a message.\"\n    => \"Le message affiché après avoir envoyé un message avec succès.\",\n    \"Your message has been sent.\"\n    => \"Votre message a été envoyé.\",\n];\n"
  },
  {
    "path": "src/translations/it/contact-form.php",
    "content": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"Destinatario\",\n    'Your Name' => \"Nominativo\",\n    'Your Email' => \"Email\",\n    'Message' => \"Messaggio\",\n    'Subject' => \"Oggetto\",\n\n    // email body\n    'Email' => 'Email',\n    'Name' => 'Nome',\n\n    // error messages\n    'There was a problem with your submission, please check the form and try again!'\n    => \"Si è verificato un problema con l'invio, controlla il modulo e riprova!\",\n\n    // plugin settings\n    \"The email address(es) that the contact form will send to. Separate multiple email addresses with commas.\"\n    => \"Gli indirizzi che riceveranno le email. Separa gli indirizzi multipli con le virgole.\",\n    \"Sender Text\"\n    => \"Testo del mittente\",\n    \"Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by.\"\n    => \"Testo che verrà anteposto al mittente dell'email per indicare da chi è stato effettivamente inviato il modulo contatti.\",\n    \"On behalf of\"\n    => \"Per conto di\",\n    \"Subject Text\"\n    => \"Oggetto\",\n    \"Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form.\"\n    => \"Testo che verrà anteposto all'oggetto dell'email per segnalare che proviene dal modulo contatti.\",\n    \"Allow attachments?\"\n    => \"Consentire allegati?\",\n    \"Success Flash Message\"\n    => \"Messaggio di successo\",\n    \"The flash message displayed after successfully sending a message.\"\n    => \"Il messaggio flash visualizzato dopo aver inviato con successo un messaggio.\",\n    \"Your message has been sent.\"\n    => \"Il tuo messaggio è stato inviato.\",\n];\n"
  },
  {
    "path": "src/translations/nl/contact-form.php",
    "content": "<?php\n\nreturn [\n    // model\n    \"To Email\" => \"E-mail naar\",\n    'Your Name' => \"Jouw naam\",\n    'Your Email' => \"Jouw e-mailadres\",\n    'Message' => \"Bericht\",\n    'Subject' => \"Onderwerp\",\n\n    // email body\n    'Email' => 'E-mail',\n    'Name' => 'Naam',\n\n    // error messages\n    \"There was a problem with your submission, please check the form and try again!\"\n    => \"Er was een probleem met je inzending, controleer het formulier en probeer het opnieuw!\",\n\n    // plugin settings\n    \"The email address(es) that the contact form will send to. Separate multiple email addresses with commas.\"\n    => \"Het e-mailadres of de e-mailadressen naar waar het contactformulier dient te verzenden. Scheid meerdere e-mailadressen met komma's.\",\n    \"Sender Text\"\n    => \"Afzendersbericht\",\n    \"Text that will be prepended to the email’s From Name to inform who the Contact Form actually was sent by.\"\n    => \"Tekst die wordt toegevoegd aan de naam van de verzender om te informeren door wie het contactformulier is verzonden.\",\n    \"On behalf of\"\n    => \"Namens\",\n    \"Subject Text\"\n    => \"Onderwerp tekst\",\n    \"Text that will be prepended to the email’s Subject to flag that it comes from the Contact Form.\"\n    => \"Tekst die zal worden toegevoegd aan het onderwerp om te tonen dat het afkomstig is van het contactformulier.\",\n    \"Allow attachments?\"\n    => \"Bijlagen toestaan?\",\n    \"Success Flash Message\"\n    => \"Statusbericht geslaagde verzending\",\n    \"The flash message displayed after successfully sending a message.\"\n    => \"Het statusbericht dat wordt getoond na het succesvol versturen van een bericht.\",\n    \"Your message has been sent.\"\n    => \"Jouw bericht is verstuurd.\",\n];\n"
  }
]