PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.15
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.15
6.2.15 6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 All 197 releases
← All changes | app/Services/Integrations/Slack/Slack.php +90 -26 3.6.41 → 6.2.15 View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace FluentForm\App\Services\Integrations\Slack;
4 4
5 +use FluentForm\App\Helpers\Helper;
5 6 use FluentForm\App\Modules\Form\FormDataParser;
6 7 use FluentForm\App\Modules\Form\FormFieldsParser;
7 8 use FluentForm\App\Services\Integrations\LogResponseTrait;
8 9 use FluentForm\Framework\Helpers\ArrayHelper;
@@ -24,9 +25,9 @@
24 25 * @param $submissionId
25 26 * @param $formData
26 27 * @param $form
27 28 */
28 - public function handle($feed, $formData, $form, $entry)
29 + public static function handle($feed, $formData, $form, $entry)
29 30 {
30 31 $settings = $feed['processedValues'];
31 32
32 33 $inputs = FormFieldsParser::getEntryInputs($form);
@@ -32,35 +33,65 @@
32 33 $inputs = FormFieldsParser::getEntryInputs($form);
33 34
34 35 $labels = FormFieldsParser::getAdminLabels($form, $inputs);
35 36
36 - $formData = FormDataParser::parseData((object)$formData, $inputs, $form->id);
37 -
37 + $labels = apply_filters_deprecated(
38 + 'fluentform_slack_field_label_selection',
39 + [
40 + $labels,
41 + $settings,
42 + $form,
43 + ],
44 + FLUENTFORM_FRAMEWORK_UPGRADE,
45 + 'fluentform/slack_field_label_selection',
46 + 'Use fluentform/slack_field_label_selection instead of fluentform_slack_field_label_selection.'
47 + );
48 +
49 + $labels = apply_filters('fluentform/slack_field_label_selection', $labels, $settings, $form);
50 +
51 + foreach ($inputs as $name => $input) {
52 + if (empty($formData[$name])) {
53 + continue;
54 + }
55 + if ('tabular_grid' == ArrayHelper::get($input, 'element', '')) {
56 + $formData[$name] = Helper::getTabularGridFormatValue($formData[$name], $input, '<br />', ', ', 'markdown');
57 + }
58 + }
59 + $formData = FormDataParser::parseData((object) $formData, $inputs, $form->id);
60 +
38 61 $slackTitle = ArrayHelper::get($settings, 'textTitle');
39 -
40 - if($slackTitle === '') {
41 - $title = "New submission on " . $form->title;
42 - }else {
62 +
63 + if ('' === $slackTitle) {
64 + $title = 'New submission on ' . $form->title;
65 + } else {
43 66 $title = $slackTitle;
44 67 }
45 68
69 + $footerText = ArrayHelper::get($settings, 'footerText');
70 + if ($footerText === '') {
71 + $footerText = 'fluentform';
72 + }
73 +
46 74 $fields = [];
47 75
48 76 foreach ($formData as $attribute => $value) {
77 + $value = str_replace('<br />', "\n", $value);
49 78 $value = str_replace('&', '&amp;', $value);
50 79 $value = str_replace('<', '&lt;', $value);
51 - $value = str_replace('>', "&gt;", $value);
52 -
80 + $value = str_replace('>', '&gt;', $value);
81 + if (! isset($labels[$attribute]) || empty($value)) {
82 + continue;
83 + }
53 84 $fields[] = [
54 85 'title' => $labels[$attribute],
55 86 'value' => $value,
56 - 'short' => false
87 + 'short' => false,
57 88 ];
58 89 }
59 -
60 90 $slackHook = ArrayHelper::get($settings, 'webhook');
61 91
62 - $titleLink = admin_url('admin.php?page=fluent_forms&form_id='
92 + $titleLink = admin_url(
93 + 'admin.php?page=fluent_forms&form_id='
63 94 . $form->id
64 95 . '&route=entries#/entries/'
65 96 . $entry->id
66 97 );
@@ -73,16 +104,49 @@
73 104 'fallback' => $title,
74 105 'title' => $title,
75 106 'title_link' => $titleLink,
76 107 'fields' => $fields,
77 - 'footer' => 'fluentform',
78 - 'ts' => round(microtime(true) * 1000)
79 - ]
80 - ]
81 - ])
108 + 'footer' => $footerText,
109 + 'ts' => round(microtime(true) * 1000),
110 + ],
111 + ],
112 + ]),
82 113 ];
83 114
84 - $result = wp_remote_post($slackHook, [
115 + // SECURITY (FINDING-15): the webhook URL is admin-supplied and was fetched with no egress
116 + // restriction — an SSRF with a logged status/error oracle (the response is written into the
117 + // feed log). wp_safe_remote_post (below) blocks private/loopback ranges; additionally pin
118 + // the host, since Slack incoming webhooks are always https://hooks.slack.com/... , so an
119 + // arbitrary external host cannot be used as the oracle target either.
120 + // COMPAT: filterable so a site using a Slack-compatible endpoint (Mattermost, Rocket.Chat,
121 + // an internal relay) can keep an existing feed working without patching. Site PHP only —
122 + // a form manager cannot reach it, so the default-deny posture is preserved.
123 + $defaultHookHosts = ['hooks.slack.com'];
124 + $allowedHookHosts = (array) apply_filters('fluentform/slack_allowed_webhook_hosts', $defaultHookHosts, $feed);
125 + $allowedHookHosts = array_map('strtolower', array_filter($allowedHookHosts, 'is_string'));
126 + // A filter returning nothing usable must not silently break every Slack feed — fall back
127 + // to the official host so the default keeps working no matter what the filter returns.
128 + if (!$allowedHookHosts) {
129 + $allowedHookHosts = $defaultHookHosts;
130 + }
131 +
132 + $parsedHook = wp_parse_url($slackHook);
133 + $hookHost = isset($parsedHook['host']) ? strtolower($parsedHook['host']) : '';
134 + $hookScheme = isset($parsedHook['scheme']) ? strtolower($parsedHook['scheme']) : '';
135 + if ('https' !== $hookScheme || !in_array($hookHost, $allowedHookHosts, true)) {
136 + $message = sprintf(
137 + // translators: %s is a comma-separated list of allowed webhook hosts.
138 + __('Invalid Slack webhook URL. It must be an https:// URL on: %s', 'fluentform'),
139 + implode(', ', $allowedHookHosts)
140 + );
141 + do_action('fluentform/integration_action_result', $feed, 'failed', $message);
142 + return [
143 + 'status' => 'failed',
144 + 'message' => $message,
145 + ];
146 + }
147 +
148 + $result = wp_safe_remote_post($slackHook, [
85 149 'method' => 'POST',
86 150 'timeout' => 30,
87 151 'redirection' => 5,
88 152 'httpversion' => '1.0',
@@ -87,9 +151,9 @@
87 151 'redirection' => 5,
88 152 'httpversion' => '1.0',
89 153 'headers' => [],
90 154 'body' => $body,
91 - 'cookies' => []
155 + 'cookies' => [],
92 156 ]);
93 157
94 158 if (is_wp_error($result)) {
95 159 $status = 'failed';
@@ -95,19 +159,19 @@
95 159 $status = 'failed';
96 160 $message = $result->get_error_message();
97 161 } else {
98 162 $message = $result['response'];
99 - $status = $result['response']['code'] == 200 ? 'success' : 'failed';
163 + $status = 200 == $result['response']['code'] ? 'success' : 'failed';
100 164 }
101 165
102 - if ($status == 'failed') {
103 - do_action('ff_integration_action_result', $feed, 'failed', $message);
166 + if ('failed' == $status) {
167 + do_action('fluentform/integration_action_result', $feed, 'failed', $message);
104 168 } else {
105 - do_action('ff_integration_action_result', $feed, 'success', 'Submission notification has been successfully delivered to slack channel');
169 + do_action('fluentform/integration_action_result', $feed, 'success', 'Submission notification has been successfully delivered to slack channel');
106 170 }
107 171
108 - return array(
172 + return [
109 173 'status' => $status,
110 - 'message' => $message
111 - );
174 + 'message' => $message,
175 + ];
112 176 }
113 177 }