├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── LICENSE-lgpl ├── README.md ├── composer.json ├── config └── config.yml.dist ├── couscous.yml ├── docs ├── email.md ├── events.md ├── example │ └── Choice │ │ ├── EventChoice.md │ │ ├── EventChoice.php │ │ ├── StaticChoice.md │ │ ├── StaticChoice.php │ │ ├── TraversableChoice.md │ │ └── TraversableChoice.php ├── fields.md ├── fields │ ├── choice.md │ ├── text.md │ └── upload.md ├── form-options.md ├── getting-started.md ├── index.md ├── meta-data.md ├── recaptcha.md ├── saving-to-contenttype-database.md ├── search.md ├── submission.md ├── templates.md └── upgrading.md ├── icon-boltforms.png ├── phpunit.xml.dist ├── src ├── Asset │ └── ReCaptcha.php ├── BoltForms.php ├── BoltFormsExtension.php ├── Choice │ ├── AbstractChoiceOptionResolver.php │ ├── ChoiceInterface.php │ ├── ChoiceResolver.php │ ├── ContentTypeResolver.php │ └── EventResolver.php ├── Config │ ├── AbstractCascadingBag.php │ ├── Config.php │ ├── EmailConfig.php │ ├── FieldMap │ │ ├── ContentType.php │ │ └── Email.php │ ├── Form │ │ ├── DatabaseOptionsBag.php │ │ ├── FeedbackOptionsBag.php │ │ ├── FieldBag.php │ │ ├── FieldOptionsBag.php │ │ ├── FieldsBag.php │ │ ├── FormOptionsBag.php │ │ ├── MetaDataBag.php │ │ ├── NotificationOptionsBag.php │ │ ├── ReCaptchaOptionsBag.php │ │ ├── SubmissionOptionsBag.php │ │ ├── TemplateOptionsBag.php │ │ ├── UploadsOptionBag.php │ │ └── UploadsOptionsBag.php │ ├── FormConfig.php │ └── MetaData.php ├── Controller │ ├── Async.php │ └── UploadManagement.php ├── Event │ ├── BoltFormsEvent.php │ ├── BoltFormsEvents.php │ ├── ChoiceEvent.php │ ├── CustomDataEvent.php │ ├── EmailEvent.php │ ├── LifecycleEvent.php │ └── ProcessorEvent.php ├── Exception │ ├── BoltFormsException.php │ ├── EmailException.php │ ├── FileUploadException.php │ ├── FormOptionException.php │ ├── FormValidationException.php │ ├── InternalProcessorException.php │ ├── InvalidConstraintException.php │ └── UnknownFormException.php ├── Factory │ ├── FieldConstraint.php │ ├── FieldOptionsResolver.php │ └── FormContext.php ├── Form │ ├── BoltFormsExtension.php │ ├── DataTransformer │ │ └── EntityTransformer.php │ ├── Entity │ │ └── Content.php │ ├── ResolvedBoltForm.php │ └── Type │ │ └── BoltFormType.php ├── Provider │ ├── BoltFormsServiceProvider.php │ └── RecaptchaServiceProvider.php ├── Submission │ ├── FeedbackTrait.php │ ├── File.php │ ├── Handler │ │ ├── AbstractHandler.php │ │ ├── ContentType.php │ │ ├── DatabaseTable.php │ │ ├── Email.php │ │ ├── PostRequest.php │ │ ├── Redirect.php │ │ └── Upload.php │ ├── Processor.php │ ├── Processor │ │ ├── AbstractProcessor.php │ │ ├── ContentType.php │ │ ├── DatabaseTable.php │ │ ├── Email.php │ │ ├── Feedback.php │ │ ├── Fields.php │ │ ├── ProcessorInterface.php │ │ ├── Redirect.php │ │ └── Uploads.php │ └── Result.php ├── Subscriber │ ├── DynamicDataSubscriber.php │ ├── ProcessLifecycleSubscriber.php │ └── SymfonyFormProxySubscriber.php └── Twig │ ├── Extension │ ├── BoltFormsExtension.php │ └── BoltFormsRuntime.php │ └── RuntimeLoader.php ├── templates ├── README.md ├── _macros.twig ├── asset │ ├── _ajax.twig │ ├── _css.twig │ └── _js.twig ├── email │ ├── _blocks.twig │ ├── email.twig │ └── subject.twig ├── feedback │ ├── _exception.twig │ └── _messages.twig ├── file │ └── browser.twig └── form │ ├── _form_theme.twig │ ├── _recaptcha.twig │ └── form.twig ├── tests └── data │ ├── bolt-logo │ └── bolt-logo.png └── web ├── boltforms.css └── boltforms.js /.gitignore: -------------------------------------------------------------------------------- 1 | config.yml 2 | composer.lock 3 | vendor/ 4 | tests/tmp/ 5 | app/ 6 | extensions/ 7 | .couscous 8 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | before_commands: 2 | - composer install 3 | tools: 4 | php_cs_fixer: 5 | enabled: true 6 | extensions: 7 | - php 8 | filter: 9 | paths: 10 | - src/ 11 | - Extension.php 12 | config: 13 | level: all 14 | php_sim: 15 | enabled: true 16 | min_mass: 30 # Defaults to 16 17 | php_code_sniffer: 18 | enabled: true 19 | config: 20 | standard: PSR2 21 | filter: 22 | paths: 23 | - src/ 24 | build_failure_conditions: 25 | # - 'elements.rating(<= D).exists' # No classes/methods with a rating of D or worse 26 | - 'elements.rating(<= D).new.exists' # No new classes/methods with a rating of D or worse 27 | 28 | # - 'issues.label("coding-style").exists' # No coding style issues allowed 29 | - 'issues.label("coding-style").new.exists' # No new coding style issues allowed 30 | 31 | - 'issues.label("coding-style").new.count > 5' # More than 5 new coding style issues. 32 | - 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity 33 | 34 | # Note that this should be increased when we get our quality socre up 35 | - 'project.metric("scrutinizer.quality", < 5.0)' # Code Quality Rating drops below 6 36 | - 'project.metric("scrutinizer.test_coverage", < 0.60)' # Code Coverage drops below 60% 37 | 38 | # Code Coverage decreased from previous inspection 39 | - 'project.metric_change("scrutinizer.test_coverage", < 0)' 40 | 41 | # Code Coverage decreased from previous inspection by more than 10% 42 | - 'project.metric_change("scrutinizer.test_coverage", < -0.10)' 43 | 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | # Prevent cutover to GCE. Can be removed once full Travis legacy environment is upgraded 4 | sudo: required 5 | dist: precise 6 | group: legacy 7 | 8 | php: 9 | - 5.3 10 | - 5.4 11 | - 5.5 12 | - 5.6 13 | - 7.0 14 | - hhvm 15 | 16 | matrix: 17 | fast_finish: true 18 | allow_failures: 19 | - php: hhvm 20 | 21 | before_install: 22 | 23 | before_script: 24 | # Set up Composer 25 | - composer self-update || true 26 | - composer install --prefer-dist 27 | 28 | script: 29 | # PHPUnit 30 | - phpunit 31 | 32 | after_script: 33 | 34 | # Cache vendor dirs 35 | cache: 36 | directories: 37 | - vendor 38 | - $COMPOSER_CACHE_DIR 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | | :warning: Note - Not the latest version | 2 | |:----------------------------------------| 3 | | This is the repository for Boltforms for Bolt 3. Please know
that Bolt 5 has been released. If you are starting a new
project, please use the following: 4 | | - [Bolt 5 Forms repository](https://github.com/bolt/forms) | 5 | 6 | 7 | 8 | Bolt Forms 9 | ========== 10 | 11 | Bolt Forms is an interface to Symfony Forms for Bolt 3. It provides a Twig 12 | template function and exposes a simplified API for extending as you need. 13 | 14 | Documentation 15 | -------------- 16 | 17 | For full documentation see the [Online docs][docs]. 18 | 19 | [docs]: https://bolt.github.io/boltforms/ 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bolt/boltforms", 3 | "description": "Powerful and flexible HTML form generator for Bolt based on Symfony Forms", 4 | "type": "bolt-extension", 5 | "keywords": [ 6 | "forms", 7 | "contact", 8 | "comments", 9 | "reCaptcha", 10 | "uploads" 11 | ], 12 | "require": { 13 | "bolt/bolt": "^3.0", 14 | "bolt/email-spooler": "^3.0", 15 | "google/recaptcha": "^1.1.2" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^4.7" 19 | }, 20 | "suggest": { 21 | "rossriley/formeditor": "A UI editor for form configuration." 22 | }, 23 | "license": [ 24 | "GPL-3.0+", 25 | "LGPL-3.0+" 26 | ], 27 | "authors": [ 28 | { 29 | "name": "Gawain Lynch", 30 | "email": "gawain.lynch@gmail.com" 31 | } 32 | ], 33 | "minimum-stability": "dev", 34 | "prefer-stable": true, 35 | "autoload": { 36 | "psr-4": { 37 | "Bolt\\Extension\\Bolt\\BoltForms\\": [ 38 | "src/" 39 | ] 40 | }, 41 | "classmap": [ 42 | "docs/example/" 43 | ] 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Bolt\\Extension\\Bolt\\BoltForms\\Tests\\": "tests/", 48 | "Bolt\\Tests\\": "vendor/bolt/bolt/tests/phpunit/unit/" 49 | } 50 | }, 51 | "extra": { 52 | "bolt-assets": "web", 53 | "bolt-class": "Bolt\\Extension\\Bolt\\BoltForms\\BoltFormsExtension", 54 | "bolt-icon": "icon-boltforms.png", 55 | "branch-alias": { 56 | "dev-4.0": "4.0.x-dev" 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /couscous.yml: -------------------------------------------------------------------------------- 1 | # This is the Couscous configuration file, used to generate the documentation 2 | # See couscous.io for details. 3 | 4 | template: 5 | # The following assumes you have a checkout of the template folder in ../couscous-template 6 | directory: ../couscous-template/public 7 | # url: https://github.com/bolt/Extension-docs-template 8 | index: index.md 9 | 10 | include: 11 | - docs 12 | 13 | branch: gh-pages 14 | baseUrl: https://bolt.github.io/boltforms 15 | 16 | title: BoltForms 17 | subTitle: A Bolt extension to make forms. 18 | github: 19 | user: bolt 20 | repo: boltforms 21 | editlink: https://github.com/bolt/boltforms/edit/4.2/docs/ 22 | 23 | menu: 24 | getting-started.md: 25 | label: Getting Started 26 | fields.md: 27 | label: Fields 28 | fields/text.md: 29 | label: Basic Fields 30 | class: sub 31 | fields/choice.md: 32 | label: Choice 33 | class: sub 34 | fields/upload.md: 35 | label: Upload 36 | class: sub 37 | meta-data.md: 38 | label: Meta data 39 | templates.md: 40 | label: Templates 41 | email.md: 42 | label: Email Notifications 43 | submission.md: 44 | label: Submission & Redirection 45 | saving-to-contenttype-database.md: 46 | label: Saving to ContentType or Database 47 | events.md: 48 | label: Events 49 | recaptcha.md: 50 | label: Recaptcha 51 | upgrading.md: 52 | label: Upgrading 53 | -------------------------------------------------------------------------------- /docs/email.md: -------------------------------------------------------------------------------- 1 | Email Notifications 2 | =================== 3 | 4 | You use email notifications to do something after the visitor submits the form. 5 | Most probable, you want it to send to an email address. Another option is, for example, [Saving it to a database](saving-to-contenttype-database.md). 6 | 7 | From, To, CC, bCC & ReplyTo Values 8 | ---------------------------------- 9 | 10 | You can use the following values to send email: 11 | 12 | ```yaml 13 | my_form: 14 | notification: 15 | from_name: full_name 16 | from_email: email_address 17 | to_name: Kenny Koala 18 | to_email: kenny@koala.com 19 | cc_name: Fanny Koala 20 | cc_email: fanny@koala.com 21 | bcc_name: Bob the Builder 22 | bcc_email: bob@example.com 23 | replyto_name: full_name 24 | replyto_email: email_address 25 | ``` 26 | 27 | Each of these values can be either a literal string or the name of a field that you defined in your form. 28 | For instance, by using your `full_name` field as a value for `from_name`, the email is sent on behalf of 29 | the name that your visitor submitted. 30 | 31 | In the case of the `*_name` values, an array of field names that will be 32 | concatenated (space delimited) can also be specified. 33 | 34 | ### String Literal 35 | 36 | ```yaml 37 | my_form: 38 | notification: 39 | from_name: Kenny Koala 40 | from_email: kenny@koala.com 41 | ``` 42 | 43 | ### Field Names 44 | 45 | ```yaml 46 | my_form: 47 | notification: 48 | from_name: full_name # using your field 'full_name' 49 | from_email: email_address # using your field 'email_address' 50 | ``` 51 | 52 | ### Array of Field Names 53 | 54 | ```yaml 55 | my_form: 56 | notification: 57 | from_name: [ first_name, last_name ] 58 | from_email: email_address 59 | ``` 60 | 61 | Email Uploaded Files 62 | -------------------- 63 | 64 | If your form uses file uploads, you can attach them to the emails by setting the 65 | `attach_files` parameter to `true`. 66 | 67 | ```yaml 68 | my_form: 69 | notification: 70 | attach_files: true 71 | ``` 72 | 73 | Email Queues 74 | ------------ 75 | 76 | BoltForms spools all emails to a file spool directory, and them dispatches them 77 | after the request has been sent to the client. 78 | 79 | 80 | ### Viewing Queued Messages 81 | 82 | Queued messages can be viewed but running the following `nut` command: 83 | `./app/nut email:spool --show`. 84 | 85 | Which will output a table of queued emails similar to: 86 | 87 | ``` 88 | Currently queued emails: 89 | +---+---------------------------+----------------------------------+-------------------------+ 90 | | | Date | Address | Subject | 91 | +---+---------------------------+----------------------------------+-------------------------+ 92 | | 1 | 2016-07-04T05:00:00+10:00 | Kenny Koala | Stock Order: Gum leaves | 93 | +---+---------------------------+----------------------------------+-------------------------+ 94 | ``` 95 | 96 | ### Recovering Messages 97 | 98 | Occasionally during sending, the Swiftmailer component used by BoltForms will 99 | encounter a severe error when processing and sending emails and the queued 100 | message file will have the `.sending` suffix. 101 | 102 | To re-add them to the queue for processing, you can just run the following 103 | command in your terminal: 104 | `./app/nut email:spool --recover`. 105 | 106 | ### Flushing (sending) Queues 107 | 108 | If you have queued emails due to SMTP server problems, or Bolt / BoltForms 109 | misconfiguration and wish to retry sending them, simply execute the following 110 | command: 111 | 112 | `./app/nut email:spool --flush` 113 | 114 | ### Clearing (deleting) Queued Messages 115 | 116 | If you have stale message objects that you want to flush, e.g. debugging or 117 | testing, you can clear the queued messages with: 118 | 119 | `./app/nut email:spool --clear` 120 | 121 | **NOTE:** This is a destructive action and will delete the messages, which 122 | generally means they have not been sent. 123 | -------------------------------------------------------------------------------- /docs/example/Choice/EventChoice.md: -------------------------------------------------------------------------------- 1 | EventChoice 2 | =========== 3 | 4 | ```php 5 | 18 | */ 19 | class EventChoice extends SimpleExtension 20 | { 21 | protected function subscribe(EventDispatcherInterface $dispatcher) 22 | { 23 | $dispatcher->addListener(BoltFormsEvents::DATA_CHOICE_EVENT, [$this, 'onDataChoiceEvent']); 24 | } 25 | 26 | public function onDataChoiceEvent(ChoiceEvent $event) 27 | { 28 | if ($event->getFormName() === 'contact' && $event->getFieldName() === 'department') { 29 | $choices = [ 30 | '' => null, 31 | 'Sales' => 'dept_sales', 32 | 'Marketing' => 'dept_markt', 33 | 'support' => 'dept_support', 34 | ]; 35 | 36 | $event->setChoices($choices); 37 | } 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/example/Choice/EventChoice.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class EventChoice extends SimpleExtension 16 | { 17 | protected function subscribe(EventDispatcherInterface $dispatcher) 18 | { 19 | $dispatcher->addListener(BoltFormsEvents::DATA_CHOICE_EVENT, [$this, 'onDataChoiceEvent']); 20 | } 21 | 22 | public function onDataChoiceEvent(ChoiceEvent $event) 23 | { 24 | if ($event->getFormName() === 'contact' && $event->getFieldName() === 'department') { 25 | $choices = [ 26 | '' => null, 27 | 'Sales' => 'dept_sales', 28 | 'Marketing' => 'dept_markt', 29 | 'support' => 'dept_support', 30 | ]; 31 | 32 | $event->setChoices($choices); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/example/Choice/StaticChoice.md: -------------------------------------------------------------------------------- 1 | StaticChoice 2 | ============ 3 | 4 | ```php 5 | 13 | */ 14 | class StaticChoice 15 | { 16 | const ITEM_1 = 'item_1'; 17 | const ITEM_2 = 'item_2'; 18 | const ITEM_11 = 'item_11'; 19 | const ITEM_12 = 'item_12'; 20 | 21 | /** 22 | * Singleton constructor. 23 | */ 24 | private function __construct() 25 | { 26 | } 27 | 28 | /** 29 | * Returns the string "value" for each choice. 30 | * 31 | * This is used in the value attribute in HTML and submitted in the POST/PUT 32 | * requests. You don't normally need to worry about this, but it might be 33 | * handy when processing an API request (since you can configure the value 34 | * that will be sent in the API request). 35 | * 36 | * @param string $choicesValue Value in 'choices' array 37 | * @param string|int $key Array key of value in 'choices' array 38 | * @param string $index The result of value() 39 | * 40 | * @return string 41 | */ 42 | public static function choiceValue($choicesValue, $key = null, $index = null) 43 | { 44 | return 'your_prefix_' . $choicesValue; 45 | } 46 | 47 | /** 48 | * Label text that's shown to the user. 49 | * 50 | * @param string $choicesValue Value in 'choices' array 51 | * @param string|int $key Array key of value in 'choices' array 52 | * @param string $index The result of value() 53 | * 54 | * @return string 55 | */ 56 | public static function choiceLabel($choicesValue, $key, $index) 57 | { 58 | $labels = [ 59 | static::ITEM_1 => 'Item One', 60 | static::ITEM_2 => 'Item Two', 61 | // …and others 62 | static::ITEM_11 => 'Item Eleven', 63 | static::ITEM_12 => 'Item Twelve', 64 | ]; 65 | 66 | if (isset($labels[$choicesValue])) { 67 | return $labels[$choicesValue]; 68 | } 69 | 70 | return ucwords($choicesValue); 71 | } 72 | 73 | /** 74 | * Add additional HTML attributes to each choice. 75 | * 76 | * @param string $choicesValue Value in 'choices' array 77 | * @param string|int $key Array key of value in 'choices' array 78 | * @param string $index The result of value() 79 | * 80 | * @return array 81 | */ 82 | public static function choiceAttr($choicesValue, $key, $index) 83 | { 84 | return [ 85 | 'class' => 'doing_' . strtolower($key), 86 | ]; 87 | } 88 | 89 | /** 90 | * Choice values grouping. 91 | * 92 | * @param string $choicesValue Value in 'choices' array 93 | * @param string|int $key Array key of value in 'choices' array 94 | * @param string $index The result of value() 95 | * 96 | * @return string|null 97 | */ 98 | public static function groupBy($choicesValue, $key, $index) 99 | { 100 | // Assign items into groups 101 | $itemGrouping = [ 102 | static::ITEM_1 => 'group_a', 103 | static::ITEM_2 => 'group_a', 104 | // …and others 105 | static::ITEM_11 => 'group_b', 106 | static::ITEM_12 => 'group_b', 107 | // …and others 108 | ]; 109 | // Assign groups labels 110 | $groupLabels = [ 111 | 'group_a' => 'Group Aye', 112 | 'group_b' => 'Group Bee', 113 | // …and others 114 | ]; 115 | 116 | if (isset($itemGrouping[$choicesValue])) { 117 | $groupLabelKey = $itemGrouping[$choicesValue]; 118 | 119 | return $groupLabels[$groupLabelKey]; 120 | } 121 | 122 | return null; 123 | } 124 | 125 | /** 126 | * Allows you to move certain choices to the top of your list with a visual 127 | * separator between them and the rest of the options. 128 | * 129 | * @param string $choicesValue Value in 'choices' array 130 | * @param string|int $key Array key of value in 'choices' array 131 | * 132 | * @return bool 133 | */ 134 | public static function preferredChoices($choicesValue, $key) 135 | { 136 | if ($choicesValue === static::ITEM_12) { 137 | return true; 138 | } 139 | 140 | return false; 141 | } 142 | 143 | /** 144 | * Simple array of choices. 145 | * 146 | * @return array 147 | */ 148 | public static function choices() 149 | { 150 | return [ 'item_1', 'item_2', 'item_11', 'item_12', 'koala_bear' ]; 151 | } 152 | } 153 | ``` 154 | -------------------------------------------------------------------------------- /docs/example/Choice/StaticChoice.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class StaticChoice 11 | { 12 | const ITEM_1 = 'item_1'; 13 | const ITEM_2 = 'item_2'; 14 | const ITEM_11 = 'item_11'; 15 | const ITEM_12 = 'item_12'; 16 | 17 | /** 18 | * Singleton constructor. 19 | */ 20 | private function __construct() 21 | { 22 | } 23 | 24 | /** 25 | * Returns the string "value" for each choice. 26 | * 27 | * This is used in the value attribute in HTML and submitted in the POST/PUT 28 | * requests. You don't normally need to worry about this, but it might be 29 | * handy when processing an API request (since you can configure the value 30 | * that will be sent in the API request). 31 | * 32 | * @param string $choicesValue Value in 'choices' array 33 | * @param string|int $key Array key of value in 'choices' array 34 | * @param string $index The result of value() 35 | * 36 | * @return string 37 | */ 38 | public static function choiceValue($choicesValue, $key = null, $index = null) 39 | { 40 | return 'your_prefix_' . $choicesValue; 41 | } 42 | 43 | /** 44 | * Label text that's shown to the user. 45 | * 46 | * @param string $choicesValue Value in 'choices' array 47 | * @param string|int $key Array key of value in 'choices' array 48 | * @param string $index The result of value() 49 | * 50 | * @return string 51 | */ 52 | public static function choiceLabel($choicesValue, $key, $index) 53 | { 54 | $labels = [ 55 | static::ITEM_1 => 'Item One', 56 | static::ITEM_2 => 'Item Two', 57 | // …and others 58 | static::ITEM_11 => 'Item Eleven', 59 | static::ITEM_12 => 'Item Twelve', 60 | ]; 61 | 62 | if (isset($labels[$choicesValue])) { 63 | return $labels[$choicesValue]; 64 | } 65 | 66 | return ucwords($choicesValue); 67 | } 68 | 69 | /** 70 | * Add additional HTML attributes to each choice. 71 | * 72 | * @param string $choicesValue Value in 'choices' array 73 | * @param string|int $key Array key of value in 'choices' array 74 | * @param string $index The result of value() 75 | * 76 | * @return array 77 | */ 78 | public static function choiceAttr($choicesValue, $key, $index) 79 | { 80 | return [ 81 | 'class' => 'doing_' . strtolower($key), 82 | ]; 83 | } 84 | 85 | /** 86 | * Choice values grouping. 87 | * 88 | * @param string $choicesValue Value in 'choices' array 89 | * @param string|int $key Array key of value in 'choices' array 90 | * @param string $index The result of value() 91 | * 92 | * @return string|null 93 | */ 94 | public static function groupBy($choicesValue, $key, $index) 95 | { 96 | // Assign items into groups 97 | $itemGrouping = [ 98 | static::ITEM_1 => 'group_a', 99 | static::ITEM_2 => 'group_a', 100 | // …and others 101 | static::ITEM_11 => 'group_b', 102 | static::ITEM_12 => 'group_b', 103 | // …and others 104 | ]; 105 | // Assign groups labels 106 | $groupLabels = [ 107 | 'group_a' => 'Group Aye', 108 | 'group_b' => 'Group Bee', 109 | // …and others 110 | ]; 111 | 112 | if (isset($itemGrouping[$choicesValue])) { 113 | $groupLabelKey = $itemGrouping[$choicesValue]; 114 | 115 | return $groupLabels[$groupLabelKey]; 116 | } 117 | 118 | return null; 119 | } 120 | 121 | /** 122 | * Allows you to move certain choices to the top of your list with a visual 123 | * separator between them and the rest of the options. 124 | * 125 | * @param string $choicesValue Value in 'choices' array 126 | * @param string|int $key Array key of value in 'choices' array 127 | * 128 | * @return bool 129 | */ 130 | public static function preferredChoices($choicesValue, $key) 131 | { 132 | if ($choicesValue === static::ITEM_12) { 133 | return true; 134 | } 135 | 136 | return false; 137 | } 138 | 139 | /** 140 | * Simple array of choices. 141 | * 142 | * @return array 143 | */ 144 | public static function choices() 145 | { 146 | return [ 'item_1', 'item_2', 'item_11', 'item_12', 'koala_bear' ]; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /docs/example/Choice/TraversableChoice.md: -------------------------------------------------------------------------------- 1 | TraversableChoice 2 | ================= 3 | 4 | ```php 5 | 13 | */ 14 | class TraversableChoice implements \Iterator 15 | { 16 | /** @var string */ 17 | private $name; 18 | /** @var int */ 19 | private $position = 0; 20 | /** @var array */ 21 | private $lists = [ 22 | 'group_a' => [ 23 | 'item_1', 'item_2', 'item_3', 'item_4', 'item_5' 24 | ], 25 | 'group_b' => [ 26 | 'item_11', 'item_12', 'item_13', 'item_14', 'item_15' 27 | ], 28 | ]; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param string $name The "group" key of the $lists property array 34 | */ 35 | public function __construct($name) 36 | { 37 | $this->name = $name; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getName() 44 | { 45 | return $this->name; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function current() 52 | { 53 | $listName = $this->name; 54 | $position = $this->position; 55 | 56 | return $this->lists[$listName][$position]; 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function next() 63 | { 64 | ++$this->position; 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function key() 71 | { 72 | return $this->position; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function valid() 79 | { 80 | $listName = $this->name; 81 | $position = $this->position; 82 | 83 | return isset($this->lists[$listName][$position]); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function rewind() 90 | { 91 | $this->position = 0; 92 | } 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /docs/example/Choice/TraversableChoice.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class TraversableChoice implements \Iterator 11 | { 12 | /** @var string */ 13 | private $name; 14 | /** @var int */ 15 | private $position = 0; 16 | /** @var array */ 17 | private $lists = [ 18 | 'group_a' => [ 19 | 'item_1', 'item_2', 'item_3', 'item_4', 'item_5' 20 | ], 21 | 'group_b' => [ 22 | 'item_11', 'item_12', 'item_13', 'item_14', 'item_15' 23 | ], 24 | ]; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param string $name The "group" key of the $lists property array 30 | */ 31 | public function __construct($name) 32 | { 33 | $this->name = $name; 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getName() 40 | { 41 | return $this->name; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function current() 48 | { 49 | $listName = $this->name; 50 | $position = $this->position; 51 | 52 | return $this->lists[$listName][$position]; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function next() 59 | { 60 | ++$this->position; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function key() 67 | { 68 | return $this->position; 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function valid() 75 | { 76 | $listName = $this->name; 77 | $position = $this->position; 78 | 79 | return isset($this->lists[$listName][$position]); 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function rewind() 86 | { 87 | $this->position = 0; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /docs/fields/text.md: -------------------------------------------------------------------------------- 1 | Text Based Fields with Examples 2 | =============================== 3 | 4 | Textfields are used to for basic sorts of information. 5 | See below for all possible types of text fields. 6 | 7 | **NOTE:** See [the Symfony Forms documentation][forms] for an always up-to-date 8 | list of field types and their options. 9 | 10 | Standard Text Field 11 | ------------------- 12 | 13 | ```yaml 14 | mytextfield: 15 | type: text 16 | options: 17 | required: true 18 | label: My Text Field 19 | attr: 20 | placeholder: Enter some text… 21 | value: A Default Value 22 | ``` 23 | 24 | Textarea Field 25 | -------------- 26 | 27 | ```yaml 28 | mytextareafield: 29 | type: textarea 30 | options: 31 | label: My Textarea Field 32 | attr: 33 | placeholder: Enter some text… 34 | ``` 35 | 36 | Email Field 37 | ----------- 38 | 39 | This renders an `` form element. 40 | 41 | ```yaml 42 | myemailfield: 43 | type: email 44 | options: 45 | label: My Email Field 46 | attr: 47 | placeholder: you@example.com 48 | ``` 49 | 50 | Integer Field 51 | ------------- 52 | 53 | This renders an `` form element. 54 | 55 | ```yaml 56 | mynumber: 57 | type: integer 58 | options: 59 | label: My Integer Field 60 | attr: 61 | min: 0 62 | max: 1000 63 | ``` 64 | 65 | Money Field 66 | ----------- 67 | 68 | This renders a number form element with a currency symbol before the input. Any 69 | ISO 3-Letter Currency code is supported. 70 | 71 | ```yaml 72 | amount: 73 | type: money 74 | options: 75 | label: How much does it cost? 76 | currency: EUR 77 | ``` 78 | 79 | Password Field 80 | -------------- 81 | 82 | This renders am HTML password input. 83 | 84 | ```yaml 85 | password: 86 | type: password 87 | options: 88 | label: Enter a secret word 89 | ``` 90 | 91 | Percent Field 92 | ------------- 93 | 94 | This renders an HTML number input and converts the inputted percentage to a 95 | decimal value. It also adds a percentage sign after the form input. 96 | 97 | ```yaml 98 | percentage: 99 | type: percent 100 | options: 101 | label: Percentage Increase? 102 | ``` 103 | 104 | Search Field 105 | ------------ 106 | 107 | This renders an HTML search input ``. 108 | 109 | ```yaml 110 | query: 111 | type: search 112 | options: 113 | label: Search Term 114 | attr: 115 | value: Example Query 116 | ``` 117 | 118 | 119 | URL Field 120 | --------- 121 | 122 | This renders an HTML URL input ``. 123 | 124 | ```yaml 125 | website: 126 | type: url 127 | options: 128 | label: Your Website 129 | ``` 130 | 131 | Range Field 132 | ----------- 133 | 134 | This renders an HTML URL range input ``. 135 | 136 | ```yml 137 | amount: 138 | type: range 139 | options: 140 | label: Enter a number 141 | attr: 142 | min: 10 143 | max: 100 144 | step: 5 145 | ``` 146 | 147 | Note that by default the range has no UI to show the actual selected value, 148 | this is normally accomplished by listening to the `input` event on the range 149 | field and using javascript to update another element with the value. 150 | 151 | [forms]: http://symfony.com/doc/current/reference/forms/types/form.html -------------------------------------------------------------------------------- /docs/fields/upload.md: -------------------------------------------------------------------------------- 1 | File Uploads 2 | ============ 3 | 4 | You can make the visitor upload a file through the form, using the `type: file` fieldtype. 5 | 6 | First, a warning about security: 7 | 8 | Security 9 | -------- 10 | 11 | Handling file uploads is a very common way used to compromise (hack) 12 | a server. BoltForms does a few things to help increase slightly the security of handling 13 | file uploads. 14 | 15 | ### Store the files outside of the webroot 16 | 17 | The following are the "global" options that apply to all form uploads: 18 | 19 | ```yaml 20 | uploads: 21 | enabled: true # The global on/off switch for upload handling 22 | base_directory: /data/customer-uploads/ # Outside web root, absolute path and writable by the web server's user 23 | filename_handling: prefix # Can be either "prefix", "suffix", or "keep" 24 | management_controller: true # Enable a controller to handle browsing and downloading of uploaded files 25 | ``` 26 | 27 | The directory that you specify for `base_directory` should **NOT** be a route 28 | accessible to the outside world and have to be an **absolute path**. BoltForms provides a special route should you 29 | wish to make the files browsable after upload. This route can be enabled as a 30 | global setting via the `management_controller` option. 31 | 32 | ### Renaming the uploaded files 33 | 34 | Secondly, is the `filename_handling` parameter is an important consideration 35 | for your level of required site security. The reason this setting is important 36 | is, if an attacker knows the uploaded file name then this can make their job a 37 | lot easier. BoltForms provides three uploaded file naming options, `prefix`, 38 | `suffix` and `keep`. 39 | 40 | For example, when uploading the file `kitten.jpg` the settings would provide 41 | something similar to the following table: 42 | 43 | | Setting | Resulting file name | 44 | |-----------|-------------------------| 45 | | `prefix` | kitten.Ze1d352rrI3p.jpg | 46 | | `suffix` | kitten.jpg.Ze1d352rrI3p | 47 | | `keep` | kitten.jpg | 48 | 49 | We recommend `suffix`, as this is the most secure. Alternatively `prefix` will 50 | aid in file browsing. However, `keep` should always be used with caution! 51 | 52 | How to use upload 53 | ----------------- 54 | 55 | First, set the global switch for uploads to true in the Boltforms.bolt.yml: 56 | 57 | ```yaml 58 | uploads: 59 | enabled: true 60 | ``` 61 | 62 | Each form has individual options for uploads, such as whether to attach the 63 | uploaded file in the notification message, or whether to place the uploaded file 64 | in a separate subdirectory or the given global upload target. 65 | 66 | A very basic, and cut-down, example of a form with an upload field type is given 67 | here: 68 | 69 | ```yaml 70 | file_upload_form: 71 | notification: 72 | enabled: true 73 | attach_files: true # Optionally send the file as an email attachment 74 | uploads: 75 | subdirectory: file_upload_dir # Optional subdirectory 76 | fields: 77 | upload: 78 | type: file 79 | options: 80 | required: false 81 | label: Picture of your pet that you want us to add to our site 82 | 83 | ``` 84 | 85 | Post-Upload Browsing 86 | -------------------- 87 | 88 | When `management_controller` is enabled in the config, a file in the `base_directory` 89 | location is accessible via `http://your-site.com/boltforms/download?file=filename.ext`. 90 | 91 | These files can be listed via the Twig function `boltforms_uploads()`, e.g. 92 | 93 | ```twig 94 | {{ boltforms_uploads() }} 95 | ``` 96 | 97 | This can be limited to a form's (optionally defined) subdirectory by passing the 98 | form name into `boltforms_uploads()`, e.g. 99 | 100 | ```twig 101 | {{ boltforms_uploads('file_upload_form') }} 102 | ``` 103 | -------------------------------------------------------------------------------- /docs/form-options.md: -------------------------------------------------------------------------------- 1 | Setting-up Form options 2 | ======================= 3 | 4 | Should you want to set up your Form's global options, you can specify 5 | them by setting the `options` key to it. 6 | 7 | This is useful for custom validation on the whole form, e.g: say you want to 8 | make sure your end-user only fills-in **one** out of two fields, you can define a 9 | Callback validator like so: 10 | 11 | ```yaml 12 | # app/config/extensions/boltforms.bolt.yml 13 | options: 14 | constraints: 15 | - { Callback: { callback: [ 'Bundle\App\Form\Validator\FormValidator', 'validate'] } } 16 | ``` 17 | 18 | Then create the validator class. 19 | 20 | ```php 21 | get('adherent_structure') xor null === $object->get('other_structure'))) { 39 | $context 40 | ->buildViolation('You must fill-in one structure only.') 41 | ->addViolation() 42 | ; 43 | } 44 | } 45 | 46 | } 47 | ``` 48 | 49 | Any option available from `FormType field can be defined. 50 | See [Symfony's doc](https://symfony.com/doc/current/reference/forms/types/form.html) 51 | for more information. -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | When you install BoltForms, the default configuration will be installed. 5 | this config file will be located at `app/config/extensions/boltforms.bolt.yml`. 6 | 7 | The config comes with a form called `contact`. This is a simple 8 | contactform. It asks for a name, email and message of the visitor. 9 | After submission it is send to the specified e-mail address. 10 | 11 | You can safely remove (or comment out) this form if you don't need it. But it 12 | is a handy first place to start. 13 | 14 | 15 | Debugging 16 | --------- 17 | **NOTE:** When first installed, BoltForms defaults to `debug : true` in 18 | the configuration. This should be set to `false` when deployed in production. 19 | 20 | You can set debug on two levels: 21 | - for all forms (top of the config) 22 | - for one separate form (in the config of that form) 23 | 24 | The debug of a separate form overrides the global debug setting. 25 | When debugging is on, all outbound emails are sent to the configured debug 26 | email address. 27 | 28 | **NOTE:** When the debug of _BOLT_ in the bolt config is set to `false`, debug will 29 | function the same, but will give less information on screen after sending. 30 | 31 | Your First Form 32 | --------------- 33 | 34 | For a first form, let us use a simplified version of the "contact" form as an example. 35 | 36 | ### Configuration 37 | To make the form actually _do_ stuff, we will edit the configuration of the form in the config. 38 | 39 | - **Define fields for the visitor to fill out.** We define two fields; a `comment` field that allows text entry, and the `submit` button. 40 | 41 | - **Send the form somewhere on submission** . Under the `notification:` key we need to set `enabled: true` and then a set of email addresses, the minimum list shown below. [More on email notifications](./email.md) 42 | 43 | 44 | ```yaml 45 | contact: 46 | notification: 47 | enabled: true 48 | subject: The form on your website was submitted 49 | from_name: name # uses the submitted value of the name field 50 | from_email: email # uses the submitted value of the email field 51 | to_name: Kenny Koala # recipient of the notification mail 52 | to_email: kenny@example.com # recipient of the notification mail 53 | fields: 54 | name: 55 | type: text 56 | options: 57 | constraints: [ NotBlank ] 58 | email: 59 | type: email 60 | options: 61 | constraints: [ NotBlank, Email ] 62 | comment: 63 | type: text 64 | options: 65 | label: Leave an anonymous comment 66 | submit: 67 | type: submit 68 | ``` 69 | 70 | ### Include the form in your website 71 | 72 | To show our contact form, place the following tag in the desired Twig template 73 | where we want the form to show: 74 | 75 | ```twig 76 | {{ boltforms('contact') }} 77 | ``` 78 | 79 | ### Result: A contactform in your website 80 | 81 | Refresh the page and view your new form. Test by filling out the fields and 82 | submitting the form. You should get a notification on either the debug address 83 | (if `debug : true`) or on the to_email address (if `debug:false`). 84 | 85 | ### Customization of your form 86 | Now you have your first form, you can continue and customize your Boltform in many ways: 87 | 88 | - [Adding your own fields](./fields.md) 89 | - [Customizing the email or formtemplates](./templates.md) 90 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | BoltForms Documentation 2 | ======================= 3 | 4 | - [Getting Started](getting-started.md) 5 | - [Fields](fields.md) 6 | - [Basic Fields](fields/text.md) 7 | - [Choice](fields/choice.md) 8 | - [Upload](fields/upload.md) 9 | - [Meta data](meta-data.md) 10 | - [Templates](templates.md) 11 | - [Email Notifications](email.md) 12 | - [Submission & Redirection](submission.md) 13 | - [Saving to a contenttype or a database](saving-to-contenttype-database.md) 14 | - [Setting-up Form options](form-options.md) 15 | - [Events](events.md) 16 | - [Recaptcha](recaptcha.md) 17 | - [Upgrading](upgrading.md) 18 | -------------------------------------------------------------------------------- /docs/meta-data.md: -------------------------------------------------------------------------------- 1 | Using Hidden Meta Data 2 | ====================== 3 | 4 | Additional data can be used for form processing, that is not sent to the user's 5 | browser, rather stored locally. 6 | 7 | Meta data is added in the Twig template where `{{ boltforms() }}` is used, via 8 | the `meta` parameter. This parameter is an associative array of property names 9 | and a set of value keys: 10 | 11 | - `use` — Either a string or array of places that the data should be passed to 12 | - `value` — A string, number or array 13 | 14 | An example would be: 15 | 16 | ```twig 17 | {{ boltforms('my_form', 18 | meta = { 19 | 'name': { 20 | use: [ 'database', 'email' ], 21 | value: variable_value 22 | }, 23 | 'id': { 24 | use: 'database', 25 | value: record.id 26 | }, 27 | 'koala': { 28 | value: { food: 'Gum Leaves', shelter: 'Gum Tree' } 29 | } 30 | }) 31 | }} 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/recaptcha.md: -------------------------------------------------------------------------------- 1 | reCAPTCHA Support 2 | ================= 3 | 4 | BoltForms has support for Google's reCAPTCHA service which adds a level of spam 5 | protection to your forms, preventing automated bots from making submissions. 6 | 7 | If you look inside the configuration file you'll see some settings available 8 | that are initially commented out, they look like this: 9 | 10 | ```yml 11 | recaptcha: 12 | enabled: false 13 | label: "Please enter the reCaptcha text to prove you're a human" 14 | public_key: '' 15 | private_key: '' 16 | error_message: "The CAPTCHA wasn't entered correctly. Please try again." 17 | theme: clean 18 | ``` 19 | 20 | Basic Setup 21 | ----------- 22 | 23 | To use the basic version of reCAPTCHA you will need to visit: 24 | https://www.google.com/recaptcha/admin and register your site. When asked the 25 | question: Choose the type of reCAPTCHA, use the option labelled: reCAPTCHA V2. 26 | 27 | When you have completed the setup form you will be given two keys, a public key 28 | and a private key. Both of these need to be added to the above configuration 29 | next to the appropriate setting. 30 | 31 | Once you have done this, then set `enabled` to `true` and your forms will have 32 | the additional reCAPTCHA fields added to them. 33 | 34 | Invisible Setup 35 | --------------- 36 | 37 | BoltForms also has support for the new Invisible reCAPTCHA service which works 38 | similarly to the original service, but without the need for an additional form 39 | field. Instead Google analyses the behaviour of your visitors and automatically 40 | classifies them as genuine or not. 41 | 42 | To setup BoltForms in this way, you follow the same process as above but when 43 | signing up for a key but select the option labelled: Invisible reCAPTCHA on the 44 | setup form. 45 | 46 | You then need to add an additional `type` option to the configuration so your 47 | config will look something like this: 48 | 49 | ```yml 50 | recaptcha: 51 | enabled: true 52 | label: "Please enter the reCaptcha text to prove you're a human" 53 | public_key: 'abc123456789' 54 | private_key: 'xyz123456789' 55 | error_message: "The CAPTCHA wasn't entered correctly. Please try again." 56 | theme: clean 57 | type: invisible 58 | ``` 59 | 60 | Setting the type attribute to `invisible` will use the correct service, you'll 61 | see that it is working via a small reCAPTCHA logo added to the bottom right of 62 | your page. You can change the position of the badge by adding badge_location to 63 | your config. You can use: bottomright, bottomleft or inline 64 | 65 | Disabling reCAPTCHA on some forms 66 | --------------------------------- 67 | 68 | If you want reCAPTCHA enabled globally but want to disable it on a certain form 69 | you can do this by adding a setting to the form configuration: 70 | 71 | For example assuming that reCAPTCHA is enabled in the global settings but you 72 | don't want it added to your contact form, then you can setup the form like 73 | this: 74 | 75 | ```yml 76 | contact: 77 | notification: 78 | enabled: true 79 | debug: false 80 | subject: Your message was submitted 81 | from_name: name # Email addresses and names can be either the 82 | from_email: email # name of a field below or valid text. 83 | replyto_email: email # 84 | replyto_name: name # NOTE: Email addresses must be valid 85 | to_name: My Site # 86 | to_email: noreply@example.com # 87 | feedback: 88 | success: Message submission successful 89 | error: There are errors in the form, please fix before trying to resubmit 90 | recaptcha: false 91 | fields: 92 | name: 93 | type: text 94 | options: 95 | required: true 96 | label: Name 97 | attr: 98 | placeholder: Your name... 99 | email: 100 | type: email 101 | options: 102 | required: true 103 | label: Email address 104 | attr: 105 | placeholder: Your email... 106 | message: 107 | type: textarea 108 | options: 109 | required: true 110 | label: Your message 111 | attr: 112 | placeholder: Your message... 113 | ``` 114 | -------------------------------------------------------------------------------- /docs/saving-to-contenttype-database.md: -------------------------------------------------------------------------------- 1 | Saving to a ContentType or Database 2 | ============== 3 | You can save form entries into a contenttype or a separate database. 4 | 5 | ## ContentTypes 6 | 7 | There are basically 2 things you need to do: 8 | 9 | - Prepare the ContentType that the data goes into. 10 | - Map which BoltForms field goes into which ContentType field. 11 | 12 | ### Prepare the ContentType 13 | 14 | ```yaml 15 | database: 16 | contenttype: responses # ContentType record to create 17 | ``` 18 | 19 | ### Mapped Fields 20 | 21 | ###### (boltforms.bolt.yml) 22 | 23 | ```yaml 24 | database: 25 | contenttype: 26 | name: responses # ContentType record to create 27 | field_map: 28 | name: 'title' # Form field "name" will be saved to the ContentType field "title" 29 | email: ~ # Do not try to save this field to the ContentType 30 | animal: 'animal' # Form field "animal" will be saved to the ContentType field "animal" 31 | message: 'sent_message' # Form field "message" will be saved to the ContentType field "sent_message" 32 | fields: 33 | email: 34 | type: email 35 | name: 36 | type: text 37 | options: 38 | label: "Your name" 39 | animal: 40 | type: choice 41 | options: 42 | label: "Use your paw to select what you are" 43 | choices: { "I am a cat" : "I am a cat", "I am a raccoon" : "I am a raccoon", "I am a Koala" : "I am a Koala" } 44 | expanded: false 45 | message: 46 | type: textarea 47 | status: # Don't publish new record after submitting but leave that to the editor 48 | type: hidden 49 | options: 50 | label: false 51 | attr: 52 | value: draft 53 | ``` 54 | 55 | ###### (contenttypes.yml) 56 | 57 | ```yaml 58 | responses: 59 | name: Responses 60 | singular_name: Response 61 | fields: 62 | title: 63 | type: text 64 | label: Name 65 | animal: 66 | type: select 67 | values: { "I am a cat" : "I am a cat", "I am a raccoon" : "I am a raccoon", "I am a Koala" : "I am a Koala" } 68 | sent_message: 69 | type: textarea 70 | default_status: draft 71 | ``` 72 | 73 | 74 | In most cases the fieldtype in `boltforms.bolt.yml` and `contenttypes.yml` can be the same, but note that you need a `select` field with `values` to save a `choice` BoltForms field with `choices`. 75 | 76 | ## Set the publication status 77 | 78 | If you would like to change the default status of an entry from `published` to something else, you can add a hidden field to your `boltforms.bolt.yml` to set the status. For example: 79 | 80 | 81 | ```yaml 82 | fields: 83 | status: 84 | type: hidden 85 | options: 86 | attr: 87 | value: draft 88 | ``` 89 | 90 | In the above example, you can replace "draft" with whatever status you would like to assign newly inserted records. 91 | 92 | 93 | ## Regular Database Table 94 | 95 | If you want to keep form entries apart from your site's content, you may prefer a separate database to collect form entries. 96 | 97 | **(This part of the docs yet to be completed)** 98 | 99 | ```yaml 100 | database: 101 | table: bolt_secret_table 102 | ``` 103 | -------------------------------------------------------------------------------- /docs/search.md: -------------------------------------------------------------------------------- 1 | Search 2 | ====== 3 | 4 |
5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/submission.md: -------------------------------------------------------------------------------- 1 | Submission & Redirection 2 | ======================== 3 | 4 | Redirect on success 5 | ------------------- 6 | 7 | On successful submit the user can be redirected to another Bolt page, or URL. 8 | The page for the redirect target must exist. The redirect is added to the 9 | `feedback` key of the form, for example: 10 | 11 | ```yaml 12 | feedback: 13 | success: Form submission successful 14 | error: There are errors in the form, please fix before trying to resubmit 15 | redirect: 16 | target: page/another-page # A page path, or URL 17 | query: [ name, email ] # Optional keys for the GET parameters 18 | ``` 19 | 20 | **NOTE:** 21 | 22 | - `target:` — Either a route in the form of `contenttype/slug`, a full URL like 23 | `https://example.org/foo/bar` or an anchor like `'#contactform'` to return to 24 | a specific location on the originating page. 25 | - `query:` — (optional) Either an indexed, or associative array 26 | - `[ name, email ]` would create the query string `?name=value-of-name-field&email=value-of-email-field` 27 | - `{ name: 'foo', email: 'bar' }` would create the query string `?name=foo&email=bar` 28 | 29 | 30 | AJAX (beta) 31 | ----------- 32 | 33 | Submissions can be done using AJAX request/responses. To enable this (beta) 34 | feature, simplly set `ajax: true` under the `submission:` key in your form's 35 | configuration, e.g.: 36 | 37 | ``` 38 | my_form: 39 | submission: 40 | ajax: true 41 | fields: 42 | # … and so on 43 | ``` 44 | 45 | **NOTE:** This feature *currently requires* jQuery, and we will request Bolt to 46 | attempt to load it via its asset loader chain, i.e. if you're inserting a 47 | javascript file called `jquery.*.js` (note the wildcard), then Bolt shouldn't 48 | interfere. 49 | -------------------------------------------------------------------------------- /docs/upgrading.md: -------------------------------------------------------------------------------- 1 | Upgrading 2 | ========= 3 | 4 | If you are upgrading from a relase prior to 4.0, there are a few settings which 5 | may require attention, or modification. 6 | 7 | Twig Variables 8 | -------------- 9 | 10 | The following Twig feedback variables are deprecated: 11 | 12 | - `error` 13 | - `message` 14 | 15 | They are replaced by `messages` (plural) that contains a keyed array of 16 | `info`, `error`, and `debug` message arrays. 17 | -------------------------------------------------------------------------------- /icon-boltforms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bolt/boltforms/de80a4f5549fe642309149b83e819d5796d683ba/icon-boltforms.png -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | vendor/bolt/bolt/app/config/config.yml.dist 25 | 26 | 27 | vendor/bolt/bolt/app/config/contenttypes.yml.dist 28 | 29 | 30 | vendor/bolt/bolt/app/config/menu.yml.dist 31 | 32 | 33 | vendor/bolt/bolt/app/config/permissions.yml.dist 34 | 35 | 36 | vendor/bolt/bolt/app/config/routing.yml.dist 37 | 38 | 39 | vendor/bolt/bolt/app/config/taxonomy.yml.dist 40 | 41 | 42 | 43 | 44 | 45 | theme/base-2014 46 | 47 | 48 | 49 | 50 | 51 | 52 | vendor/bolt/bolt/tests/phpunit/unit/resources/db/bolt.db 53 | 54 | 55 | 56 | true 57 | 58 | true 59 | 60 | 61 | 62 | 63 | 64 | vendor 65 | lib 66 | init.php 67 | 68 | 69 | Extension.php 70 | src 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/Asset/ReCaptcha.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class ReCaptcha extends JavaScript 31 | { 32 | /** @var string */ 33 | protected $htmlLang; 34 | 35 | /** @var string */ 36 | protected $renderType; 37 | 38 | /** @var string */ 39 | protected $theme; 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function __toString() 45 | { 46 | $onLoad = ($this->getRenderType() == 'invisible') ? '&render=explicit&onload=invisibleRecaptchaOnLoad' : ''; 47 | $theme = sprintf('', $this->getTheme()); 48 | $api = sprintf('', $this->getHtmlLang(), $onLoad); 49 | 50 | return $theme . $api; 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getHtmlLang() 57 | { 58 | return $this->htmlLang; 59 | } 60 | 61 | /** 62 | * @param string $htmlLang 63 | * 64 | * @return ReCaptcha 65 | */ 66 | public function setHtmlLang($htmlLang) 67 | { 68 | $this->htmlLang = $htmlLang; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * @return string 75 | */ 76 | public function getRenderType() 77 | { 78 | return $this->renderType; 79 | } 80 | 81 | /** 82 | * @param $type 83 | * 84 | * @return ReCaptcha 85 | */ 86 | public function setRenderType($type) 87 | { 88 | $this->renderType = $type; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * @return string 95 | */ 96 | public function getTheme() 97 | { 98 | return $this->theme; 99 | } 100 | 101 | /** 102 | * @param string $theme 103 | * @return ReCaptcha 104 | */ 105 | public function setTheme($theme) 106 | { 107 | $this->theme = $theme; 108 | 109 | return $this; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Choice/ChoiceInterface.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | 29 | interface ChoiceInterface 30 | { 31 | public function getName(); 32 | 33 | public function getChoices(); 34 | } 35 | -------------------------------------------------------------------------------- /src/Choice/ChoiceResolver.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class ChoiceResolver extends AbstractChoiceOptionResolver 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Choice/EventResolver.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class EventResolver extends AbstractChoiceOptionResolver 33 | { 34 | /** @var array */ 35 | private $choices; 36 | /** @var string */ 37 | private $formName; 38 | /** @var EventDispatcherInterface */ 39 | private $dispatcher; 40 | 41 | /** 42 | * Constructor. 43 | * 44 | * @param string $formName Name of the form containing the field 45 | * @param string $fieldName Name of the field 46 | * @param array $fieldOptions Options for field 47 | * @param EventDispatcherInterface $dispatcher 48 | */ 49 | public function __construct($formName, $fieldName, array $fieldOptions, EventDispatcherInterface $dispatcher) 50 | { 51 | parent::__construct($formName, $fieldName, $fieldOptions); 52 | 53 | $this->dispatcher = $dispatcher; 54 | $this->formName = $formName; 55 | } 56 | 57 | /** 58 | * Get the name. 59 | * 60 | * @return string 61 | */ 62 | public function getName() 63 | { 64 | return $this->name; 65 | } 66 | 67 | /** 68 | * Return choices array. 69 | * 70 | * @return array 71 | */ 72 | public function getChoices() 73 | { 74 | if ($this->choices === null) { 75 | $event = new ChoiceEvent($this->formName, $this->name, $this->options); 76 | 77 | $this->dispatcher->dispatch($this->getEventName(), $event); 78 | 79 | $this->choices = $event->getChoices(); 80 | } 81 | 82 | return $this->choices; 83 | } 84 | 85 | /** 86 | * Return the name of the event we want to dispatch. 87 | * 88 | * @return string 89 | */ 90 | private function getEventName() 91 | { 92 | $parts = explode('::', $this->options['choices']); 93 | 94 | return isset($parts[1]) ? $parts[1] : BoltFormsEvents::DATA_CHOICE_EVENT; 95 | } 96 | 97 | /** 98 | * Get the name. 99 | * 100 | * @return string 101 | */ 102 | public function getFormName() 103 | { 104 | return $this->formName; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Config/AbstractCascadingBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | abstract class AbstractCascadingBag extends ParameterBag 31 | { 32 | /** @var Config */ 33 | protected $rootConfig; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param array $parameters 39 | * @param Config|null $rootConfig 40 | */ 41 | public function __construct(array $parameters = [], Config $rootConfig = null) 42 | { 43 | parent::__construct($parameters); 44 | $this->rootConfig = $rootConfig; 45 | } 46 | 47 | /** 48 | * If there is a root configuration supplied, return its value as a default. 49 | * 50 | * @param string $key 51 | * 52 | * @return mixed 53 | */ 54 | protected function getHierarchicalValue($key) 55 | { 56 | if ($this->rootConfig === null) { 57 | return $this->get($key); 58 | } 59 | 60 | return $this->get($key) ?: $this->getRootSection()->get($key); 61 | } 62 | 63 | /** 64 | * @return ParameterBag 65 | */ 66 | abstract protected function getRootSection(); 67 | } 68 | -------------------------------------------------------------------------------- /src/Config/FieldMap/ContentType.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class ContentType extends ParameterBag 31 | { 32 | } 33 | -------------------------------------------------------------------------------- /src/Config/FieldMap/Email.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class Email extends ParameterBag 31 | { 32 | /** @var string */ 33 | protected $config = 'config'; 34 | /** @var string */ 35 | protected $data = 'data'; 36 | /** @var string */ 37 | protected $metaData = 'metadata'; 38 | /** @var string */ 39 | protected $fields = 'fields'; 40 | /** @var string */ 41 | protected $subject = 'subject'; 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getConfig() 47 | { 48 | return $this->config; 49 | } 50 | 51 | /** 52 | * @param string $config 53 | * 54 | * @return Email 55 | */ 56 | public function setConfig($config) 57 | { 58 | $this->config = $config; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getData() 67 | { 68 | return $this->data; 69 | } 70 | 71 | /** 72 | * @param string $data 73 | * 74 | * @return Email 75 | */ 76 | public function setData($data) 77 | { 78 | $this->data = $data; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getMetaData() 87 | { 88 | return $this->metaData; 89 | } 90 | 91 | /** 92 | * @param string $metaData 93 | * 94 | * @return Email 95 | */ 96 | public function setMetaData($metaData) 97 | { 98 | $this->metaData = $metaData; 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @return string 105 | */ 106 | public function getFields() 107 | { 108 | return $this->fields; 109 | } 110 | 111 | /** 112 | * @param string $fields 113 | * 114 | * @return Email 115 | */ 116 | public function setFields($fields) 117 | { 118 | $this->fields = $fields; 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * @return string 125 | */ 126 | public function getSubject() 127 | { 128 | return $this->subject; 129 | } 130 | 131 | /** 132 | * @param string $subject 133 | * 134 | * @return Email 135 | */ 136 | public function setSubject($subject) 137 | { 138 | $this->subject = $subject; 139 | 140 | return $this; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Config/Form/DatabaseOptionsBag.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class DatabaseOptionsBag extends ParameterBag 32 | { 33 | /** 34 | * Constructor. 35 | * 36 | * @param array $parameters 37 | * @param FieldsBag $fieldsConfig 38 | */ 39 | public function __construct(array $parameters = [], FieldsBag $fieldsConfig) 40 | { 41 | parent::__construct($parameters); 42 | $this->initialise($fieldsConfig); 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getTable() 49 | { 50 | return $this->get('table'); 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getContentType() 57 | { 58 | return $this->get('contenttype'); 59 | } 60 | 61 | /** 62 | * @return FieldMap\ContentType|null 63 | */ 64 | public function getContentTypeFieldMap() 65 | { 66 | return $this->get('field_map'); 67 | } 68 | 69 | /** 70 | * We may get the config value `contenttype` as a string, in which case we 71 | * map form field names to ContentType field names one-to-one. 72 | * 73 | * If we get an array, we check for the `field_map` associative array of 74 | * form field names to ContentType field names to alternatively map to.. 75 | * 76 | * @param FieldsBag $fieldsConfig 77 | */ 78 | private function initialise(FieldsBag $fieldsConfig) 79 | { 80 | if ($this->has('contenttype') === false) { 81 | return; 82 | } 83 | 84 | $contentType = $this->get('contenttype'); 85 | if ($contentType === null) { 86 | // Would be something weird here 87 | return; 88 | } 89 | 90 | if (is_string($contentType)) { 91 | $name = $contentType; 92 | $fieldNameMap = []; 93 | } elseif (isset($contentType['name'])) { 94 | $name = $contentType['name']; 95 | $fieldNameMap = isset($contentType['field_map']) ? (array) $contentType['field_map'] : []; 96 | } else { 97 | return; 98 | } 99 | 100 | $fieldNames = $fieldsConfig->keys(); 101 | $mapParams = array_combine($fieldNames, $fieldNames); 102 | $map = new FieldMap\ContentType($mapParams); 103 | foreach ($map->all() as $formFieldName => $contentTypeFieldName) { 104 | if (array_key_exists($formFieldName, $fieldNameMap) && $fieldNameMap[$formFieldName] === null) { 105 | $map->remove($formFieldName); 106 | } elseif (isset($fieldNameMap[$formFieldName])) { 107 | $map->set($formFieldName, $fieldNameMap[$formFieldName]); 108 | } 109 | } 110 | 111 | $this->set('field_map', $map); 112 | $this->set('contenttype', $name); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Config/Form/FeedbackOptionsBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class FeedbackOptionsBag extends ParameterBag 31 | { 32 | /** 33 | * @return string 34 | */ 35 | public function getSuccessMessage() 36 | { 37 | return $this->get('success'); 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getErrorMessage() 44 | { 45 | return $this->get('error'); 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getRedirect() 52 | { 53 | return $this->get('redirect'); 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getRedirectTarget() 60 | { 61 | return $this->get('redirect')['target']; 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getRedirectQuery() 68 | { 69 | return $this->get('redirect')['query']; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Config/Form/FieldBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class FieldBag extends ParameterBag 31 | { 32 | /** @var string */ 33 | protected $fieldName; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param array $fieldName 39 | * @param array $parameters 40 | */ 41 | public function __construct($fieldName, array $parameters) 42 | { 43 | $this->fieldName = $fieldName; 44 | $parameters += ['type' => null, 'options' => null]; 45 | $parameters['options'] = new FieldOptionsBag($parameters['options']); 46 | if (isset($parameters['event'])) { 47 | $parameters['event'] = new FieldOptionsBag($parameters['event']); 48 | } 49 | 50 | parent::__construct($parameters); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function all() 57 | { 58 | $parameters = $this->parameters; 59 | foreach ($parameters as $key => $value) { 60 | if ($value instanceof ParameterBag) { 61 | $parameters[$key] = $value->all(); 62 | } else { 63 | $parameters[$key] = $value; 64 | } 65 | } 66 | 67 | return $parameters; 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | public function getFieldName() 74 | { 75 | return $this->fieldName; 76 | } 77 | 78 | /** 79 | * @return mixed 80 | */ 81 | public function getOptions() 82 | { 83 | return $this->get('options'); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function __call($name, $args = []) 90 | { 91 | $name = strtolower(preg_replace('/^get/', '', $name)); 92 | if (isset($this->parameters[$name])) { 93 | return $this->parameters[$name]; 94 | } 95 | 96 | throw new \BadMethodCallException(sprintf('%s ', __CLASS__)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Config/Form/FieldOptionsBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class FieldOptionsBag extends ParameterBag 31 | { 32 | /** 33 | * Constructor. 34 | * 35 | * @param array $parameters 36 | */ 37 | public function __construct(array $parameters) 38 | { 39 | foreach ($parameters as $key => $value) { 40 | if (is_array($value)) { 41 | $value = new ParameterBag($value); 42 | } 43 | $parameters[$key] = $value; 44 | } 45 | 46 | parent::__construct($parameters); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function all() 53 | { 54 | $parameters = $this->parameters; 55 | foreach ($parameters as $key => $value) { 56 | if ($value instanceof ParameterBag) { 57 | $parameters[$key] = $value->all(); 58 | } else { 59 | $parameters[$key] = $value; 60 | } 61 | } 62 | 63 | return $parameters; 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getType() 70 | { 71 | return $this->get('type'); 72 | } 73 | 74 | /** 75 | * @return array 76 | */ 77 | public function getOptions() 78 | { 79 | return $this->get('options'); 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function __call($name, $args = []) 86 | { 87 | $name = strtolower(preg_replace('/^get/', '', $name)); 88 | if (isset($this->parameters[$name])) { 89 | return $this->parameters[$name]; 90 | } 91 | 92 | throw new \BadMethodCallException(sprintf('Unknown field option parameter: %s ', $name)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Config/Form/FieldsBag.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class FieldsBag extends ParameterBag 32 | { 33 | /** 34 | * Constructor. 35 | * 36 | * @param array $parameters 37 | * 38 | * @throws FormOptionException 39 | */ 40 | public function __construct(array $parameters) 41 | { 42 | foreach ($parameters as $fieldName => $fieldConfig) { 43 | $parameters[$fieldName] = new FieldBag($fieldName, $fieldConfig); 44 | } 45 | 46 | parent::__construct($parameters); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function all() 53 | { 54 | $parameters = $this->parameters; 55 | foreach ($parameters as $key => $value) { 56 | if ($value instanceof ParameterBag) { 57 | $parameters[$key] = $value->all(); 58 | } else { 59 | $parameters[$key] = $value; 60 | } 61 | } 62 | 63 | return $parameters; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function __call($name, $args = []) 70 | { 71 | $name = strtolower(preg_replace('/^get/', '', $name)); 72 | if (isset($this->parameters[$name])) { 73 | return $this->parameters[$name]; 74 | } 75 | 76 | throw new \BadMethodCallException(sprintf('%s ', __CLASS__)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Config/Form/FormOptionsBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Arthur Guigand 26 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 27 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 28 | */ 29 | class FormOptionsBag extends ParameterBag 30 | { 31 | /** 32 | * Constructor. 33 | * 34 | * @param array $parameters 35 | */ 36 | public function __construct(array $parameters) 37 | { 38 | foreach ($parameters as $key => $value) { 39 | if (is_array($value)) { 40 | $value = new ParameterBag($value); 41 | } 42 | $parameters[$key] = $value; 43 | } 44 | 45 | parent::__construct($parameters); 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function all() 52 | { 53 | $parameters = $this->parameters; 54 | foreach ($parameters as $key => $value) { 55 | if ($value instanceof ParameterBag) { 56 | $parameters[$key] = $value->all(); 57 | } else { 58 | $parameters[$key] = $value; 59 | } 60 | } 61 | 62 | return $parameters; 63 | } 64 | 65 | /** 66 | * Get form's constraints. 67 | * 68 | * @return array 69 | */ 70 | public function getConstraints() 71 | { 72 | return $this->get('constraints'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Config/Form/MetaDataBag.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class MetaDataBag 29 | { 30 | /** @var string */ 31 | protected $name; 32 | /** @var array */ 33 | protected $use; 34 | /** @var mixed */ 35 | protected $value; 36 | 37 | /** 38 | * Constructor. 39 | * 40 | * @param string $name 41 | * @param array $parameters 42 | */ 43 | public function __construct($name, array $parameters = []) 44 | { 45 | $this->name = $name; 46 | $this->use = isset($parameters['use']) ? (array) $parameters['use'] : null; 47 | $this->value = isset($parameters['value']) ? $parameters['value'] : null; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getName() 54 | { 55 | return $this->name; 56 | } 57 | 58 | /** 59 | * @param string $name 60 | * 61 | * @return MetaDataBag 62 | */ 63 | public function setName($name) 64 | { 65 | $this->name = $name; 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * @return array 72 | */ 73 | public function getUse() 74 | { 75 | return $this->use; 76 | } 77 | 78 | /** 79 | * @param array $use 80 | * 81 | * @return MetaDataBag 82 | */ 83 | public function setUse($use) 84 | { 85 | $this->use = $use; 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * @return mixed 92 | */ 93 | public function getValue() 94 | { 95 | return $this->value; 96 | } 97 | 98 | /** 99 | * @param mixed $value 100 | * 101 | * @return MetaDataBag 102 | */ 103 | public function setValue($value) 104 | { 105 | $this->value = $value; 106 | 107 | return $this; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Config/Form/SubmissionOptionsBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class SubmissionOptionsBag extends ParameterBag 31 | { 32 | /** 33 | * @return bool 34 | */ 35 | public function isAjax() 36 | { 37 | return $this->getBoolean('ajax'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Config/Form/UploadsOptionBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class UploadsOptionBag extends ParameterBag 31 | { 32 | /** 33 | * Constructor. 34 | */ 35 | public function __construct(array $parameter) 36 | { 37 | parent::__construct($parameter); 38 | } 39 | 40 | /** 41 | * @return boolean 42 | */ 43 | public function isEnabled() 44 | { 45 | return $this->get('enabled', false); 46 | } 47 | 48 | /** 49 | * @param boolean $enabled 50 | * 51 | * @return UploadsOptionBag 52 | */ 53 | public function setEnabled($enabled) 54 | { 55 | $this->enabled = $enabled; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function getBaseDirectory() 64 | { 65 | return $this->get('base_directory'); 66 | } 67 | 68 | /** 69 | * @param string $baseDirectory 70 | * 71 | * @return UploadsOptionBag 72 | */ 73 | public function setBaseDirectory($baseDirectory) 74 | { 75 | $this->set('base_directory', $baseDirectory); 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getFilenameHandling() 84 | { 85 | return $this->get('filename_handling', 'suffix'); 86 | } 87 | 88 | /** 89 | * @param string $filenameHandling 90 | * 91 | * @return UploadsOptionBag 92 | */ 93 | public function setFilenameHandling($filenameHandling) 94 | { 95 | $this->set('filename_handling', $filenameHandling); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * @return string 102 | */ 103 | public function getManagementController() 104 | { 105 | return $this->get('management_controller', false); 106 | } 107 | 108 | /** 109 | * @param string $managementController 110 | * 111 | * @return UploadsOptionBag 112 | */ 113 | public function setManagementController($managementController) 114 | { 115 | $this->set('management_controller', $managementController); 116 | 117 | return $this; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Config/Form/UploadsOptionsBag.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class UploadsOptionsBag extends ParameterBag 31 | { 32 | /** 33 | * @return string 34 | */ 35 | public function getSubdirectory() 36 | { 37 | return $this->get('subdirectory'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Config/MetaData.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class MetaData extends ParameterBag 33 | { 34 | /** @var string */ 35 | protected $_metaId; 36 | 37 | /** 38 | * Constructor. 39 | * 40 | * @param array $parameters 41 | */ 42 | public function __construct(array $parameters = []) 43 | { 44 | parent::__construct(); 45 | $this->_metaId = bin2hex(random_bytes(32)); 46 | 47 | foreach ($parameters as $key => $value) { 48 | $this->set($key, $value); 49 | } 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function set($key, $value) 56 | { 57 | $this->parameters[$key] = new Form\MetaDataBag($key, $value); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function replace(array $parameters = []) 64 | { 65 | $this->parameters = []; 66 | foreach ($parameters as $key => $value) { 67 | $this->set($key, $value); 68 | } 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getMetaId() 75 | { 76 | return $this->_metaId; 77 | } 78 | 79 | /** 80 | * @param string $metaId 81 | */ 82 | public function setMetaId($metaId) 83 | { 84 | $this->_metaId = $metaId; 85 | } 86 | 87 | /** 88 | * Return the meta data for a particular use. 89 | * 90 | * @param string $target 91 | * 92 | * @return array 93 | */ 94 | public function getUsedMeta($target) 95 | { 96 | $meta = []; 97 | foreach ($this->keys() as $key) { 98 | if (in_array($target, (array) $this->get($key)->getUse())) { 99 | $meta[$key] = $this->get($key)->getValue(); 100 | } 101 | } 102 | 103 | return $meta; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Controller/UploadManagement.php: -------------------------------------------------------------------------------- 1 | . 30 | * 31 | * @author Gawain Lynch 32 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 33 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 34 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 35 | */ 36 | class UploadManagement implements ControllerProviderInterface 37 | { 38 | /** 39 | * @param \Silex\Application $app 40 | * 41 | * @return \Silex\ControllerCollection 42 | */ 43 | public function connect(Application $app) 44 | { 45 | /** @var $ctr \Silex\ControllerCollection */ 46 | $ctr = $app['controllers_factory']; 47 | 48 | $ctr->match('/download', [$this, 'download']) 49 | ->bind('BoltFormsDownload') 50 | ->method('GET'); 51 | 52 | return $ctr; 53 | } 54 | 55 | public function download(Application $app, Request $request) 56 | { 57 | $fs = new Filesystem(); 58 | $file = $request->query->get('file'); 59 | if ($file === null) { 60 | return new Response('File not given', Response::HTTP_BAD_REQUEST); 61 | } 62 | /** @var Config\Config $config */ 63 | $config = $app['boltforms.config']; 64 | $fullPath = $config->getUploads()->getBaseDirectory() . '/' . $file; 65 | if (!$fs->exists($fullPath)) { 66 | return new Response('File not found', Response::HTTP_NOT_FOUND); 67 | } 68 | 69 | $response = new BinaryFileResponse($fullPath); 70 | $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, basename($fullPath)); 71 | 72 | return $response; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Event/BoltFormsEvent.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class BoltFormsEvent extends FormEvent 32 | { 33 | /** @var \Symfony\Component\Form\FormEvent */ 34 | protected $event; 35 | /** @var array */ 36 | protected $data; 37 | /** @var \Symfony\Component\Form\FormInterface */ 38 | protected $form; 39 | /** @var string */ 40 | protected $formsEventName; 41 | 42 | /** 43 | * @param FormEvent $event 44 | * @param string $formsEventName 45 | */ 46 | public function __construct(FormEvent $event, $formsEventName) 47 | { 48 | parent::__construct($event->getForm(), $event->getData()); 49 | 50 | $this->event = $event; 51 | $this->formsEventName = $formsEventName; 52 | } 53 | 54 | public function getEvent() 55 | { 56 | return $this->event; 57 | } 58 | 59 | public function getData() 60 | { 61 | return $this->data; 62 | } 63 | 64 | public function setData($data) 65 | { 66 | if ($this->formsEventName === FormEvents::PRE_SUBMIT) { 67 | $this->event->setData($data); 68 | } else { 69 | throw new \RuntimeException(__CLASS__ . '::' . __FUNCTION__ . ' can only be called in BoltFormsEvents::PRE_SUBMIT'); 70 | } 71 | } 72 | 73 | public function getForm() 74 | { 75 | return $this->form; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Event/BoltFormsEvents.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | final class BoltFormsEvents 29 | { 30 | /* 31 | * Symfony Forms events 32 | */ 33 | const PRE_SUBMIT = 'boltforms.pre_bind'; 34 | const SUBMIT = 'boltforms.bind'; 35 | const POST_SUBMIT = 'boltforms.post_bind'; 36 | const PRE_SET_DATA = 'boltforms.pre_set_data'; 37 | const POST_SET_DATA = 'boltforms.post_set_data'; 38 | 39 | /* 40 | * Events in the data processor 41 | */ 42 | const SUBMISSION_PRE_PROCESSOR = 'boltforms.submission_pre_processor'; 43 | const SUBMISSION_POST_PROCESSOR = 'boltforms.submission_post_processor'; 44 | 45 | const SUBMISSION_PROCESS_FIELDS = 'boltforms.submission_process_fields'; 46 | const SUBMISSION_PROCESS_UPLOADS = 'boltforms.submission_process_uploads'; 47 | const SUBMISSION_PROCESS_CONTENTTYPE = 'boltforms.submission_process_contenttype'; 48 | const SUBMISSION_PROCESS_DATABASE = 'boltforms.submission_process_database'; 49 | const SUBMISSION_PROCESS_EMAIL = 'boltforms.submission_process_email'; 50 | const SUBMISSION_PROCESS_FEEDBACK = 'boltforms.submission_process_feedback'; 51 | const SUBMISSION_PROCESS_REDIRECT = 'boltforms.submission_process_redirect'; 52 | 53 | /* 54 | * Shortcut priorities for processor event priority 55 | */ 56 | const PRIORITY_EARLY = 128; 57 | const PRIORITY_INTERNAL = 0; 58 | const PRIORITY_LATE = -128; 59 | 60 | /* 61 | * Custom data events 62 | */ 63 | const DATA_NEXT_INCREMENT = 'boltforms.next_increment'; 64 | const DATA_RANDOM_STRING = 'boltforms.random_string'; 65 | const DATA_SERVER_VALUE = 'boltforms.server_value'; 66 | const DATA_SESSION_VALUE = 'boltforms.session_value'; 67 | const DATA_TIMESTAMP = 'boltforms.timestamp'; 68 | 69 | const DATA_CHOICE_EVENT = 'boltforms.choice'; 70 | 71 | /* 72 | * Email notification 73 | */ 74 | const PRE_EMAIL_SEND = 'boltforms.pre_email_send'; 75 | 76 | private function __construct() 77 | { 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Event/ChoiceEvent.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class ChoiceEvent extends Event 32 | { 33 | /** @var string */ 34 | private $formName; 35 | /** @var string */ 36 | private $fieldName; 37 | /** @var array */ 38 | private $options; 39 | /** @var ParameterBag */ 40 | private $choices; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param string $formName 46 | * @param string $fieldName 47 | * @param array $options 48 | */ 49 | public function __construct($formName, $fieldName, array $options) 50 | { 51 | $this->formName = $formName; 52 | $this->fieldName = $fieldName; 53 | $this->options = $options; 54 | $this->choices = new ParameterBag(); 55 | } 56 | 57 | /** 58 | * @return string 59 | */ 60 | public function getFormName() 61 | { 62 | return $this->formName; 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | public function getFieldName() 69 | { 70 | return $this->fieldName; 71 | } 72 | 73 | /** 74 | * @return array 75 | */ 76 | public function getOptions() 77 | { 78 | return $this->options; 79 | } 80 | 81 | /** 82 | * @return mixed $key 83 | */ 84 | public function getChoice($key) 85 | { 86 | return $this->choices->get($key); 87 | } 88 | 89 | /** 90 | * @return array 91 | */ 92 | public function getChoices() 93 | { 94 | return $this->choices->all(); 95 | } 96 | 97 | /** 98 | * @param string $key 99 | * @param mixed $value 100 | */ 101 | public function setChoice($key, $value) 102 | { 103 | $this->choices->set($key, $value); 104 | } 105 | 106 | /** 107 | * @param array $choices 108 | */ 109 | public function setChoices(array $choices) 110 | { 111 | $this->choices = new ParameterBag($choices); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Event/CustomDataEvent.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class CustomDataEvent extends Event 32 | { 33 | /** @var string */ 34 | protected $eventName; 35 | /** @var ParameterBag */ 36 | protected $eventParams; 37 | /** @var mixed */ 38 | protected $data; 39 | 40 | /** 41 | * @param string $eventName 42 | * @param ParameterBag $eventParams 43 | */ 44 | public function __construct($eventName, ParameterBag $eventParams) 45 | { 46 | $this->eventName = $eventName; 47 | $this->eventParams = $eventParams; 48 | } 49 | 50 | /** 51 | * Get the event name. 52 | * 53 | * @return string 54 | */ 55 | public function getName() 56 | { 57 | return $this->eventName; 58 | } 59 | 60 | /** 61 | * Get the user supplied parameters. 62 | * 63 | * @return ParameterBag 64 | */ 65 | public function getParameters() 66 | { 67 | return $this->eventParams; 68 | } 69 | 70 | /** 71 | * Get the event's data. 72 | * 73 | * @return mixed 74 | */ 75 | public function getData() 76 | { 77 | return $this->data; 78 | } 79 | 80 | /** 81 | * Set the event's data. 82 | * 83 | * @param mixed 84 | */ 85 | public function setData($data) 86 | { 87 | $this->data = $data; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Event/EmailEvent.php: -------------------------------------------------------------------------------- 1 | . 27 | * 28 | * @author Gawain Lynch 29 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 30 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 31 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 32 | */ 33 | class EmailEvent extends Event 34 | { 35 | /** @var EmailConfig */ 36 | protected $emailConfig; 37 | /** @var FormConfig */ 38 | protected $formConfig; 39 | /** @var Entity\Entity */ 40 | protected $formData; 41 | 42 | public function __construct(EmailConfig $emailConfig, FormConfig $formConfig, Entity\Entity $formData) 43 | { 44 | $this->emailConfig = $emailConfig; 45 | $this->formConfig = $formConfig; 46 | $this->formData = $formData; 47 | } 48 | 49 | /** 50 | * Get the EmailConfig object used in the email notification. 51 | * 52 | * @return EmailConfig 53 | */ 54 | public function getEmailConfig() 55 | { 56 | return $this->emailConfig; 57 | } 58 | 59 | /** 60 | * Get the FormConfig object used in the email notification. 61 | * 62 | * @return FormConfig 63 | */ 64 | public function getFormConfig() 65 | { 66 | return $this->formConfig; 67 | } 68 | 69 | /** 70 | * Get the Entity object used in the email notification. 71 | * 72 | * @return Entity\Entity 73 | */ 74 | public function getFormData() 75 | { 76 | return $this->formData; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Event/LifecycleEvent.php: -------------------------------------------------------------------------------- 1 | . 28 | * 29 | * @author Gawain Lynch 30 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 31 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 32 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 33 | */ 34 | class LifecycleEvent extends Event 35 | { 36 | /** @var FormConfig $formConfig */ 37 | protected $formConfig; 38 | /** @var Entity $formData */ 39 | protected $formData; 40 | /** @var MetaData */ 41 | private $formMetaData; 42 | /** @var Button */ 43 | protected $clickedButton; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param FormConfig $formConfig 49 | * @param Entity $formData 50 | * @param MetaData $formMetaData 51 | * @param Button $clickedButton 52 | */ 53 | public function __construct( 54 | FormConfig $formConfig, 55 | Entity $formData, 56 | MetaData $formMetaData, 57 | Button $clickedButton = null 58 | ) { 59 | $this->formConfig = $formConfig; 60 | $this->formData = $formData; 61 | $this->formMetaData = $formMetaData; 62 | $this->clickedButton = $clickedButton; 63 | } 64 | 65 | /** 66 | * @return FormConfig 67 | */ 68 | public function getFormConfig() 69 | { 70 | return $this->formConfig; 71 | } 72 | 73 | /** 74 | * @return Entity 75 | */ 76 | public function getFormData() 77 | { 78 | return $this->formData; 79 | } 80 | 81 | /** 82 | * @return MetaData 83 | */ 84 | public function getFormMetaData() 85 | { 86 | return $this->formMetaData; 87 | } 88 | 89 | /** 90 | * @return Button 91 | */ 92 | public function getClickedButton() 93 | { 94 | return $this->clickedButton; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Event/ProcessorEvent.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class ProcessorEvent extends Event 32 | { 33 | /** @var string */ 34 | protected $formName; 35 | /** @var Entity\Entity */ 36 | protected $data; 37 | 38 | /** 39 | * @param string $formName 40 | * @param Entity\Entity $data 41 | */ 42 | public function __construct($formName, Entity\Entity $data) 43 | { 44 | $this->formName = $formName; 45 | $this->data = $data; 46 | } 47 | 48 | public function getFormName() 49 | { 50 | return $this->formName; 51 | } 52 | 53 | public function getData() 54 | { 55 | return $this->data; 56 | } 57 | 58 | public function setData($data) 59 | { 60 | $this->data = $data; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Exception/BoltFormsException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | interface BoltFormsException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/EmailException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class EmailException extends \Exception implements BoltFormsException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/FileUploadException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class FileUploadException extends InternalProcessorException implements BoltFormsException 29 | { 30 | /** @var string */ 31 | private $systemMessage; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param string $message 37 | * @param string $systemMessage 38 | * @param int $code 39 | * @param \Exception|null $previous 40 | * @param bool $abort 41 | */ 42 | public function __construct($message, $systemMessage, $code = 0, \Exception $previous = null, $abort) 43 | { 44 | $code = $previous ? $previous->getCode() : 1; 45 | parent::__construct($message, $code, $previous, $abort); 46 | $this->systemMessage = $systemMessage; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function getSystemMessage() 53 | { 54 | return $this->systemMessage; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Exception/FormOptionException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class FormOptionException extends \Exception implements BoltFormsException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/FormValidationException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class FormValidationException extends \Exception implements BoltFormsException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/InternalProcessorException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class InternalProcessorException extends \Exception implements BoltFormsException 29 | { 30 | /** @var bool */ 31 | private $abort; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param string $message 37 | * @param int $code 38 | * @param \Exception|null $previous 39 | * @param bool $abort 40 | */ 41 | public function __construct($message, $code = 0, \Exception $previous = null, $abort) // = false) 42 | { 43 | $this->abort = $abort; 44 | parent::__construct($message, $code, $previous); 45 | } 46 | 47 | /** 48 | * @return boolean 49 | */ 50 | public function isAbort() 51 | { 52 | return $this->abort; 53 | } 54 | 55 | /** 56 | * @param boolean $abort 57 | */ 58 | public function setAbort($abort) 59 | { 60 | $this->abort = $abort; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Exception/InvalidConstraintException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class InvalidConstraintException extends \RuntimeException implements BoltFormsException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/UnknownFormException.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | * @author Gawain Lynch 24 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 25 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 26 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 27 | */ 28 | class UnknownFormException extends \RuntimeException implements BoltFormsException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Factory/FieldConstraint.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class FieldConstraint 31 | { 32 | const SF_NAMESPACE = '\\Symfony\\Component\\Validator\\Constraints\\'; 33 | 34 | /** 35 | * Extract, expand and set & create validator instance array(s) 36 | * 37 | * @param string $formName 38 | * @param mixed $input 39 | * 40 | * @throws Exception\InvalidConstraintException 41 | * 42 | * @return \Symfony\Component\Validator\Constraint 43 | */ 44 | public static function get($formName, $input) 45 | { 46 | $class = null; 47 | $params = null; 48 | 49 | $inputType = gettype($input); 50 | 51 | if ($inputType === 'string') { 52 | $class = static::getClassFromString($input); 53 | } elseif ($inputType === 'array') { 54 | $class = static::getClassFromArray($input, $params); 55 | } 56 | 57 | if (!class_exists($class)) { 58 | throw new Exception\InvalidConstraintException(sprintf('[BoltForms] The form "%s" has an invalid field constraint: "%s".', $formName, $class)); 59 | } 60 | 61 | return new $class($params); 62 | } 63 | 64 | /** 65 | * @param string $input 66 | * 67 | * @return string 68 | */ 69 | private static function getClassFromString($input) 70 | { 71 | return static::SF_NAMESPACE . $input; 72 | } 73 | 74 | /** 75 | * @param array $input 76 | * @param mixed $params 77 | * 78 | * @return string|null 79 | */ 80 | private static function getClassFromArray(array $input, &$params) 81 | { 82 | $input = current($input); 83 | $inputType = gettype($input); 84 | if ($inputType === 'string') { 85 | $class = static::SF_NAMESPACE . $input; 86 | } elseif ($inputType === 'array') { 87 | $class = static::SF_NAMESPACE . key($input); 88 | $params = array_pop($input); 89 | } else { 90 | $class = null; 91 | } 92 | 93 | return $class; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Form/BoltFormsExtension.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class BoltFormsExtension extends AbstractExtension 32 | { 33 | /** @var Application */ 34 | protected $app; 35 | 36 | public function __construct(Application $app) 37 | { 38 | $this->app = $app; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | protected function loadTypes() 45 | { 46 | return [ 47 | new Type\BoltFormType($this->app['dispatcher'], $this->app['boltforms.config']), 48 | ]; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | protected function loadTypeExtensions() 55 | { 56 | return []; 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | protected function loadTypeGuesser() 63 | { 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Form/DataTransformer/EntityTransformer.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | class EntityTransformer implements DataTransformerInterface 32 | { 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function transform($value) 37 | { 38 | if (is_array($value)) { 39 | return new Content($value); 40 | } 41 | 42 | return $value; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function reverseTransform($value) 49 | { 50 | return $value; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Form/Entity/Content.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class Content extends BoltContent 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function getTitle() 36 | { 37 | if (array_key_exists('title', $this->_fields)) { 38 | return $this->_fields['title']; 39 | } 40 | 41 | if (property_exists($this, 'title')) { 42 | return $this->title; 43 | } 44 | 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Form/ResolvedBoltForm.php: -------------------------------------------------------------------------------- 1 | . 27 | * 28 | * @author Gawain Lynch 29 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 30 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 31 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 32 | */ 33 | class ResolvedBoltForm 34 | { 35 | /** @var Form */ 36 | protected $form; 37 | /** @var FormConfig */ 38 | private $formConfig; 39 | /** @var MetaData */ 40 | protected $meta; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param FormConfig $formConfig 46 | * @param Form $form 47 | * @param MetaData $meta 48 | */ 49 | public function __construct(Form $form = null, FormConfig $formConfig = null, MetaData $meta = null) 50 | { 51 | $this->form = $form; 52 | $this->formConfig = $formConfig; 53 | $this->meta = $meta; 54 | } 55 | 56 | /** 57 | * @return Form 58 | */ 59 | public function getForm() 60 | { 61 | return $this->form; 62 | } 63 | 64 | /** 65 | * @param Form $form 66 | * 67 | * @return ResolvedBoltForm 68 | */ 69 | public function setForm($form) 70 | { 71 | $this->form = $form; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * @return MetaData 78 | */ 79 | public function getMeta() 80 | { 81 | return $this->meta; 82 | } 83 | 84 | /** 85 | * @param MetaData|array $meta 86 | * 87 | * @return ResolvedBoltForm 88 | */ 89 | public function setMeta($meta) 90 | { 91 | if ($this->form === null) { 92 | throw new Exception\UnknownFormException('Form not created'); 93 | } 94 | if ($meta instanceof MetaData) { 95 | $meta = $meta->all(); 96 | } 97 | $this->meta->replace((array) $meta); 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * @return FormConfig 104 | */ 105 | public function getFormConfig() 106 | { 107 | return $this->formConfig; 108 | } 109 | 110 | /** 111 | * @param FormConfig $formConfig 112 | * 113 | * @return ResolvedBoltForm 114 | */ 115 | public function setFormConfig($formConfig) 116 | { 117 | $this->formConfig = $formConfig; 118 | 119 | return $this; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Form/Type/BoltFormType.php: -------------------------------------------------------------------------------- 1 | . 29 | * 30 | * @author Gawain Lynch 31 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 32 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 33 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 34 | */ 35 | class BoltFormType extends AbstractType 36 | { 37 | /** @var EventDispatcherInterface */ 38 | protected $dispatcher; 39 | /** @var Config */ 40 | protected $config; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param EventDispatcherInterface $dispatcher 46 | * @param Config $config 47 | */ 48 | public function __construct(EventDispatcherInterface $dispatcher, Config $config) 49 | { 50 | $this->dispatcher = $dispatcher; 51 | $this->config = $config; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function buildForm(FormBuilderInterface $builder, array $options) 58 | { 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function configureOptions(OptionsResolver $resolver) 65 | { 66 | $resolver->setDefaults([ 67 | 'data_class' => Entity\Content::class, 68 | 'csrf_protection' => $this->config->isCsrf(), 69 | ]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Provider/RecaptchaServiceProvider.php: -------------------------------------------------------------------------------- 1 | . 28 | * 29 | * @author Gawain Lynch 30 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 31 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 32 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 33 | */ 34 | class RecaptchaServiceProvider implements ServiceProviderInterface 35 | { 36 | public function register(Application $app) 37 | { 38 | $app['recaptcha'] = $app->share( 39 | function ($app) { 40 | /** @var BoltFormsExtension $extension */ 41 | $extension = $app['extensions']->get('Bolt/BoltForms'); 42 | $key = $extension->getConfig()['recaptcha']['private_key']; 43 | $reCaptcha = new ReCaptcha($key); 44 | 45 | return $reCaptcha; 46 | } 47 | ); 48 | 49 | $app['recapture.response.factory'] = $app->protect( 50 | function ($enabled = true) use ($app) { 51 | $request = $app['request_stack']->getCurrentRequest(); 52 | $config = $app['boltforms.config']; 53 | 54 | // Check reCaptcha, if enabled. If not just return true 55 | if (!$request->isMethod(Request::METHOD_POST) || !$config->getReCaptcha()->isEnabled() || $enabled === false) { 56 | return [ 57 | 'success' => true, 58 | 'errorCodes' => null, 59 | ]; 60 | } 61 | 62 | /** @var \ReCaptcha\ReCaptcha $reCaptcha */ 63 | $reCaptcha = $app['recaptcha']; 64 | $reCaptchaResponse = $reCaptcha->verify($request->get('g-recaptcha-response'), $request->getClientIp()); 65 | 66 | return [ 67 | 'success' => $reCaptchaResponse->isSuccess(), 68 | 'errorCodes' => $reCaptchaResponse->getErrorCodes(), 69 | ]; 70 | } 71 | ); 72 | } 73 | 74 | public function boot(Application $app) 75 | { 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Submission/FeedbackTrait.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | trait FeedbackTrait 33 | { 34 | /** 35 | * Log a feedback message, at the required level per-target. 36 | * 37 | * @param string $message 38 | * @param string $feedbackLogLevel 39 | * @param string $systemLogLevel 40 | */ 41 | protected function message($message, $feedbackLogLevel = Processor::FEEDBACK_DEBUG, $systemLogLevel = LogLevel::DEBUG) 42 | { 43 | $this->getFeedback()->add($feedbackLogLevel, $message); 44 | $this->getLogger()->log($systemLogLevel, $message, ['event' => 'extensions']); 45 | } 46 | 47 | /** 48 | * Log and optionally re-throw an exception. 49 | * 50 | * @param \Exception $e 51 | * @param bool $rethrow 52 | * @param string $messagePrefix 53 | * 54 | * @throws \Exception 55 | */ 56 | protected function exception(\Exception $e, $rethrow = true, $messagePrefix = 'An exception has occurred during form processing:') 57 | { 58 | $message = sprintf('%s%s%s', $messagePrefix, "\n", $e->getMessage()); 59 | $this->getFeedback()->add('debug', $message); 60 | $this->getLogger()->critical('[BoltForms]: ' . $message, ['event' => 'exception', 'exception' => $e]); 61 | 62 | if ($rethrow) { 63 | throw $e; 64 | } 65 | } 66 | 67 | /** 68 | * Return the BoltForms feedback service. 69 | * 70 | * @return FlashBag 71 | */ 72 | abstract protected function getFeedback(); 73 | 74 | /** 75 | * Return the Bolt system logger service. 76 | * 77 | * @return LoggerInterface 78 | */ 79 | abstract protected function getLogger(); 80 | } 81 | -------------------------------------------------------------------------------- /src/Submission/File.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class File extends HttpFile implements ArrayAccess, JsonSerializable 33 | { 34 | /** @var string */ 35 | private $baseDir; 36 | 37 | /** 38 | * Constructor. 39 | * 40 | * @param string $path 41 | * @param bool $checkPath 42 | * @param string $baseDir 43 | */ 44 | public function __construct($path, $checkPath = true, $baseDir = '') 45 | { 46 | parent::__construct($path, $checkPath); 47 | $this->baseDir = $baseDir; 48 | } 49 | 50 | /** 51 | * Return the relative path to the file. 52 | * 53 | * @return string 54 | */ 55 | public function getRelativePath() 56 | { 57 | return ltrim(str_replace($this->baseDir, '', $this->getPathname()), '/'); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function jsonSerialize() 64 | { 65 | return [ 66 | 'file' => $this->getRelativePath(), // images 67 | 'filename' => $this->getRelativePath(), // image lists 68 | 'title' => $this->getFilename(), 69 | 'basepath' => $this->baseDir, 70 | ]; 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function offsetExists($offset) 77 | { 78 | return in_array($offset, ['file', 'filename', 'title', 'basepath']); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function offsetGet($offset) 85 | { 86 | if ($offset === 'file') { 87 | return $this->getRelativePath(); 88 | } elseif ($offset === 'filename') { 89 | return $this->getRelativePath(); 90 | } elseif ($offset === 'title') { 91 | return $this->getFilename(); 92 | } elseif ($offset === 'basepath') { 93 | return $this->baseDir; 94 | }; 95 | 96 | throw new \BadMethodCallException(sprintf('Property "%s" does not exists in %s.', $offset, __CLASS__)); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function offsetSet($offset, $value) 103 | { 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function offsetUnset($offset) 110 | { 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Submission/Handler/AbstractHandler.php: -------------------------------------------------------------------------------- 1 | . 30 | * 31 | * @author Gawain Lynch 32 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 33 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 34 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 35 | */ 36 | abstract class AbstractHandler 37 | { 38 | use FeedbackTrait; 39 | 40 | /** @var Config */ 41 | private $config; 42 | /** @var EntityManager */ 43 | private $entityManager; 44 | /** @var SessionInterface */ 45 | private $session; 46 | /** @var LoggerInterface */ 47 | private $logger; 48 | /** @var SwiftMailer */ 49 | private $mailer; 50 | 51 | /** 52 | * Constructor. 53 | * 54 | * @param Config $config 55 | * @param EntityManager $entityManager 56 | * @param SessionInterface $session 57 | * @param LoggerInterface $logger 58 | * @param SwiftMailer $mailer 59 | */ 60 | public function __construct( 61 | Config $config, 62 | EntityManager $entityManager, 63 | SessionInterface $session, 64 | LoggerInterface $logger, 65 | SwiftMailer $mailer 66 | ) { 67 | $this->config = $config; 68 | $this->entityManager = $entityManager; 69 | $this->session = $session; 70 | $this->logger = $logger; 71 | $this->mailer = $mailer; 72 | } 73 | 74 | /** 75 | * @return Config 76 | */ 77 | protected function getConfig() 78 | { 79 | return $this->config; 80 | } 81 | 82 | /** 83 | * @return EntityManager 84 | */ 85 | protected function getEntityManager() 86 | { 87 | return $this->entityManager; 88 | } 89 | 90 | /** 91 | * @return FlashBagInterface 92 | */ 93 | protected function getFeedback() 94 | { 95 | /** @var FlashBagInterface $feedback */ 96 | $feedback = $this->session->getBag('boltforms'); 97 | 98 | return $feedback; 99 | } 100 | 101 | /** 102 | * @return LoggerInterface 103 | */ 104 | protected function getLogger() 105 | { 106 | return $this->logger; 107 | } 108 | 109 | /** 110 | * @return SwiftMailer 111 | */ 112 | protected function getMailer() 113 | { 114 | return $this->mailer; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Submission/Handler/ContentType.php: -------------------------------------------------------------------------------- 1 | . 30 | * 31 | * @author Gawain Lynch 32 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 33 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 34 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 35 | */ 36 | class ContentType extends AbstractHandler 37 | { 38 | /** 39 | * Write out form data to a specified ContentType record. 40 | * 41 | * @param string $contentType 42 | * @param Entity\Entity $formData 43 | * @param MetaData $formMetaData 44 | * @param FieldMap $fieldMap 45 | * 46 | * @throws InternalProcessorException 47 | */ 48 | public function handle($contentType, Entity\Entity $formData, MetaData $formMetaData, FieldMap $fieldMap) 49 | { 50 | try { 51 | /** @var ContentRepository $repo */ 52 | $repo = $this->getEntityManager()->getRepository($contentType); 53 | } catch (StorageException $e) { 54 | throw new InternalProcessorException(sprintf('Invalid ContentType name `%s` specified.', $contentType), $e->getCode(), $e, false); 55 | } 56 | 57 | // Get an empty record for our ContentType 58 | $record = $this->getRecord($repo, $formData); 59 | 60 | // Set a published date 61 | $record->setStatus('published'); 62 | if (!$formData->has('datepublish')) { 63 | $record->setDatepublish(Carbon::now()); 64 | } 65 | 66 | foreach ($formData->toArray() as $name => $data) { 67 | // Store the data array into the record 68 | if ($fieldMap->has($name) === false) { 69 | continue; 70 | } 71 | 72 | $fieldName = $fieldMap->get($name); 73 | 74 | // A Catch for uploaded files which present as a serializable instance 75 | if (is_subclass_of($data, 'JsonSerializable')) { 76 | /** @var \JsonSerializable $data */ 77 | $data = $data->jsonSerialize(); 78 | } 79 | 80 | $record->set($fieldName, $data); 81 | } 82 | 83 | // Add any meta values that are requested for 'database' use 84 | foreach ($formMetaData->getUsedMeta('database') as $key => $value) { 85 | $record->set($key, $value); 86 | } 87 | 88 | try { 89 | $repo->save($record); 90 | } catch (\Exception $e) { 91 | $message = sprintf('An exception occurred saving submission to ContentType table `%s`', $contentType); 92 | $this->exception($e, false, $message); 93 | 94 | throw new InternalProcessorException($e->getMessage(), $e->getCode(), $e, false); 95 | } 96 | } 97 | 98 | /** 99 | * Get an appropriate entity object. 100 | * 101 | * @param ContentRepository $repo 102 | * @param Entity\Entity $formData 103 | * 104 | * @return Entity\Content|object 105 | */ 106 | protected function getRecord(ContentRepository $repo, Entity\Entity $formData) 107 | { 108 | if ($formData->has('id') === false) { 109 | return $repo->getEntityBuilder()->getEntity(); 110 | } 111 | 112 | $record = $repo->find($formData->get('id')); 113 | if ($record === false) { 114 | return $repo->getEntityBuilder()->getEntity(); 115 | } 116 | 117 | return $record; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Submission/Handler/DatabaseTable.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class DatabaseTable extends AbstractHandler 33 | { 34 | /** 35 | * Write out form data to a specified database table row. 36 | * 37 | * @param string $tableName 38 | * @param Entity\Entity $formData 39 | * @param MetaData $formMetaData 40 | * 41 | * @throws InternalProcessorException 42 | */ 43 | public function handle($tableName, Entity\Entity $formData, MetaData $formMetaData) 44 | { 45 | $saveData = []; 46 | $connection = $this->getEntityManager()->getConnection(); 47 | 48 | // Don't try to write to a non-existant table 49 | $sm = $connection->getSchemaManager(); 50 | if (!$sm->tablesExist([$tableName])) { 51 | // log failed attempt 52 | throw new InternalProcessorException(sprintf('Failed attempt to save submission: missing database table `%s`', $tableName), 0, null, false); 53 | } 54 | 55 | // Build a new array with only keys that match the database table 56 | /** @var \Doctrine\DBAL\Schema\Column[] $columns */ 57 | $columns = $sm->listTableColumns($tableName); 58 | 59 | foreach ($columns as $column) { 60 | $colName = $column->getName(); 61 | // Only attempt to insert fields with existing data this way you can 62 | // have fields in your table that are not in the form eg. an auto 63 | // increment id field of a field to track status of a submission 64 | if (isset($formData[$colName])) { 65 | $data = $formData->get($colName, null, true); 66 | if (is_array($data)) { 67 | $data = implode(', ', $data); 68 | } 69 | $saveData[$colName] = $data; 70 | } 71 | 72 | // Add any meta values that are requested for 'database' use 73 | foreach ($formMetaData->getUsedMeta('database') as $key => $value) { 74 | if ($key === $colName) { 75 | $saveData[$colName] = $formMetaData->get($key)->getValue(); 76 | } 77 | } 78 | } 79 | 80 | try { 81 | $connection->insert($tableName, $saveData); 82 | } catch (\Exception $e) { 83 | throw new InternalProcessorException(sprintf('An exception occurred saving submission to database table `%s`', $tableName), $e->getCode(), $e, false); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Submission/Handler/PostRequest.php: -------------------------------------------------------------------------------- 1 | . 30 | * 31 | * @author Gawain Lynch 32 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 33 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 34 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 35 | */ 36 | class PostRequest 37 | { 38 | /** @var RequestStack */ 39 | private $requestStack; 40 | 41 | /** 42 | * @param RequestStack $requestStack 43 | */ 44 | public function __construct(RequestStack $requestStack) 45 | { 46 | $this->requestStack = $requestStack; 47 | } 48 | 49 | /** 50 | * Handle the request. Caller must test for POST. 51 | * 52 | * @param string $formName 53 | * @param BoltForms $boltForms 54 | * @param EventDispatcherInterface $dispatcher 55 | * 56 | * @return Entity\Entity|null 57 | */ 58 | public function handle($formName, BoltForms $boltForms, EventDispatcherInterface $dispatcher) 59 | { 60 | $request = $this->requestStack->getCurrentRequest(); 61 | if (!$request->request->has($formName)) { 62 | return null; 63 | } 64 | 65 | /** @var Form $form */ 66 | $form = $boltForms->get($formName)->getForm(); 67 | // Handle the Request object to check if the data sent is valid 68 | $form->handleRequest($request); 69 | 70 | // Test if form, as submitted, passes validation 71 | if (!$form->isValid()) { 72 | return null; 73 | } 74 | 75 | // Submitted data 76 | $data = $form->getData(); 77 | 78 | $event = new ProcessorEvent($formName, $data); 79 | $dispatcher->dispatch(BoltFormsEvents::SUBMISSION_PRE_PROCESSOR, $event); 80 | 81 | return $event->getData(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Submission/Processor/AbstractProcessor.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | abstract class AbstractProcessor implements ProcessorInterface 33 | { 34 | /** @var Container */ 35 | protected $handlers; 36 | /** @var array */ 37 | protected $messages; 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param Container $handlers 43 | */ 44 | public function __construct(Container $handlers) 45 | { 46 | $this->handlers = $handlers; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getMessages() 53 | { 54 | return (array) $this->messages; 55 | } 56 | 57 | /** 58 | * Log a feedback message, at the required level per-target. 59 | * 60 | * @param string $message 61 | * @param string $feedbackLogLevel 62 | * @param string $systemLogLevel 63 | */ 64 | protected function message($message, $feedbackLogLevel = Processor::FEEDBACK_DEBUG, $systemLogLevel = LogLevel::DEBUG) 65 | { 66 | $this->messages[] = [$message, $feedbackLogLevel, $systemLogLevel]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Submission/Processor/ContentType.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class ContentType extends AbstractProcessor 33 | { 34 | /** 35 | * Commit submitted data to the database if configured. 36 | * 37 | * {@inheritdoc} 38 | */ 39 | public function process(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher) 40 | { 41 | $formConfig = $lifeEvent->getFormConfig(); 42 | $formData = $lifeEvent->getFormData(); 43 | $formMeta = $lifeEvent->getFormMetaData(); 44 | 45 | // Write to a Contenttype 46 | if ($formConfig->getDatabase()->getContentType() !== null) { 47 | $fieldMap = $formConfig->getDatabase()->getContentTypeFieldMap(); 48 | /** @var Handler\ContentType $handler */ 49 | $handler = $this->handlers['content']; 50 | $handler->handle($formConfig->getDatabase()->getContentType(), $formData, $formMeta, $fieldMap); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Submission/Processor/DatabaseTable.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class DatabaseTable extends AbstractProcessor 33 | { 34 | /** 35 | * Commit submitted data to the database if configured. 36 | * 37 | * {@inheritdoc} 38 | */ 39 | public function process(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher) 40 | { 41 | $formConfig = $lifeEvent->getFormConfig(); 42 | $formData = $lifeEvent->getFormData(); 43 | $formMeta = $lifeEvent->getFormMetaData(); 44 | 45 | // Write to a normal database table 46 | if ($formConfig->getDatabase()->getTable() !== null) { 47 | /** @var Handler\DatabaseTable $handler */ 48 | $handler = $this->handlers['database']; 49 | $handler->handle($formConfig->getDatabase()->getTable(), $formData, $formMeta); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Submission/Processor/Email.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class Email extends AbstractProcessor 33 | { 34 | /** 35 | * Send email notifications if configured. 36 | * 37 | * {@inheritdoc} 38 | */ 39 | public function process(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher) 40 | { 41 | $formConfig = $lifeEvent->getFormConfig(); 42 | $formData = $lifeEvent->getFormData(); 43 | $formMeta = $lifeEvent->getFormMetaData(); 44 | 45 | if ($formConfig->getNotification()->isEnabled()) { 46 | /** @var Handler\Email $handler */ 47 | $handler = $this->handlers['email']; 48 | $handler->handle($formConfig, $formData, $formMeta); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Submission/Processor/Feedback.php: -------------------------------------------------------------------------------- 1 | . 29 | * 30 | * @author Gawain Lynch 31 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 32 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 33 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 34 | */ 35 | class Feedback extends AbstractProcessor 36 | { 37 | /** @var SessionInterface */ 38 | private $session; 39 | 40 | /** 41 | * Constructor. 42 | * 43 | * @param Container $handlers 44 | * @param SessionInterface $session 45 | */ 46 | public function __construct(Container $handlers, SessionInterface $session) 47 | { 48 | parent::__construct($handlers); 49 | $this->session = $session; 50 | } 51 | 52 | /** 53 | * Set feedback notices. 54 | * 55 | * {@inheritdoc} 56 | */ 57 | public function process(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher) 58 | { 59 | $formConfig = $lifeEvent->getFormConfig(); 60 | 61 | $this->message($formConfig->getFeedback()->getSuccessMessage(), Processor::FEEDBACK_INFO, LogLevel::DEBUG); 62 | $this->session->set(sprintf('boltforms_submit_%s', $formConfig->getName()), true); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Submission/Processor/Fields.php: -------------------------------------------------------------------------------- 1 | . 30 | * 31 | * @author Gawain Lynch 32 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 33 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 34 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 35 | */ 36 | class Fields extends AbstractProcessor 37 | { 38 | /** @var Config */ 39 | private $config; 40 | 41 | /** 42 | * Constructor. 43 | * 44 | * @param Container $handlers 45 | * @param Config $config 46 | */ 47 | public function __construct(Container $handlers, Config $config) 48 | { 49 | parent::__construct($handlers); 50 | $this->config = $config; 51 | } 52 | 53 | /** 54 | * Process the fields to get usable data. 55 | * 56 | * {@inheritdoc} 57 | * 58 | * @throws FileUploadException 59 | */ 60 | public function process(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher) 61 | { 62 | $formConfig = $lifeEvent->getFormConfig(); 63 | $formData = $lifeEvent->getFormData(); 64 | 65 | foreach ($formConfig->getFields() as $fieldName => $fieldConf) { 66 | /** @var \Bolt\Extension\Bolt\BoltForms\Config\Form\FieldBag $fieldConf */ 67 | if ($fieldConf === null) { 68 | continue; 69 | } 70 | 71 | /** @var \Bolt\Extension\Bolt\BoltForms\Config\Form\FieldOptionsBag $fieldOptions */ 72 | if ($fieldConf->has('event') === false) { 73 | continue; 74 | } 75 | 76 | // Handle events for custom data 77 | $eventConfig = $fieldConf->get('event'); 78 | if ($eventConfig->has('name')) { 79 | $data = $this->dispatchCustomDataEvent($dispatcher, $eventConfig); 80 | $formData->set($fieldName, $data); 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Dispatch custom data events. 87 | * 88 | * @param EventDispatcherInterface $dispatcher 89 | * @param FieldOptionsBag $eventConfig 90 | * 91 | * @return mixed|null 92 | */ 93 | protected function dispatchCustomDataEvent(EventDispatcherInterface $dispatcher, FieldOptionsBag $eventConfig) 94 | { 95 | if (strpos('boltforms.', $eventConfig->get('name')) === false) { 96 | $eventName = 'boltforms.' . $eventConfig->get('name'); 97 | } else { 98 | $eventName = $eventConfig->get('name'); 99 | } 100 | 101 | if (!$dispatcher->hasListeners($eventName)) { 102 | return null; 103 | } 104 | 105 | $eventParams = $eventConfig->get('params'); 106 | $event = new CustomDataEvent($eventName, $eventParams); 107 | $dispatcher->dispatch($eventName, $event); 108 | 109 | return $event->getData(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Submission/Processor/ProcessorInterface.php: -------------------------------------------------------------------------------- 1 | . 25 | * 26 | * @author Gawain Lynch 27 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 28 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 29 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 30 | */ 31 | interface ProcessorInterface 32 | { 33 | /** 34 | * Process a task. 35 | * 36 | * @param LifecycleEvent $lifeEvent 37 | * @param string $eventName 38 | * @param EventDispatcherInterface $dispatcher 39 | * 40 | * @return 41 | */ 42 | public function process(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher); 43 | 44 | /** 45 | * Get the stored messages. 46 | * 47 | * @return array 48 | */ 49 | public function getMessages(); 50 | } 51 | -------------------------------------------------------------------------------- /src/Submission/Processor/Redirect.php: -------------------------------------------------------------------------------- 1 | . 32 | * 33 | * @author Gawain Lynch 34 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 35 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 36 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 37 | */ 38 | class Redirect extends AbstractProcessor 39 | { 40 | /** @var RequestStack */ 41 | private $requestStack; 42 | /** @var SessionInterface */ 43 | private $session; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param Container $handlers 49 | * @param RequestStack $requestStack 50 | * @param SessionInterface $session 51 | */ 52 | public function __construct(Container $handlers, RequestStack $requestStack, SessionInterface $session) 53 | { 54 | parent::__construct($handlers); 55 | $this->requestStack = $requestStack; 56 | $this->session = $session; 57 | } 58 | 59 | /** 60 | * Redirect if a redirect is set and the page exists. 61 | * 62 | * {@inheritdoc} 63 | * 64 | * @throws HttpException 65 | */ 66 | public function process(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher) 67 | { 68 | $formConfig = $lifeEvent->getFormConfig(); 69 | $formData = $lifeEvent->getFormData(); 70 | 71 | // Save our session to persist though redirects 72 | $this->session->save(); 73 | 74 | if ($formConfig->getSubmission()->isAjax()) { 75 | return; 76 | } 77 | 78 | /** @var Handler\Redirect $handler */ 79 | $handler = $this->handlers['redirect']; 80 | if ($formConfig->getFeedback()->getRedirectTarget() !== null) { 81 | $handler->handle($formConfig, $formData); 82 | } 83 | 84 | // Do a get on the page as it was probably POSTed 85 | $request = $this->requestStack->getCurrentRequest(); 86 | 87 | $handler->refresh($request); 88 | 89 | // Just 'exit'-ing is bad practice, but in this case it prevents an oscure error. 90 | // @see https://github.com/bolt/boltforms/pull/284 for details 91 | exit; 92 | // throw new HttpException(Response::HTTP_FOUND, '', null, []); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Submission/Result.php: -------------------------------------------------------------------------------- 1 | . 24 | * 25 | * @author Gawain Lynch 26 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 27 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 28 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 29 | */ 30 | class Result 31 | { 32 | /** @var array */ 33 | protected $result; 34 | 35 | /** 36 | * Constructor. 37 | */ 38 | public function __construct() 39 | { 40 | $this->result = [ 41 | BoltFormsEvents::SUBMISSION_PRE_PROCESSOR => false, 42 | BoltFormsEvents::SUBMISSION_POST_PROCESSOR => false, 43 | BoltFormsEvents::SUBMISSION_PROCESS_FIELDS => false, 44 | BoltFormsEvents::SUBMISSION_PROCESS_UPLOADS => false, 45 | BoltFormsEvents::SUBMISSION_PROCESS_CONTENTTYPE => false, 46 | BoltFormsEvents::SUBMISSION_PROCESS_DATABASE => false, 47 | BoltFormsEvents::SUBMISSION_PROCESS_EMAIL => false, 48 | BoltFormsEvents::SUBMISSION_PROCESS_FEEDBACK => false, 49 | BoltFormsEvents::SUBMISSION_PROCESS_REDIRECT => false, 50 | ]; 51 | } 52 | 53 | /** 54 | * @param string $name 55 | * 56 | * @return bool 57 | */ 58 | public function isPass($name) 59 | { 60 | $name = 'boltforms.submission_process_' . $name; 61 | $this->assert($name); 62 | 63 | return (bool) $this->result[$name]; 64 | } 65 | 66 | /** 67 | * @param string $name 68 | */ 69 | public function passEvent($name) 70 | { 71 | $this->assert($name); 72 | 73 | $this->result[$name] = true; 74 | } 75 | 76 | /** 77 | * @param string $name 78 | */ 79 | public function failEvent($name) 80 | { 81 | $this->assert($name); 82 | 83 | $this->result[$name] = false; 84 | } 85 | 86 | /** 87 | * @param $name 88 | */ 89 | protected function assert($name) 90 | { 91 | if (isset($this->result[$name])) { 92 | return; 93 | } 94 | 95 | throw new \RuntimeException(sprintf('Attempted to access invalid result: %s', $name)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Subscriber/ProcessLifecycleSubscriber.php: -------------------------------------------------------------------------------- 1 | . 29 | * 30 | * @author Gawain Lynch 31 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 32 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 33 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 34 | */ 35 | class ProcessLifecycleSubscriber implements EventSubscriberInterface 36 | { 37 | /** @var Application */ 38 | private $app; 39 | 40 | /** 41 | * Constructor. 42 | * 43 | * @param Application $app 44 | */ 45 | public function __construct(Application $app) 46 | { 47 | $this->app = $app; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public static function getSubscribedEvents() 54 | { 55 | return [ 56 | BoltFormsEvents::SUBMISSION_PROCESS_FIELDS => ['onProcessLifecycleEvent', BoltFormsEvents::PRIORITY_INTERNAL], 57 | BoltFormsEvents::SUBMISSION_PROCESS_UPLOADS => ['onProcessLifecycleEvent', BoltFormsEvents::PRIORITY_INTERNAL], 58 | BoltFormsEvents::SUBMISSION_PROCESS_CONTENTTYPE => ['onProcessLifecycleEvent', BoltFormsEvents::PRIORITY_INTERNAL], 59 | BoltFormsEvents::SUBMISSION_PROCESS_DATABASE => ['onProcessLifecycleEvent', BoltFormsEvents::PRIORITY_INTERNAL], 60 | BoltFormsEvents::SUBMISSION_PROCESS_EMAIL => ['onProcessLifecycleEvent', BoltFormsEvents::PRIORITY_INTERNAL], 61 | BoltFormsEvents::SUBMISSION_PROCESS_FEEDBACK => ['onProcessLifecycleEvent', BoltFormsEvents::PRIORITY_INTERNAL], 62 | BoltFormsEvents::SUBMISSION_PROCESS_REDIRECT => ['onProcessLifecycleEvent', BoltFormsEvents::PRIORITY_INTERNAL], 63 | ]; 64 | } 65 | 66 | /** 67 | * Handle local processing of ProcessLifecycleEvents. 68 | * 69 | * @param LifecycleEvent $lifeEvent 70 | * @param string $eventName 71 | * @param EventDispatcherInterface $dispatcher 72 | */ 73 | public function onProcessLifecycleEvent(LifecycleEvent $lifeEvent, $eventName, EventDispatcherInterface $dispatcher) 74 | { 75 | $this->getProcessorManager()->runInternalProcessor($lifeEvent, $eventName, $dispatcher); 76 | } 77 | 78 | /** 79 | * @return Processor 80 | */ 81 | public function getProcessorManager() 82 | { 83 | return $this->app['boltforms.processor']; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Twig/Extension/BoltFormsExtension.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * @author Gawain Lynch 28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch 29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0 31 | */ 32 | class BoltFormsExtension extends Extension 33 | { 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function getFunctions() 38 | { 39 | $safe = ['is_safe' => ['html', 'is_safe_callback' => true]]; 40 | $env = ['needs_environment' => true]; 41 | 42 | return [ 43 | new Twig_SimpleFunction('boltforms', [BoltFormsRuntime::class, 'twigBoltForms'], $safe + $env), 44 | new Twig_SimpleFunction('boltforms_uploads', [BoltFormsRuntime::class, 'twigBoltFormsUploads']), 45 | ]; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function getTests() 52 | { 53 | return array( 54 | new Twig_SimpleTest('rootform', [BoltFormsRuntime::class, 'twigIsRootForm']), 55 | ); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/Twig/RuntimeLoader.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RuntimeLoader implements RuntimeLoaderInterface 16 | { 17 | /** @var Container */ 18 | private $container; 19 | /** @var array */ 20 | private $mapping; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param Container $container 26 | * @param array $mapping 27 | */ 28 | public function __construct(Container $container, array $mapping) 29 | { 30 | $this->container = $container; 31 | $this->mapping = $mapping; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function load($class) 38 | { 39 | if (!isset($this->mapping[$class])) { 40 | return null; 41 | } 42 | 43 | return $this->container[$this->mapping[$class]]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | BoltForms Templates 2 | =================== 3 | 4 | In the subdirectories here, you will find the following groups of template 5 | defaults: 6 | 7 | * `asset` — Web assets such as the AJAX handler and default CSS 8 | * `email` — Email notification body and subject line templates 9 | * `feedback` — General user messages, and debug feedback 10 | * `file` — Uploaded file browser 11 | * `form` — Form specific rendering and macro templates 12 | -------------------------------------------------------------------------------- /templates/_macros.twig: -------------------------------------------------------------------------------- 1 | {# Render a single line label #} 2 | {% macro label(label) %} 3 | {{ label }}: 4 | {% endmacro %} 5 | 6 | {# Render a list item value #} 7 | {% macro value(value) %} 8 |
  • {{ value }}
  • 9 | {% endmacro %} 10 | 11 | {# Render a label/value pair #} 12 | {% macro label_value(label, value) %} 13 |

    {{ label }}: {{ value }}

    14 | {% endmacro %} 15 | 16 | {% macro label_array(label, value) %} 17 | {{ label }}: 18 |
      19 | {% for val in value %} 20 | {{ _self.value(val) }} 21 | {% endfor %} 22 |
    23 | {% endmacro %} 24 | -------------------------------------------------------------------------------- /templates/asset/_ajax.twig: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /templates/asset/_css.twig: -------------------------------------------------------------------------------- 1 | {# 2 | # Passed in variables: 3 | # * webpath — BoltForms web asset path 4 | #} 5 | 6 | -------------------------------------------------------------------------------- /templates/asset/_js.twig: -------------------------------------------------------------------------------- 1 | {# 2 | # Passed in variables: 3 | # * webpath — BoltForms web asset path 4 | #} 5 | 6 | -------------------------------------------------------------------------------- /templates/email/_blocks.twig: -------------------------------------------------------------------------------- 1 | {% block submission_summary %} 2 | {%- for fieldname, values in data %} 3 | {%- set field = attribute(fields, fieldname) %} 4 | {%- set label = field.options.label|default(fieldname) %} 5 | 6 |
      7 | 8 | {%- if values is iterable %} 9 |
    • 10 | {{ block('field_label') }} 11 | {{ block('value_array') }} 12 |
    • 13 | {% elseif values.timestamp is defined %} 14 | {%- set value = values %} 15 |
    • {{ block('field_label') }}{{ block('field_date') }}
    • 16 | {%- else %} 17 | {%- set value = values %} 18 |
    • {{ block('field_label') }}{{ block('field_value') }}
    • 19 | 20 | {%- endif %} 21 | 22 |
    23 | 24 | {%- endfor %} 25 | {% endblock %} 26 | 27 | {% block field_label %} 28 | {%- if label is not empty %}{{ label }}: {% endif %} 29 | {% endblock %} 30 | 31 | {% block field_value %} 32 | {{- value }} 33 | {% endblock %} 34 | 35 | {% block field_date %} 36 | {{- value|date() }} 37 | {% endblock %} 38 | 39 | {% block file_field_value %} 40 | {% if value == '' %} 41 | {{ name }} 42 | {% else %} 43 | {{ name }} 44 | {% endif %} 45 | {% endblock %} 46 | 47 | {% block value_array %} 48 |
      49 | {%- for name, value in values %} 50 | {%- if value is iterable %} 51 | {{ block('value_array') }} 52 | {%- else %} 53 | 54 |
    • 55 | {%- if field.type == 'file' %} 56 | {{ block('file_field_value') }} 57 | {%- else %} 58 | {{ block('field_value') }} 59 | {%- endif %} 60 |
    • 61 | 62 | {%- endif %} 63 | {%- endfor %} 64 | 65 |
    66 | {% endblock %} 67 | -------------------------------------------------------------------------------- /templates/email/email.twig: -------------------------------------------------------------------------------- 1 | {# 2 | # Passed in variables: 3 | # fields — Field data from the form configuration 4 | # config — Email configuration data 5 | # data — POSTed data 6 | # metadata — Form meta data 7 | #} 8 |

    Hi,

    9 | 10 |

    Somebody used the form on {{ paths.currenturl }} to send you a message.

    11 | 12 |

    The posted data is as follows:

    13 |
    14 | {{ block('submission_summary', '@BoltForms/email/_blocks.twig') }} 15 |
    16 | Sent by the BoltForms extension for Bolt. 17 | -------------------------------------------------------------------------------- /templates/email/subject.twig: -------------------------------------------------------------------------------- 1 | {# 2 | # Passed in variables: 3 | # subject — The configured subject 4 | # data — Posted data 5 | # metadata — Form meta data 6 | #} 7 | {% spaceless %} 8 | {{ subject }} 9 | {% endspaceless %} 10 | -------------------------------------------------------------------------------- /templates/feedback/_exception.twig: -------------------------------------------------------------------------------- 1 | {# 2 | #} 3 | {% block boltforms_css %} 4 | {{ include(templates.css) }} 5 | {% endblock boltforms_css %} 6 | 7 |
    8 | {{ include(templates.messages) }} 9 |
    10 | -------------------------------------------------------------------------------- /templates/feedback/_messages.twig: -------------------------------------------------------------------------------- 1 | {# 2 | # Informational message blocks 3 | #} 4 | {% block messages_debug %} 5 | {% if debug %} 6 |

    [Debug] Notification debug mode enabled!

    7 | 8 | {% if messages.debug and app.request.get(formname) %} 9 | {% for debug in messages.debug %} 10 |

    [Debug] {{ debug|nl2br }}

    11 | {% endfor %} 12 | {% endif %} 13 | {% endif %} 14 | {% endblock %} 15 | 16 | {% block messages_error %} 17 | {% if messages.error and app.request.get(formname) %} 18 | {% for error in messages.error %} 19 |

    {{ error }}

    20 | {% endfor %} 21 | {% endif %} 22 | {% endblock %} 23 | 24 | {% block messages_info %} 25 | {% if messages.info and app.request.get(formname) %} 26 | {% for info in messages.info %} 27 |

    {{ info }}

    28 | {% endfor %} 29 | {% endif %} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /templates/file/browser.twig: -------------------------------------------------------------------------------- 1 | {# 2 | # Passed in variables: 3 | # webpath - URI for the default web assets 4 | # base_uri - Relative base URI for BoltForms download controller 5 | # directories - Symfony SplFileInfo object of found directories 6 | # files - Symfony SplFileInfo object of found files 7 | #} 8 | 9 | {% block boltforms_css %} 10 | 11 | {% endblock boltforms_css %} 12 | 13 |
    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for file in files %} 24 | 25 | 26 | 27 | 28 | 29 | {% endfor %} 30 | 31 |
    File NameSizeDate
    {{ file.getRelativePathname }}{{ file.getSize|number_format }}{{ file.getMTime|date("c") }}
    32 |
    33 | -------------------------------------------------------------------------------- /templates/form/_recaptcha.twig: -------------------------------------------------------------------------------- 1 | 2 | {% macro recaptcha(recaptcha) %} 3 | {% if recaptcha.enabled %} 4 | {% if not recaptcha.valid %} 5 |
      6 |
    • {{ recaptcha.error_message }}
    • 7 |
    8 | {% endif %} 9 | 10 | {% if recaptcha.type == 'v2' %} 11 |
    12 | 13 | 14 | 15 |
    16 |
    17 | {% endif %} 18 | {% endif %} 19 | {% endmacro %} 20 | -------------------------------------------------------------------------------- /tests/data/bolt-logo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bolt/boltforms/de80a4f5549fe642309149b83e819d5796d683ba/tests/data/bolt-logo -------------------------------------------------------------------------------- /tests/data/bolt-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bolt/boltforms/de80a4f5549fe642309149b83e819d5796d683ba/tests/data/bolt-logo.png -------------------------------------------------------------------------------- /web/boltforms.css: -------------------------------------------------------------------------------- 1 | div.boltforms-row { 2 | margin: 0.75em 0; 3 | } 4 | 5 | .boltform label.required:after { 6 | content: " *"; 7 | color: #F00; 8 | font-weight: bold; 9 | } 10 | 11 | p.boltform-debug, p.boltform-error, li.boltform-error, p.boltform-info, p.boltform-message { 12 | padding: 8px 35px 8px 14px; 13 | margin-bottom: 20px; 14 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 15 | -webkit-border-radius: 4px; 16 | -moz-border-radius: 4px; 17 | border-radius: 4px; 18 | } 19 | 20 | p.boltform-debug { 21 | border: 1px solid #EED3D7; 22 | color: #B94A48; 23 | background-color: #F2DEDE; 24 | } 25 | 26 | p.boltform-error { 27 | border: 1px solid #EED3D7; 28 | color: #B94A48; 29 | background-color: #F2DEDE; 30 | } 31 | 32 | ul.boltform-error { 33 | margin-left: 0; 34 | padding-left: 0; 35 | } 36 | 37 | li.boltform-error { 38 | list-style: none; 39 | border: 1px solid #EED3D7; 40 | color: #B94A48; 41 | background-color: #F2DEDE; 42 | } 43 | 44 | p.boltform-message { 45 | border: 1px solid #D6E9C6; 46 | color: #468847; 47 | background-color: #DFF0D8; 48 | } 49 | 50 | .boltforms-preview-image { 51 | display: inline-block; 52 | } 53 | -------------------------------------------------------------------------------- /web/boltforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Closure to handled image specific uploads 3 | * @param file 4 | * @param preview 5 | */ 6 | var $handleImage = function (file, preview) { 7 | var reader = new FileReader(); 8 | reader.onload = function (e) { 9 | var img = new Image(); 10 | img.src = e.target.result; 11 | img.onload = function () { 12 | var canvas = document.createElement('canvas'); 13 | var ctx = canvas.getContext('2d'); 14 | 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | img.width = 128; 17 | img.height = 128; 18 | canvas.width = img.width; 19 | canvas.height = img.height; 20 | ctx.drawImage(img, 0, 0, img.width, img.height); 21 | 22 | var upload = document.createElement('div'); 23 | upload.className = 'boltforms-preview-image'; 24 | upload.appendChild(img); 25 | 26 | preview.appendChild(upload); 27 | } 28 | }; 29 | reader.readAsDataURL(file); 30 | }; 31 | 32 | /** 33 | * Handler for file uploads. 34 | * 35 | * @param files 36 | * @param preview 37 | */ 38 | function handleFiles(files, preview) { 39 | preview = document.getElementById(preview); 40 | preview.innerHTML = ''; 41 | 42 | for (var i = 0; i < files.length; i++) { 43 | var file = files[i]; 44 | var imageType = /^image\//; 45 | 46 | if (imageType.test(file.type)) { 47 | $handleImage(file, preview); 48 | } else { 49 | console.debug(file.type); 50 | } 51 | } 52 | } 53 | 54 | 55 | /** 56 | * Polyfill for browsers that don't support element.closest() 57 | * We need to use this to find the relevant form for a recaptcha-protected submit button. 58 | * 59 | */ 60 | 61 | if (window.Element && !Element.prototype.closest) { 62 | Element.prototype.closest = 63 | function(s) { 64 | var matches = (this.document || this.ownerDocument).querySelectorAll(s), 65 | i, 66 | el = this; 67 | do { 68 | i = matches.length; 69 | while (--i >= 0 && matches.item(i) !== el) {}; 70 | } while ((i < 0) && (el = el.parentElement)); 71 | return el; 72 | }; 73 | } 74 | 75 | function invisibleRecaptchaOnLoad() { 76 | 77 | 78 | function createHiddenElement(name, value) { 79 | var input = document.createElement("input"); 80 | input.setAttribute("type", "hidden"); 81 | input.setAttribute("name", name); 82 | input.setAttribute("value", value); 83 | return input; 84 | } 85 | 86 | var els = document.getElementsByClassName('g-recaptcha-button'); 87 | for (var i = 0; i < els.length; ++i) { 88 | var buttonElement = els[i]; 89 | grecaptcha.render(buttonElement, { 90 | sitekey: buttonElement.getAttribute('data-sitekey'), 91 | size: 'invisible', 92 | callback: function(token) { 93 | if (token) { 94 | buttonElement.closest('form').appendChild(createHiddenElement('g-recaptcha-response', token)); 95 | buttonElement.closest('form').submit(); 96 | } 97 | } 98 | }); 99 | } 100 | } 101 | 102 | 103 | --------------------------------------------------------------------------------