'
);
add_menu_page(
'OuterWhisper Feedback',
'OuterWhisper',
'manage_options',
'outerwhisper-dashboard',
function() { (new Dashboard())->render(); },
$icon_svg,
30
);
add_submenu_page(
'outerwhisper-dashboard',
esc_html__('admin.menu.dashboard', 'outerwhisper'),
esc_html__('admin.menu.dashboard', 'outerwhisper'),
'manage_options',
'outerwhisper-dashboard',
'__return_null'
);
add_submenu_page(
'outerwhisper-dashboard',
esc_html__('admin.menu.all_forms', 'outerwhisper'),
esc_html__('admin.menu.all_forms', 'outerwhisper'),
'manage_options',
'outerwhisper-forms',
function() { (new Forms())->render(); }
);
add_submenu_page(
'outerwhisper-dashboard',
esc_html__('admin.menu.add_form', 'outerwhisper'),
esc_html__('admin.menu.add_form', 'outerwhisper'),
'manage_options',
'outerwhisper-form-builder',
function() { (new FormBuilder())->render(); }
);
add_submenu_page(
'outerwhisper-dashboard',
esc_html__('admin.menu.feedback_list', 'outerwhisper'),
esc_html__('admin.menu.feedback_list', 'outerwhisper'),
'manage_options',
'outerwhisper-form-feedbacks',
function() { (new Feedbacks())->render(); }
);
add_submenu_page(
'outerwhisper-dashboard',
esc_html__('admin.menu.export', 'outerwhisper'),
esc_html__('admin.menu.export', 'outerwhisper'),
'manage_options',
'outerwhisper-export',
function() { (new Export())->render(); }
);
add_submenu_page(
'outerwhisper-dashboard',
esc_html__('admin.menu.about', 'outerwhisper'),
esc_html__('admin.menu.about', 'outerwhisper'),
'manage_options',
'outerwhisper-about',
function() { (new About())->render(); }
);
}
public function enqueue_assets($hook) {
if (strpos($hook, 'page_outerwhisper') === false) {
return;
}
if (!EnvironmentChecks::check()) {
return;
}
$languageHelper = new \OuterWhisper\Helper\LanguageHelper();
$currentLocale = $languageHelper->get_current_locale();
wp_enqueue_style('outerwhisper-font-awesome', OUTERWHISPER_PLUGIN_URL . 'assets/css/external/font-awesome.min.css');
wp_enqueue_style('outerwhisper-admin-style', OUTERWHISPER_PLUGIN_URL . 'assets/css/dist/outerwhisper-admin.css', [], OUTERWHISPER_VERSION);
switch (true) {
case $hook === 'outerwhisper_page_outerwhisper-form-builder':
wp_enqueue_style('outerwhisper-form-builder-style', OUTERWHISPER_PLUGIN_URL . 'assets/css/dist/outerwhisper-form-builder.css', [], OUTERWHISPER_VERSION);
wp_enqueue_style('outerwhisper-form-frontend-style', OUTERWHISPER_PLUGIN_URL . 'assets/css/dist/outerwhisper-form-frontend.css', ['outerwhisper-form-builder-style'], OUTERWHISPER_VERSION);
wp_enqueue_script('outerwhisper-sortable-js', OUTERWHISPER_PLUGIN_URL . 'assets/js/external/sortable.min.js', [], '1.15.7');
$this->enqueue_outerwhisper_admin_script('outerwhisper-form-builder-script', OUTERWHISPER_PLUGIN_URL . 'assets/js/dist/outerwhisper-form-builder.js', [], OUTERWHISPER_VERSION);
$this->enqueue_outerwhisper_admin_script('outerwhisper-form-frontend-script', OUTERWHISPER_PLUGIN_URL . 'assets/js/dist/outerwhisper-form-frontend.js', ['outerwhisper-form-builder-script'], OUTERWHISPER_VERSION);
break;
case ($hook === 'outerwhisper_page_outerwhisper-form-feedbacks' || $hook === 'outerwhisper_page_outerwhisper-export'):
wp_enqueue_style('outerwhisper-form-frontend-style', OUTERWHISPER_PLUGIN_URL . 'assets/css/dist/outerwhisper-form-frontend.css', [], OUTERWHISPER_DB_VERSION);
$this->enqueue_outerwhisper_admin_script('outerwhisper-admin-script', OUTERWHISPER_PLUGIN_URL . 'assets/js/dist/outerwhisper-admin.js', [], OUTERWHISPER_VERSION);
$this->enqueue_outerwhisper_admin_script('outerwhisper-form-frontend-script', OUTERWHISPER_PLUGIN_URL . 'assets/js/dist/outerwhisper-form-frontend.js', [], OUTERWHISPER_VERSION);
wp_localize_script('outerwhisper-admin-script', 'outerwhisperAdmin', [
'locale' => $currentLocale,
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('outerwhisper_admin_nonce'),
]);
break;
case ($hook === 'outerwhisper_page_outerwhisper-forms'):
$this->enqueue_outerwhisper_admin_script('outerwhisper-admin-script', OUTERWHISPER_PLUGIN_URL . 'assets/js/dist/outerwhisper-admin.js', [], OUTERWHISPER_VERSION);
break;
case ($hook === 'outerwhisper_page_outerwhisper-about'):
wp_enqueue_script('common');
wp_enqueue_script('wp-lists');
wp_enqueue_script('postbox');
$this->enqueue_outerwhisper_admin_script('outerwhisper-admin-script', OUTERWHISPER_PLUGIN_URL . 'assets/js/dist/outerwhisper-admin.js', [], OUTERWHISPER_VERSION);
break;
case ($hook === 'toplevel_page_outerwhisper-dashboard'):
wp_enqueue_script('outerwhisper-chart-js', OUTERWHISPER_PLUGIN_URL . 'assets/js/external/chart.min.js', [], '4.0.0');
$this->enqueue_outerwhisper_admin_script('outerwhisper-admin-script', OUTERWHISPER_PLUGIN_URL . 'assets/js/dist/outerwhisper-admin.js', [], OUTERWHISPER_VERSION);
wp_localize_script('outerwhisper-admin-script', 'outerwhisperAdmin', [
'locale' => $currentLocale,
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('outerwhisper_admin_nonce'),
]);
break;
}
}
public function handle_admin_actions() {
if (!EnvironmentChecks::check()) {
wp_safe_redirect(admin_url('admin.php?page=outerwhisper-forms'));
exit;
}
if (isset($_GET['page']) && $_GET['page'] === 'outerwhisper-forms' && isset($_GET['action']) && isset($_GET['id'])) {
$nonce = sanitize_text_field(wp_unslash($_GET['nonce'] ?? ''));
if (empty($nonce) || !wp_verify_nonce($nonce, 'outerwhisper_admin_nonce')) {
wp_safe_redirect(admin_url('admin.php?page=outerwhisper-forms'));
exit;
}
$form_db = new Form();
$id = (int) $_GET['id'];
if ( $_GET['action'] === 'duplicate' ) {
$new_id = $form_db->duplicate_form( $id );
if ( $new_id ) {
wp_safe_redirect( admin_url( 'admin.php?page=outerwhisper-forms&duplicate_success=1&nonce=' . $nonce ) );
exit;
}
} elseif ( $_GET['action'] === 'delete' ) {
if ( $form_db->delete_form( $id ) ) {
wp_safe_redirect( admin_url( 'admin.php?page=outerwhisper-forms&delete_success=1&nonce=' . $nonce ) );
exit;
}
}
}
if ( isset( $_GET['page'] ) && $_GET['page'] === 'outerwhisper-form-builder' && isset( $_GET['id'] ) ) {
$nonce = sanitize_text_field(wp_unslash($_GET['nonce'] ?? ''));
if (empty($nonce) || !wp_verify_nonce($nonce, 'outerwhisper_admin_nonce')) {
wp_safe_redirect(admin_url('admin.php?page=outerwhisper-forms'));
exit;
}
$form_db = new Form();
$id = (int) $_GET['id'];
if ( $id && !$form_db->get_form( $id ) ) {
wp_safe_redirect( admin_url( 'admin.php?page=outerwhisper-forms&form_not_found=1&nonce=' . $nonce ) );
exit;
}
}
}
public function ajax_save_form_data() {
check_ajax_referer('outerwhisper_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Permission denied', 'outerwhisper'));
}
$form_id = isset($_POST['form_id']) ? absint($_POST['form_id']) : 0;
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON is validated and sanitized field-by-field by FormDataSanitizer.
$raw_data = (isset($_POST['form_data']) && is_string($_POST['form_data'])) ? wp_unslash($_POST['form_data']) : '';
$type = isset($_POST['type']) && is_string($_POST['type']) ? sanitize_key(wp_unslash($_POST['type'])) : 'preview';
if (!in_array($type, ['preview', 'live'], true)) {
wp_send_json_error('Invalid save type.', 400);
}
if (empty($raw_data)) {
wp_send_json_error(esc_html__('Missing data', 'outerwhisper'), 400);
}
$decoded_data = json_decode($raw_data, true);
if (
JSON_ERROR_NONE !== json_last_error() ||
!is_array($decoded_data) ||
empty($decoded_data)
) {
wp_send_json_error(esc_html__('Invalid data', 'outerwhisper'), 400);
}
$configHelper = new ConfigHelper();
$valid_form_fields = $configHelper->get_form_fields_data()['valid'];
$sanitizer = new \OuterWhisper\Helper\FormDataSanitizer(
$valid_form_fields
);
$sanitizedFormDataDecoded = $sanitizer->sanitize($decoded_data);
if (is_wp_error($sanitizedFormDataDecoded)) {
wp_send_json_error(
esc_html__('Invalid form configuration. Please check your form settings.', 'outerwhisper'),
400
);
}
$sanitizedFormData = wp_json_encode($sanitizedFormDataDecoded);
$form_name = $sanitizedFormDataDecoded['settings']['main']['title'] ?? esc_html__('admin.db.default_new_form_name', 'outerwhisper');
$form_db = new Form();
$result = $form_db->update_form_data($form_id, $sanitizedFormData, $type, $form_name);
if ($result !== false) {
// Rebuild targeting index
$index_builder = new IndexBuilder();
$index_builder->rebuild();
$response = ['message' => esc_html__('Form data saved successfully', 'outerwhisper')];
if ($form_id === 0) {
$response['new_id'] = $result;
}
wp_send_json_success($response);
} else {
wp_send_json_error(esc_html__('Failed to save form data', 'outerwhisper'));
}
}
public function ajax_update_form_status() {
check_ajax_referer('outerwhisper_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Permission denied', 'outerwhisper'));
}
$form_id = isset($_POST['form_id']) ? (int) $_POST['form_id'] : 0;
$status = isset($_POST['status']) ? (int) $_POST['status'] : 0;
if (!$form_id || !$status) {
wp_send_json_error(esc_html__('Missing data', 'outerwhisper'));
}
$form_db = new Form();
$success = $form_db->update_form_status($form_id, $status);
if ($success !== false) {
// Rebuild targeting index
$index_builder = new IndexBuilder();
$index_builder->rebuild();
wp_send_json_success(['message' => esc_html__('Status updated successfully', 'outerwhisper')]);
} else {
wp_send_json_error(esc_html__('Failed to update status', 'outerwhisper'));
}
}
public function ajax_update_form_feedback_status() {
check_ajax_referer('outerwhisper_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Permission denied', 'outerwhisper'));
}
$feedback_id = isset($_POST['feedback_id']) ? (int) $_POST['feedback_id'] : 0;
$status = isset($_POST['status']) ? sanitize_text_field(wp_unslash($_POST['status'])) : '';
if (!$feedback_id || !$status) {
wp_send_json_error(esc_html__('Missing data', 'outerwhisper'));
}
$fb_db = new FormFeedback();
$success = $fb_db->update_status($feedback_id, $status);
if ($success !== false) {
wp_send_json_success(['message' => esc_html__('Feedback status updated successfully', 'outerwhisper')]);
} else {
wp_send_json_error(esc_html__('Failed to update feedback status', 'outerwhisper'));
}
}
public function ajax_delete_feedback() {
check_ajax_referer('outerwhisper_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Permission denied', 'outerwhisper'));
}
$id = absint(wp_unslash($_POST['id'] ?? 0));
$feedback_db = new FormFeedback();
if (!empty($id) && $feedback_db->delete_feedback($id)) {
wp_send_json_success();
} else {
wp_send_json_error();
}
}
public function ajax_render_form_builder_templates() {
check_ajax_referer('outerwhisper_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Permission denied', 'outerwhisper'));
}
$templates = [
'fields',
'general',
'display',
'styles',
'targeting',
'confirmation',
'notification',
'tracking',
];
$rendered = [];
foreach ($templates as $tmpl) {
$php_file = OUTERWHISPER_PLUGIN_PATH . 'templates/admin/form-builder/' . $tmpl . '.php';
ob_start();
if (!file_exists($php_file)) {
continue;
}
include $php_file;
$rendered[$tmpl] = ob_get_clean();
}
wp_send_json_success([
'templates' => $rendered,
'html' => implode("\n\n", $rendered),
]);
}
public function ajax_get_chart_recent_feedbacks() {
check_ajax_referer('outerwhisper_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Permission denied', 'outerwhisper'));
}
$feedback_db = new FormFeedback();
$feedbacks = $feedback_db->get_feedbacks(['range' => 'last_30_days', 'limit' => 500]);
$formatted_feedback = [];
foreach ($feedbacks as $entity) {
$data = $entity->toArray();
// Map created to time for JS chart compatibility
$data['time'] = $data['created'];
$formatted_feedback[] = $data;
}
wp_send_json_success([
'feedback' => $formatted_feedback
]);
}
public function ajax_get_form_feedback_by_id() {
check_ajax_referer('outerwhisper_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Permission denied', 'outerwhisper'));
}
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
if (!$id) {
wp_send_json_error(esc_html__('ID missing', 'outerwhisper'));
}
$feedback_db = new FormFeedback();
$feedback = $feedback_db->get_feedback_by_id($id);
if (!$feedback) {
wp_send_json_error(esc_html__('Feedback not found', 'outerwhisper'));
}
$user_data = $feedback->getDecodedFeedbackData();
$helper = new FormFeedbackHelper();
$prepared_data = $helper->prepare_steps_for_detail_view($user_data);
$feedbackArray = $feedback->toArray(['feedback_data']);
$feedbackArray['title'] = $user_data['title'];
$form_db = new Form();
$formData = $form_db->get_form($feedback->getFormId());
$accentColor = '#000';
if (!empty($formData->getDecodedPreviewData())) {
$accentColor = $formData->getDecodedPreviewData()['settings']['styles']['accentColor'] ?? '#000';
}
wp_send_json_success([
'feedback' => $feedbackArray,
'steps' => $prepared_data['steps'],
'user_values' => $prepared_data['userValues'],
'accent_color' => $accentColor,
]);
}
/**
* Adds a "Thank You" message to the admin footer content.
*
* @param string $content Current admin footer content.
* @return string New admin footer content.
*/
public function add_admin_footer_text( $content ): string {
$screen = get_current_screen();
if ( ! $screen || false === strpos( $screen->base, 'outerwhisper' ) ) {
return (string) $content;
}
if ( ! is_string( $content ) ) {
$content = '';
}
$content .= sprintf(
esc_html__( 'admin.notice.thank_you', 'outerwhisper' ),
'OuterWhisper'
);
return $content;
}
private function enqueue_outerwhisper_admin_script($handle, $file, $deps = [], $version = OUTERWHISPER_VERSION) {
$deps[] = 'wp-i18n';
wp_enqueue_script($handle, $file, $deps, $version, true);
wp_set_script_translations($handle, 'outerwhisper', OUTERWHISPER_PLUGIN_PATH . 'languages/');
}
}