$value) { self::sanitizeJsonNode($value); $node->{$key} = $value; } } elseif (is_string($node)) { $node = wp_kses_post($node); } } public static function exportForms($formIds) { $result = Form::with(['formMeta']) ->whereIn('id', $formIds) ->get(); $forms = []; foreach ($result as $item) { $form = json_decode($item); $formMetaFiltered = array_filter($form->form_meta, function ($item) { return ($item->meta_key !== '_total_views'); }); $form->metas = $formMetaFiltered; $form->form_fields = json_decode($form->form_fields); $forms[] = $form; } $fileName = 'fluentform-export-forms-' . count($forms) . '-' . date('d-m-Y') . '.json'; header('Content-disposition: attachment; filename=' . $fileName); header('Content-type: application/json'); echo json_encode(array_values($forms)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $forms is escaped before being passed in. die(); } /** * Build the notice shown when imported custom JS/CSS was skipped because the * importer lacks unfiltered_html. Returns an empty string when nothing was skipped. * * @param int $skippedForms * @param int $totalForms * @return string */ protected static function restrictedCodeNotice($skippedForms, $totalForms) { if (!$skippedForms) { return ''; } if ($totalForms < 2) { return __('Custom JS and CSS were not imported because your account cannot add custom code. Ask an administrator to add it.', 'fluentform'); } return sprintf( /* translators: 1: number of forms whose custom code was skipped, 2: total number of imported forms */ __('Custom JS and CSS were not imported for %1$d of %2$d forms because your account cannot add custom code. Ask an administrator to add it.', 'fluentform'), $skippedForms, $totalForms ); } /** * @param File $file The uploaded JSON file * @param bool $applyDefaultStyle Whether to apply default style settings to imported forms * @throws Exception */ public static function importForms($file, $applyDefaultStyle = false) { if ($file instanceof File) { $forms = \json_decode($file->getContents(), true); $insertedForms = []; $restrictedCodeForms = 0; if ($forms && is_array($forms)) { foreach ($forms as $formItem) { $formFields = json_encode([]); if ($fields = Arr::get($formItem, 'form', '')) { $formFields = json_encode($fields); } elseif ($fields = Arr::get($formItem, 'form_fields', '')) { $formFields = json_encode($fields); } else { throw new Exception(esc_html__('You have a faulty JSON file, please export the Fluent Forms again.', 'fluentform')); } // SECURITY (FINDING-07): the editor save path routes form_fields through // Updater::sanitizeFields (skipped only for unfiltered_html users), but import // stored them verbatim, so an importer without unfiltered_html could plant // stored XSS (e.g. a field label of ). Apply the same // recursive HTML sanitizer used for imported meta values unless the importer // may author raw HTML. if (!fluentformCanUnfilteredHTML()) { $decodedFields = json_decode($formFields, true); if (is_array($decodedFields)) { static::sanitizeJsonNode($decodedFields); $formFields = wp_json_encode($decodedFields) ?: $formFields; } } $formTitle = sanitize_text_field(Arr::get($formItem, 'title')); $form = [ 'title' => $formTitle ?: 'Blank Form', 'form_fields' => $formFields, 'status' => sanitize_text_field(Arr::get($formItem, 'status', 'published')), 'has_payment' => sanitize_text_field(Arr::get($formItem, 'has_payment', 0)), 'type' => sanitize_text_field(Arr::get($formItem, 'type', 'form')), 'created_by' => get_current_user_id(), ]; if (Arr::get($formItem, 'conditions')) { $form['conditions'] = Arr::get($formItem, 'conditions'); } if (isset($formItem['appearance_settings'])) { $form['appearance_settings'] = Arr::get($formItem, 'appearance_settings'); } $formId = Form::insertGetId($form); $insertedForms[$formId] = [ 'title' => $form['title'], 'edit_url' => admin_url('admin.php?page=fluent_forms&route=editor&form_id=' . $formId), ]; $skippedCustomCode = false; if (isset($formItem['metas'])) { foreach ($formItem['metas'] as $metaData) { $metaKey = sanitize_text_field(Arr::get($metaData, 'meta_key')); $metaValue = Arr::get($metaData, 'value'); // SECURITY (FINDING-08): Customizer::store() refuses to save custom // JS/CSS without unfiltered_html; import must honor the same boundary. // Sanitizing _custom_form_js via fluentform_kses_js is insufficient // because the value is JS *code* executed inside a