esc_html__('Alpha Styles', 'alpha-addons-for-gravity-forms'),
'aria-label' => esc_html__('Alpha Styles', 'alpha-addons-for-gravity-forms'),
'url' => self::get_editor_url($form_id),
'target' => '_blank',
'menu_class' => 'gf_form_toolbar_alphagf_styles',
'capabilities' => array('gravityforms_edit_forms'),
'priority' => 700,
);
return $menu_items;
}
/**
* Adds "Styles" to the form list row actions, between Duplicate and Trash.
*/
public function add_form_list_action($form_actions, $form_id) {
$form_actions['alphagf_styles'] = array(
'label' => esc_html__('Styles', 'alpha-addons-for-gravity-forms'),
'aria-label' => esc_html__('Style this form', 'alpha-addons-for-gravity-forms'),
'url' => self::get_editor_url($form_id),
'target' => '_blank',
'menu_class' => 'gf_form_toolbar_alphagf_styles',
'capabilities' => array('gravityforms_edit_forms'),
'priority' => 550,
);
return $form_actions;
}
public static function get_editor_url($form_id) {
return add_query_arg(self::QUERY_VAR, absint($form_id), home_url('/'));
}
/**
* Render the full page styler (or its preview iframe) for ?alpha-styles-preview={form_id}.
*/
public function maybe_render_editor() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only screens, guarded by capability checks.
if (! isset($_GET[self::QUERY_VAR])) {
return;
}
$form_id = absint(wp_unslash($_GET[self::QUERY_VAR]));
$is_frame = ! empty($_GET[self::FRAME_VAR]);
// phpcs:enable WordPress.Security.NonceVerification.Recommended
// auth_redirect() validates the admin-only auth cookie, which isn't sent to frontend URLs.
if (! is_user_logged_in()) {
wp_safe_redirect(wp_login_url(self::get_editor_url($form_id)));
exit;
}
if (! GFCommon::current_user_can_any('gravityforms_edit_forms')) {
wp_die(esc_html__('Sorry, you are not allowed to style this form.', 'alpha-addons-for-gravity-forms'), '', array('response' => 403));
}
$form = $form_id ? GFAPI::get_form($form_id) : false;
if (! $form) {
wp_die(esc_html__('The requested form could not be found.', 'alpha-addons-for-gravity-forms'), '', array('response' => 404));
}
if (! defined('DONOTCACHEPAGE')) {
define('DONOTCACHEPAGE', true); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Standard constant read by caching plugins.
}
nocache_headers();
add_filter('show_admin_bar', '__return_false');
if ($is_frame) {
$this->render_frame($form);
} else {
$this->render_editor($form);
}
exit;
}
private function render_editor($form) {
$form_id = absint($form['id']);
$saved = rgar($form, self::META_KEY);
$forms = array();
foreach (GFFormsModel::get_forms(null, 'title') as $item) {
$forms[] = array(
'id' => absint($item->id),
'title' => $item->title,
);
}
$data = array(
'formId' => $form_id,
'formTitle' => $form['title'],
'forms' => $forms,
'values' => (object) self::strip_desktop_layouts(is_array($saved) && is_array(rgar($saved, 'values')) ? $saved['values'] : array()),
'themes' => $this->get_themes(),
'presets' => self::get_presets(),
'schema' => self::get_schema(),
'fonts' => self::get_fonts(),
/**
* Filter which form features the editor shows sections for (a section's 'requires' key).
*
* @param array $features
* @param array $form
*/
'features' => apply_filters('alphagf_styles_features', array(
'steps' => self::form_has_steps($form),
'progressbar' => self::form_has_steps($form, 'percentage'),
'list' => self::form_has_list($form),
'repeater' => self::form_has_repeater($form),
'imagepicker' => self::form_has_image_picker($form),
'qrcode' => self::form_has_qrcode($form),
'emailverify' => self::form_has_email_verification($form),
), $form),
'choiceFields' => self::get_choice_fields($form),
'imagePickerFields' => self::get_image_picker_fields($form),
'imagePickerOptions' => self::get_image_picker_options(),
'frameUrl' => add_query_arg(array(self::QUERY_VAR => $form_id, self::FRAME_VAR => 1), home_url('/')),
'editorUrl' => self::get_editor_url(0),
'backUrl' => admin_url('admin.php?page=gf_edit_forms&id=' . $form_id),
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce(self::NONCE_ACTION),
'i18n' => self::get_i18n(),
);
/**
* Filter the data passed to the editor script (window.AlphaStylesData).
*
* Extensions can add 'icons' (name => SVG paths) and 'partTargets' (preview part => control key).
*
* @param array $data
* @param array $form
*/
$data = apply_filters('alphagf_styles_editor_data', $data, $form);
/**
* Filter extra script URLs loaded in the editor before js/editor.js.
*
* @param string[] $scripts
* @param array $form
*/
$extra_scripts = (array) apply_filters('alphagf_styles_editor_scripts', array(), $form);
$assets_url = $this->get_base_url();
wp_register_style('alphagf-styles-editor-font', 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap', array(), null); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Google Fonts URL.
wp_register_style('alphagf-styles-editor', $assets_url . '/css/editor.css', array('alphagf-styles-editor-font'), $this->_version);
// Data handle with no source: prints window.AlphaStylesData ahead of the extra scripts and editor.js.
wp_register_script('alphagf-styles-editor-data', false, array(), $this->_version, false);
wp_add_inline_script('alphagf-styles-editor-data', 'window.AlphaStylesData = ' . wp_json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ';');
$editor_deps = array('alphagf-styles-editor-data');
foreach (array_values($extra_scripts) as $index => $script_url) {
$handle = 'alphagf-styles-editor-extra-' . $index;
wp_register_script($handle, $script_url, array('alphagf-styles-editor-data'), null, false); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party URLs carry their own version.
$editor_deps[] = $handle;
}
wp_register_script('alphagf-styles-editor', $assets_url . '/js/editor.js', $editor_deps, $this->_version, false);
include __DIR__ . '/views/editor.php';
}
/**
* Whether the form is multi-page and shows the given progress indicator ("steps" or "percentage").
*/
private static function form_has_steps($form, $indicator = 'steps') {
if ($indicator !== rgars($form, 'pagination/type')) {
return false;
}
foreach ((array) rgar($form, 'fields') as $field) {
if ('page' === rgobj($field, 'type')) {
return true;
}
}
return false;
}
/**
* Whether the form has a List field (List Field Pro styles it through the "List field" section).
*/
private static function form_has_list($form) {
foreach ((array) rgar($form, 'fields') as $field) {
if (is_object($field) && 'list' === $field->get_input_type()) {
return true;
}
}
return false;
}
/**
* Whether the form has a Repeater field.
*/
private static function form_has_repeater($form) {
foreach ((array) rgar($form, 'fields') as $field) {
if (is_object($field) && 'repeater' === $field->type) {
return true;
}
}
return false;
}
/**
* Whether Image Picker Pro is active and the form has a field with it enabled.
*/
private static function form_has_image_picker($form) {
if (! class_exists('ImagePickerAddon') || ! defined('IMAGE_PICKER_PRO_ADDON_VERSION')) {
return false;
}
$addon = ImagePickerAddon::get_instance();
return method_exists($addon, 'form_contains_image_picker_fields') && $addon->form_contains_image_picker_fields($form);
}
/**
* Whether QR Code Pro is active and the form has a QR Code field or a field with the QR scanner on.
*/
private static function form_has_qrcode($form) {
if (! class_exists('ALPHAGF_QRCode')) {
return false;
}
foreach ((array) rgar($form, 'fields') as $field) {
if (is_object($field) && ('alphagf_qrcode' === $field->type || ! empty($field->alphagfQrScanner))) {
return true;
}
}
return false;
}
/**
* Whether Email Verification Pro is active and the form has an Email Verification field.
*/
private static function form_has_email_verification($form) {
if (! class_exists('EVPGF_Addon')) {
return false;
}
foreach ((array) rgar($form, 'fields') as $field) {
if (is_object($field) && 'evpgf_verification' === $field->type) {
return true;
}
}
return false;
}
private function render_frame($form) {
self::$is_frame = true;
gravity_form_enqueue_scripts($form['id'], false);
wp_enqueue_style('alphagf-styles-frame', $this->get_base_url() . '/css/frame.css', array(), $this->_version);
wp_enqueue_script('alphagf-styles-frame', $this->get_base_url() . '/js/frame.js', array(), $this->_version, true);
include __DIR__ . '/views/frame.php';
}
/**
* Prepend the form's saved styles to its markup on the frontend.
*/
public function print_form_styles($markup, $form) {
if (self::$is_frame || ! is_array($form)) {
return $markup;
}
$form_id = absint(rgar($form, 'id'));
$styles = rgar($form, self::META_KEY);
if (! $form_id || ! is_array($styles) || isset($this->printed[$form_id])) {
return $markup;
}
// Horizontal choice layouts come first so saved tablet/mobile overrides can replace them.
$css = self::get_horizontal_choice_css($form) . str_replace(
array('{{wrapper}}', '{{confirmation}}', '{{qrmodal}}'),
array('#gform_wrapper_' . $form_id, '#gform_confirmation_wrapper_' . $form_id, '.alphagf-qr-modal[data-form="' . $form_id . '"]'),
self::sanitize_css((string) rgar($styles, 'css'))
);
if ('' === $css) {
return $markup;
}
$this->printed[$form_id] = true;
$output = '';
$font_url = self::get_font_url(rgar($styles, 'font'));
if ($font_url) {
// Printed inline (not in the footer) so forms rendered over AJAX get their font too.
$handle = 'alphagf-styles-font-' . $form_id;
wp_register_style($handle, $font_url, array(), null); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Google Fonts URL; a version arg would break the query.
ob_start();
wp_print_styles($handle);
$output .= ob_get_clean();
}
$output .= ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Sanitized by sanitize_css().
return $output . $markup;
}
public function ajax_save_styles() {
$this->verify_ajax_request();
$form = GFAPI::get_form(absint(rgpost('form_id')));
if (! $form) {
wp_send_json_error(array('message' => esc_html__('The form could not be found.', 'alpha-addons-for-gravity-forms')), 404);
}
$values = self::sanitize_values(json_decode((string) rgpost('values'), true));
$font = sanitize_key((string) rgpost('font'));
$css = self::sanitize_css((string) rgpost('css'));
// Desktop choice layouts belong to the fields themselves (same settings as the form editor).
$values = self::apply_choice_layouts($form, $values);
$values = self::apply_image_picker_settings($form, $values);
if (empty($values) && '' === $css && ! self::has_horizontal_choices($form)) {
unset($form[self::META_KEY]);
} else {
$form[self::META_KEY] = array(
'values' => $values,
'css' => $css,
'font' => array_key_exists($font, self::get_fonts()) ? $font : '',
'updated' => time(),
);
}
$result = GFAPI::update_form($form);
if (is_wp_error($result)) {
wp_send_json_error(array('message' => $result->get_error_message()), 500);
}
wp_send_json_success(array(
'message' => esc_html__('Styles saved.', 'alpha-addons-for-gravity-forms'),
'choiceFields' => self::get_choice_fields($form),
'imagePickerFields' => self::get_image_picker_fields($form),
));
}
/**
* Checkbox/radio fields that support Gravity Forms' "Choice Layout" setting.
*/
private static function get_choice_fields($form) {
$list = array();
foreach ((array) rgar($form, 'fields') as $field) {
if (! is_object($field) || ! in_array('display_choices_columns_setting', (array) $field->get_form_editor_field_settings(), true)) {
continue;
}
$input_type = $field->get_input_type();
if ('multi_choice' === $input_type || 'multi_choice' === $field->type) {
$input_type = 'single' === rgobj($field, 'choiceLimit') ? 'radio' : 'checkbox';
}
if (! in_array($input_type, array('checkbox', 'radio'), true)) {
continue;
}
if (method_exists($field, 'get_field_choice_alignment')) {
$layout = $field->get_field_choice_alignment($field);
} else {
$layout = rgobj($field, 'choiceAlignment') ? $field->choiceAlignment : (rgobj($field, 'enableDisplayInColumns') ? 'columns' : 'vertical');
}
$columns = absint(rgobj($field, 'displayColumns'));
$label = wp_strip_all_tags(GFCommon::get_label($field));
$list[] = array(
'id' => absint($field->id),
'type' => $input_type,
/* translators: %d: field ID. */
'label' => '' !== trim($label) ? $label : sprintf(__('Field #%d', 'alpha-addons-for-gravity-forms'), $field->id),
'layout' => in_array($layout, array('vertical', 'horizontal', 'columns'), true) ? $layout : 'vertical',
'columns' => (string) ($columns >= 2 && $columns <= 5 ? $columns : 2),
'flow' => 'vertical' === rgobj($field, 'displayAlignment') ? 'down' : 'across',
);
}
return $list;
}
/**
* Write "field.{id}.layout|columns|flow" values into the fields' Gravity Forms settings and
* return the values without them. Tablet/mobile overrides ("…@tablet") stay as styles.
*/
private static function apply_choice_layouts(&$form, $values) {
$changes = array();
foreach ($values as $key => $value) {
if (preg_match('/^field\.(\d+)\.(layout|columns|flow)$/', $key, $match)) {
$changes[absint($match[1])][$match[2]] = $value;
unset($values[$key]);
}
}
if (empty($changes)) {
return $values;
}
foreach ((array) rgar($form, 'fields') as $field) {
$id = absint($field->id);
if (! isset($changes[$id])) {
continue;
}
$change = $changes[$id];
if (isset($change['layout']) && in_array($change['layout'], array('vertical', 'horizontal', 'columns'), true)) {
$field->choiceAlignment = $change['layout'];
}
if (isset($change['columns'])) {
$columns = absint($change['columns']);
if ($columns >= 2 && $columns <= 5) {
$field->displayColumns = $columns;
}
}
if (isset($change['flow'])) {
$field->displayAlignment = 'down' === $change['flow'] ? 'vertical' : 'horizontal';
}
if ('columns' === rgobj($field, 'choiceAlignment') && absint(rgobj($field, 'displayColumns')) < 2) {
$field->displayColumns = 2;
}
}
return $values;
}
/**
* Image Picker Pro field settings the editor can change, per kind of field:
* "imgpf.{id}.{option}" => Gravity Forms field property.
*/
private static function image_picker_props($kind) {
$props = array(
'choice' => array(
'theme' => 'imagePicker_theme',
'imageStyle' => 'imagePicker_image_style',
'alignment' => 'imagePicker_alignment',
'columns' => 'imagePicker_column_large',
'columnsTablet' => 'imagePicker_column_medium',
'columnsMobile' => 'imagePicker_column_small',
'width' => 'imagePicker_column_large_fixed',
'widthTablet' => 'imagePicker_column_medium_fixed',
'widthMobile' => 'imagePicker_column_small_fixed',
'height' => 'imagePicker_height_large',
'heightTablet' => 'imagePicker_height_medium',
'heightMobile' => 'imagePicker_height_small',
),
'product' => array(
'theme' => 'imagePicker_product_theme',
'imageStyle' => 'imagePicker_product_image_style',
'height' => 'imagePicker_product_height',
'heightTablet' => 'imagePicker_product_height_medium',
'heightMobile' => 'imagePicker_product_height_small',
'cardWidth' => 'imagePicker_product_width',
'cardWidthTablet' => 'imagePicker_product_width_medium',
'cardWidthMobile' => 'imagePicker_product_width_small',
'quantityBtn' => 'imagePicker_product_quantity_btn',
),
'dropdown' => array(
'imageStyle' => 'imagePicker_dropdown_image_style',
),
);
return isset($props[$kind]) ? $props[$kind] : array();
}
/**
* Allowed values of the select-type options ("form_setting" falls back to the form/global setting).
*/
private static function get_image_picker_options() {
$columns = array();
foreach (range(1, 12) as $count) {
$columns[] = array('value' => (string) $count, 'label' => (string) $count);
}
$columns[] = array('value' => 'fixed', 'label' => __('Fixed width', 'alpha-addons-for-gravity-forms'));
$columns[] = array('value' => 'auto', 'label' => __('Auto', 'alpha-addons-for-gravity-forms'));
return array(
'themes' => array(
array('value' => 'simple', 'label' => __('Simple', 'alpha-addons-for-gravity-forms')),
array('value' => 'card', 'label' => __('Card', 'alpha-addons-for-gravity-forms')),
array('value' => 'polar', 'label' => __('Polar', 'alpha-addons-for-gravity-forms')),
array('value' => 'box', 'label' => __('Box', 'alpha-addons-for-gravity-forms')),
array('value' => 'cover', 'label' => __('Cover', 'alpha-addons-for-gravity-forms')),
array('value' => 'circle', 'label' => __('Circle', 'alpha-addons-for-gravity-forms')),
array('value' => 'luma', 'label' => __('Luma', 'alpha-addons-for-gravity-forms')),
),
'productThemes' => array(
array('value' => 'simple', 'label' => __('Simple', 'alpha-addons-for-gravity-forms')),
array('value' => 'card', 'label' => __('Card', 'alpha-addons-for-gravity-forms')),
array('value' => 'cover', 'label' => __('Cover title', 'alpha-addons-for-gravity-forms')),
array('value' => 'orbit', 'label' => __('Orbit', 'alpha-addons-for-gravity-forms')),
array('value' => 'luma', 'label' => __('Luma', 'alpha-addons-for-gravity-forms')),
array('value' => 'vivid', 'label' => __('Vivid', 'alpha-addons-for-gravity-forms')),
),
'imageStyles' => array(
array('value' => 'cover', 'label' => __('Cover', 'alpha-addons-for-gravity-forms')),
array('value' => 'contain', 'label' => __('Contained', 'alpha-addons-for-gravity-forms')),
),
'alignments' => array(
array('value' => 'default', 'label' => __('Default', 'alpha-addons-for-gravity-forms')),
array('value' => 'left', 'label' => __('Left', 'alpha-addons-for-gravity-forms')),
array('value' => 'center', 'label' => __('Center', 'alpha-addons-for-gravity-forms')),
array('value' => 'right', 'label' => __('Right', 'alpha-addons-for-gravity-forms')),
),
'columns' => $columns,
);
}
/**
* Fields with Image Picker enabled, with their current settings and what "Use form setting" resolves to.
*/
private static function get_image_picker_fields($form) {
$list = array();
if (! self::form_has_image_picker($form)) {
return $list;
}
$addon = ImagePickerAddon::get_instance();
// An empty field makes Image Picker resolve the form setting, then the global one.
$inherit = function ($setting, $field_key, $default) use ($addon, $form) {
return (string) $addon->get_active_settings_value($form, array(), $setting, $field_key, $default);
};
$inherited = array(
'choice' => array(
'theme' => $inherit('image_picker_theme', 'imagePicker_theme', 'simple'),
'imageStyle' => $inherit('image_picker_image_style', 'imagePicker_image_style', 'cover'),
'alignment' => $inherit('image_picker_alignment', 'imagePicker_alignment', 'default'),
'columns' => $inherit('image_picker_column_large', 'imagePicker_column_large', '6'),
'columnsTablet' => $inherit('image_picker_column_medium', 'imagePicker_column_medium', '4'),
'columnsMobile' => $inherit('image_picker_column_small', 'imagePicker_column_small', '2'),
),
'product' => array(
'theme' => $inherit('image_picker_theme', 'imagePicker_product_theme', 'simple'),
'imageStyle' => $inherit('image_picker_image_style', 'imagePicker_product_image_style', 'cover'),
),
'dropdown' => array(
'imageStyle' => $inherit('image_picker_image_style', 'imagePicker_dropdown_image_style', 'cover'),
),
);
foreach ((array) rgar($form, 'fields') as $field) {
if (! is_object($field) || ! $addon->is_image_picker_enabled($field)) {
continue;
}
$input_type = $addon->get_input_type($field);
if ('singleproduct' === $input_type) {
$kind = 'product';
} elseif ($addon->is_dropdown_image_picker_enabled($field)) {
$kind = 'dropdown';
} else {
$kind = 'choice';
}
$current = array();
foreach (self::image_picker_props($kind) as $option => $prop) {
$value = rgobj($field, $prop);
if ('quantityBtn' === $option) {
$current[$option] = true === $value ? 'on' : 'off';
} elseif (self::is_image_picker_size($option)) {
$number = floatval(preg_replace('/[^0-9.]/', '', (string) $value));
$current[$option] = $number > 0 ? $number : '';
} else {
$current[$option] = is_string($value) && '' !== $value ? $value : 'form_setting';
}
}
$label = wp_strip_all_tags(GFCommon::get_label($field));
$list[] = array(
'id' => absint($field->id),
'kind' => $kind,
/* translators: %d: field ID. */
'label' => '' !== trim($label) ? $label : sprintf(__('Field #%d', 'alpha-addons-for-gravity-forms'), $field->id),
'values' => $current,
'inherit' => $inherited[$kind],
);
}
return $list;
}
private static function is_image_picker_size($option) {
return (bool) preg_match('/^(width|height|cardWidth)/', $option);
}
/**
* Write "imgpf.{id}.{option}" values into the Image Picker settings of the fields and
* return the values without them.
*/
private static function apply_image_picker_settings(&$form, $values) {
$changes = array();
foreach ($values as $key => $value) {
if (preg_match('/^imgpf\.(\d+)\.([a-zA-Z]+)(@\w+)?$/', $key, $match)) {
if (empty($match[3])) {
$changes[absint($match[1])][$match[2]] = $value;
}
unset($values[$key]);
}
}
if (empty($changes)) {
return $values;
}
$fields = self::get_image_picker_fields($form);
$options = self::get_image_picker_options();
$allowed = array(
'imageStyle' => wp_list_pluck($options['imageStyles'], 'value'),
'alignment' => wp_list_pluck($options['alignments'], 'value'),
'columns' => wp_list_pluck($options['columns'], 'value'),
);
$kinds = wp_list_pluck($fields, 'kind', 'id');
foreach ((array) rgar($form, 'fields') as $field) {
$id = absint($field->id);
if (! isset($changes[$id], $kinds[$id])) {
continue;
}
$kind = $kinds[$id];
$props = self::image_picker_props($kind);
foreach ($changes[$id] as $option => $value) {
if (! isset($props[$option])) {
continue;
}
$value = (string) $value;
if ('quantityBtn' === $option) {
$field->{$props[$option]} = 'on' === $value;
continue;
}
if (self::is_image_picker_size($option)) {
$number = floatval($value);
$field->{$props[$option]} = $number > 0 ? (string) $number : '';
continue;
}
if ('theme' === $option) {
$valid = wp_list_pluck($options['product' === $kind ? 'productThemes' : 'themes'], 'value');
} else {
$valid = $allowed[preg_replace('/(Tablet|Mobile)$/', '', $option)];
}
if ('form_setting' === $value || in_array($value, $valid, true)) {
$field->{$props[$option]} = $value;
}
}
}
return $values;
}
// Desktop layouts always come from the fields' current settings, never from stored styles.
private static function strip_desktop_layouts($values) {
foreach (array_keys($values) as $key) {
if (preg_match('/^field\.\d+\.(layout|columns|flow)$/', $key) || 0 === strpos($key, 'imgpf.')) {
unset($values[$key]);
}
}
return $values;
}
private static function has_horizontal_choices($form) {
foreach (self::get_choice_fields($form) as $field) {
if ('horizontal' === $field['layout']) {
return true;
}
}
return false;
}
/**
* Orbital only styles "Horizontal" for the newer Choice field, so lay out horizontal
* checkbox/radio fields here, based on each field's current Gravity Forms setting.
*/
private static function get_horizontal_choice_css($form) {
$css = '';
foreach (self::get_choice_fields($form) as $field) {
if ('horizontal' !== $field['layout']) {
continue;
}
$selector = '#gform_wrapper_' . absint($form['id']) . ' .gfield[id$="_' . $field['id'] . '"]';
$css .= $selector . ' .gfield_checkbox,' . $selector . ' .gfield_radio{display:flex;flex-direction:row;flex-wrap:wrap;gap:var(--gf-field-choice-align-x-gap-y, 8px) var(--gf-field-choice-align-x-gap-x, 24px);grid-template-columns:none;columns:auto}';
}
return $css;
}
public function ajax_save_theme() {
$this->verify_ajax_request();
$themes = get_option(self::THEMES_OPTION, array());
$themes = is_array($themes) ? $themes : array();
$id = sanitize_key((string) rgpost('id'));
$name = mb_substr(sanitize_text_field((string) rgpost('name')), 0, 60);
$raw = rgpost('values');
$values = null === $raw ? null : self::sanitize_values(json_decode((string) $raw, true));
if ($id && isset($themes[$id])) {
if ('' !== $name) {
$themes[$id]['name'] = $name;
}
if (null !== $values) {
$themes[$id]['values'] = $values;
}
$themes[$id]['updated'] = time();
} else {
if ('' === $name) {
wp_send_json_error(array('message' => esc_html__('Please give your theme a name.', 'alpha-addons-for-gravity-forms')), 400);
}
$id = 'theme_' . str_replace('-', '', wp_generate_uuid4());
$themes[$id] = array(
'id' => $id,
'name' => $name,
'values' => $values ? $values : array(),
'created' => time(),
'updated' => time(),
);
}
update_option(self::THEMES_OPTION, $themes, false);
wp_send_json_success(array(
'id' => $id,
'themes' => $this->get_themes(),
));
}
public function ajax_delete_theme() {
$this->verify_ajax_request();
$themes = get_option(self::THEMES_OPTION, array());
$themes = is_array($themes) ? $themes : array();
$id = sanitize_key((string) rgpost('id'));
unset($themes[$id]);
update_option(self::THEMES_OPTION, $themes, false);
wp_send_json_success(array('themes' => $this->get_themes()));
}
private function verify_ajax_request() {
if (! check_ajax_referer(self::NONCE_ACTION, 'nonce', false)) {
wp_send_json_error(array('message' => esc_html__('Your session has expired. Please reload the page.', 'alpha-addons-for-gravity-forms')), 403);
}
if (! GFCommon::current_user_can_any('gravityforms_edit_forms')) {
wp_send_json_error(array('message' => esc_html__('Sorry, you are not allowed to do that.', 'alpha-addons-for-gravity-forms')), 403);
}
}
private function get_themes() {
$themes = get_option(self::THEMES_OPTION, array());
$list = array();
if (! is_array($themes)) {
return $list;
}
foreach ($themes as $theme) {
$list[] = array(
'id' => rgar($theme, 'id'),
'name' => rgar($theme, 'name'),
'values' => (object) (is_array(rgar($theme, 'values')) ? $theme['values'] : array()),
);
}
return $list;
}
/**
* Keep only flat "section.property" => scalar pairs.
*/
private static function sanitize_values($values) {
$clean = array();
if (! is_array($values)) {
return $clean;
}
foreach (array_slice($values, 0, 300, true) as $key => $value) {
// Responsive overrides use "section.property@tablet" / "@mobile".
if (! is_string($key) || ! preg_match('/^[a-zA-Z0-9_.]{1,64}(@(tablet|mobile))?$/', $key) || ! is_scalar($value)) {
continue;
}
if (is_bool($value)) {
$clean[$key] = $value;
} elseif (is_int($value) || is_float($value)) {
$clean[$key] = $value + 0;
} else {
$value = mb_substr(sanitize_text_field($value), 0, 200);
if ('' !== $value) {
$clean[$key] = $value;
}
}
}
return $clean;
}
/**
* The CSS is generated by the editor; strip anything that could break out of the