$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