Plugin Directory

Changeset 3183478


Ignore:
Timestamp:
11/07/2024 01:13:18 AM (16 months ago)
Author:
fpcorso
Message:

Update to version 2.0.0 from GitHub

Location:
litesurveys
Files:
62 added
2 deleted
7 edited
1 copied

Legend:

Unmodified
Added
Removed
  • litesurveys/tags/2.0.0/litesurveys-wordpress-plugin.php

    r3130242 r3183478  
    22/**
    33 * Plugin Name: LiteSurveys
    4  * Description: Adds your LiteSurveys to your WordPress site
    5  * Version: 1.0.3
    6  * Requires at least: 6.0
     4 * Description: Adds simple one-question surveys to your WordPress site
     5 * Version: 2.0.0
     6 * Requires at least: 6.1
    77 * Requires PHP: 8.0
    88 * License: GPLv3
     
    1010 * Author: LiteSurveys
    1111 * Author URI: https://litesurveys.com
     12 * Text Domain: litesurveys
    1213 *
    13  * @author LiteSurveys
     14 * @package LiteSurveys
    1415 */
    1516
    1617// Exits if accessed directly.
    17 if ( ! defined( 'ABSPATH' ) ) {
    18     exit;
    19 }
    20 
    21 // Define plugin version constant.
    22 define( 'LSAPP_PLUGIN_VERSION', '1.0.3' );
    23 
     18defined( 'ABSPATH' ) || exit;
    2419
    2520/**
    26  * The plugin's main class
     21 * Main LiteSurveys plugin class.
    2722 *
    2823 * @since 1.0.0
    2924 */
    30 class LSAPP_LiteSurveys_Integration {
    31 
    32    
    33 
    34     /**
    35      * Initializes our plugin
     25class LSAPP_LiteSurveys {
     26
     27    /**
     28     * Plugin version.
     29     *
     30     * @var string
     31     */
     32    const VERSION = '2.0.0';
     33
     34    /**
     35     * The single instance of the class.
     36     *
     37     * @var LSAPP_LiteSurveys
     38     */
     39    private static $instance = null;
     40
     41    /**
     42     * Plugin path.
     43     *
     44     * @var string
     45     */
     46    private $plugin_path;
     47
     48    /**
     49     * Plugin URL.
     50     *
     51     * @var string
     52     */
     53    private $plugin_url;
     54
     55    /**
     56     * Our REST API functionality.
     57     */
     58    private $rest_api;
     59
     60    /**
     61     * Cache time in seconds.
     62     *
     63     * @var int
     64     */
     65    const CACHE_TIME = 300;
     66
     67    /**
     68     * Main LSAPP_LiteSurveys Instance.
     69     *
     70     * Ensures only one instance of LSAPP_LiteSurveys is loaded or can be loaded.
    3671     *
    3772     * @since 1.0.0
    38      */
    39     public static function init() {
    40         self::load_hooks();
    41     }
    42 
    43     /**
    44      * Adds in any plugin-wide hooks
     73     * @return LSAPP_LiteSurveys Main instance
     74     */
     75    public static function get_instance() {
     76        if ( is_null( self::$instance ) ) {
     77            self::$instance = new self();
     78        }
     79        return self::$instance;
     80    }
     81
     82    /**
     83     * LSAPP_LiteSurveys Constructor.
     84     */
     85    private function __construct() {
     86        $this->plugin_path = plugin_dir_path( __FILE__ );
     87        $this->plugin_url  = plugin_dir_url( __FILE__ );
     88
     89        // Include the REST API class
     90        require_once $this->plugin_path . 'includes/class-rest-api.php';
     91        $this->rest_api = new LSAPP_REST_API();
     92
     93        $this->init_hooks();
     94    }
     95
     96    /**
     97     * Initialize WordPress hooks.
    4598     *
    4699     * @since 1.0.0
    47100     */
    48     public static function load_hooks() {
    49         if (is_admin()) {
    50             add_action( 'admin_menu', array( __CLASS__, 'setup_admin_menu' ) );
    51             add_action( 'admin_init', array( __CLASS__, 'admin_init' ) );
     101    private function init_hooks() {
     102        // Setting up plugin upon activation.
     103        register_activation_hook( __FILE__, array( $this, 'activate_plugin' ) );
     104
     105        // Load translations.
     106        add_action( 'init', array( $this, 'load_textdomain' ) );
     107
     108        // Add Admin code.
     109        add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
     110        add_action( 'admin_post_save_survey', array( $this, 'handle_save_survey' ) );
     111        add_action( 'admin_post_delete_survey', array( $this, 'handle_delete_survey' ) );
     112        add_action( 'admin_post_delete_submission', array( $this, 'handle_delete_submission' ) );
     113        add_action( 'admin_notices', array( $this, 'display_admin_notices' ) );
     114        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
     115        add_filter( 'plugin_action_links', array( $this, 'plugin_action_links' ), 10, 2 );
     116
     117        // Add REST API endpoints.
     118        add_action( 'rest_api_init', array( $this->rest_api, 'register_rest_routes' ) );
     119
     120        // Add frontend script.
     121        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
     122    }
     123
     124    /**
     125     * Activate plugin and create database tables.
     126     *
     127     * @since 1.0.0
     128     */
     129    public function activate_plugin() {
     130        global $wpdb;
     131
     132        if ( ! current_user_can( 'activate_plugins' ) ) {
     133            return;
     134        }
     135
     136        $charset_collate = $wpdb->get_charset_collate();
     137
     138        $this->create_database_tables( $charset_collate );
     139
     140        add_option( 'lsapp_litesurveys_version', self::VERSION );
     141    }
     142
     143    /**
     144     * Create plugin database tables.
     145     *
     146     * @since 1.0.0
     147     * @param string $charset_collate Database charset and collation.
     148     */
     149    public function create_database_tables( $charset_collate ) {
     150        global $wpdb;
     151
     152        // Create surveys table.
     153        $sql_surveys = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_surveys (
     154            id bigint(20) NOT NULL AUTO_INCREMENT,
     155            name varchar(100) NOT NULL,
     156            active tinyint(1) DEFAULT 0,
     157            submit_message text NOT NULL,
     158            targeting_settings json DEFAULT NULL,
     159            appearance_settings json DEFAULT NULL,
     160            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     161            updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     162            deleted_at timestamp NULL DEFAULT NULL,
     163            PRIMARY KEY (id)
     164        ) $charset_collate;";
     165
     166        // Create questions table.
     167        $sql_questions = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_questions (
     168            id bigint(20) NOT NULL AUTO_INCREMENT,
     169            survey_id bigint(20) NOT NULL,
     170            type varchar(25) NOT NULL,
     171            content varchar(100) NOT NULL,
     172            answers json DEFAULT NULL,
     173            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     174            updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     175            deleted_at timestamp NULL DEFAULT NULL,
     176            PRIMARY KEY (id),
     177            FOREIGN KEY (survey_id) REFERENCES {$wpdb->prefix}litesurveys_surveys(id) ON DELETE CASCADE
     178        ) $charset_collate;";
     179
     180        // Create submissions table.
     181        $sql_submissions = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_submissions (
     182            id bigint(20) NOT NULL AUTO_INCREMENT,
     183            survey_id bigint(20) NOT NULL,
     184            page varchar(255) DEFAULT NULL,
     185            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     186            deleted_at timestamp NULL DEFAULT NULL,
     187            PRIMARY KEY (id),
     188            FOREIGN KEY (survey_id) REFERENCES {$wpdb->prefix}litesurveys_surveys(id) ON DELETE CASCADE
     189        ) $charset_collate;";
     190
     191        // Create responses table.
     192        $sql_responses = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_responses (
     193            id bigint(20) NOT NULL AUTO_INCREMENT,
     194            submission_id bigint(20) NOT NULL,
     195            question_id bigint(20) NOT NULL,
     196            content text NOT NULL,
     197            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     198            deleted_at timestamp NULL DEFAULT NULL,
     199            PRIMARY KEY (id),
     200            FOREIGN KEY (submission_id) REFERENCES {$wpdb->prefix}litesurveys_submissions(id) ON DELETE CASCADE,
     201            FOREIGN KEY (question_id) REFERENCES {$wpdb->prefix}litesurveys_questions(id) ON DELETE CASCADE
     202        ) $charset_collate;";
     203
     204        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
     205        dbDelta( $sql_surveys );
     206        dbDelta( $sql_questions );
     207        dbDelta( $sql_submissions );
     208        dbDelta( $sql_responses );
     209    }
     210
     211    /**
     212     * Load plugin text domain for translations.
     213     *
     214     * @since 1.0.0
     215     */
     216    public function load_textdomain() {
     217        load_plugin_textdomain(
     218            'litesurveys',
     219            false,
     220            dirname( plugin_basename( __FILE__ ) ) . '/languages'
     221        );
     222    }
     223
     224    /**
     225     * Enqueue admin scripts and styles.
     226     *
     227     * @since 1.0.0
     228     * @param string $hook The current admin page.
     229     */
     230    public function enqueue_admin_assets( $hook ) {
     231        if ( false === strpos( $hook, 'litesurveys' ) ) {
     232            return;
     233        }
     234
     235        $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : 'list';
     236
     237        if ( 'edit' === $action || 'new' === $action ) {
     238            $suffix = $this->get_asset_suffix();
     239            wp_enqueue_style(
     240                'litesurveys-admin',
     241                $this->plugin_url . "resources/css/admin{$suffix}.css",
     242                array(),
     243                self::VERSION
     244            );
     245
     246            wp_enqueue_script(
     247                'litesurveys-admin',
     248                $this->plugin_url . "resources/js/admin{$suffix}.js",
     249                array( 'jquery' ),
     250                self::VERSION,
     251                true
     252            );
     253        }
     254    }
     255
     256    /**
     257     * Add admin menu items.
     258     *
     259     * @since 1.0.0
     260     */
     261    public function add_admin_menu() {
     262        add_menu_page(
     263            'LiteSurveys',
     264            'LiteSurveys',
     265            'manage_options',
     266            'LSAPP_litesurveys',
     267            array( $this, 'render_admin_page' ),
     268            'dashicons-chart-bar',
     269            30
     270        );
     271    }
     272
     273    /**
     274     * Render the admin page content.
     275     *
     276     * @since 1.0.0
     277     */
     278    public function render_admin_page() {
     279        $this->verify_admin_access();
     280
     281        global $wpdb;
     282
     283        $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : 'list';
     284
     285        switch ( $action ) {
     286            case 'view-responses':
     287                $survey_id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
     288                if ( ! $survey_id ) {
     289                    wp_die( esc_html__( 'Invalid survey ID.', 'litesurveys' ) );
     290                }
     291
     292                // Get survey data with question.
     293                $survey = $wpdb->get_row(
     294                    $wpdb->prepare(
     295                        "SELECT s.*, q.content as question_content
     296                        FROM {$wpdb->prefix}litesurveys_surveys s
     297                        LEFT JOIN {$wpdb->prefix}litesurveys_questions q ON s.id = q.survey_id
     298                        WHERE s.id = %d AND s.deleted_at IS NULL",
     299                        $survey_id
     300                    )
     301                );
     302
     303                if ( ! $survey ) {
     304                    wp_die( esc_html__( 'Survey not found.', 'litesurveys' ) );
     305                }
     306
     307                // Pagination settings.
     308                $per_page     = 20;
     309                $current_page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1;
     310                $offset       = ( $current_page - 1 ) * $per_page;
     311
     312                // Get total count for pagination.
     313                $total_items = $wpdb->get_var(
     314                    $wpdb->prepare(
     315                        "SELECT COUNT(*)
     316                        FROM {$wpdb->prefix}litesurveys_submissions s
     317                        WHERE s.survey_id = %d AND s.deleted_at IS NULL",
     318                        $survey_id
     319                    )
     320                );
     321
     322                // Get submissions with responses.
     323                $submissions = $wpdb->get_results(
     324                    $wpdb->prepare(
     325                        "SELECT s.id, s.created_at, s.page, r.content as response
     326                        FROM {$wpdb->prefix}litesurveys_submissions s
     327                        LEFT JOIN {$wpdb->prefix}litesurveys_responses r ON s.id = r.submission_id
     328                        WHERE s.survey_id = %d AND s.deleted_at IS NULL
     329                        ORDER BY s.created_at DESC
     330                        LIMIT %d OFFSET %d",
     331                        $survey_id,
     332                        $per_page,
     333                        $offset
     334                    )
     335                );
     336
     337                // Calculate pagination values.
     338                $total_pages = ceil( $total_items / $per_page );
     339
     340                include $this->plugin_path . 'views/admin/survey-submissions.php';
     341                break;
     342
     343            case 'edit':
     344            case 'new':
     345                // Get survey data if editing.
     346                $survey_id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
     347                $survey    = null;
     348
     349                if ( $survey_id ) {
     350                    $survey = $wpdb->get_row(
     351                        $wpdb->prepare(
     352                            "SELECT * FROM {$wpdb->prefix}litesurveys_surveys WHERE id = %d AND deleted_at IS NULL",
     353                            $survey_id
     354                        )
     355                    );
     356
     357                    if ( $survey ) {
     358                        $question = $wpdb->get_row(
     359                            $wpdb->prepare(
     360                                "SELECT * FROM {$wpdb->prefix}litesurveys_questions WHERE survey_id = %d AND deleted_at IS NULL",
     361                                $survey_id
     362                            )
     363                        );
     364
     365                        if ( $question ) {
     366                            $survey->question         = $question;
     367                            $survey->question->answers = json_decode( $question->answers );
     368                        }
     369
     370                        $survey->targeting_settings   = json_decode( $survey->targeting_settings );
     371                        $survey->appearance_settings = json_decode( $survey->appearance_settings );
     372                    }
     373                }
     374
     375                // Set defaults for new survey.
     376                if ( ! $survey ) {
     377                    $survey = (object) array(
     378                        'name'                => '',
     379                        'active'             => false,
     380                        'submit_message'     => 'Thanks! I appreciate you taking the time to respond.',
     381                        'targeting_settings' => (object) array(
     382                            'targets'  => (object) array(
     383                                'show'     => 'all',
     384                                'includes' => array(),
     385                                'excludes' => array(),
     386                            ),
     387                            'trigger'  => array(
     388                                (object) array(
     389                                    'type'        => 'auto',
     390                                    'auto_timing' => 5,
     391                                ),
     392                            ),
     393                        ),
     394                        'appearance_settings' => (object) array(
     395                            'horizontal_position' => 'right',
     396                        ),
     397                        'question'           => (object) array(
     398                            'type'    => 'multiple-choice',
     399                            'content' => '',
     400                            'answers' => array( '', '', '' ),
     401                        ),
     402                    );
     403                }
     404                include $this->plugin_path . 'views/admin/survey-edit.php';
     405                break;
     406
     407            default:
     408                $surveys = $wpdb->get_results(
     409                    "SELECT * FROM {$wpdb->prefix}litesurveys_surveys WHERE deleted_at IS NULL ORDER BY created_at DESC"
     410                );
     411                include $this->plugin_path . 'views/admin/surveys-admin.php';
     412                break;
     413        }
     414    }
     415
     416    /**
     417     * Handle saving survey data.
     418     *
     419     * @since 1.0.0
     420     */
     421    public function handle_save_survey() {
     422        $this->verify_admin_access();
     423
     424        check_admin_referer( 'save_survey', 'survey_nonce' );
     425
     426        global $wpdb;
     427
     428        $survey_id = isset( $_POST['survey_id'] ) ? absint( $_POST['survey_id'] ) : 0;
     429        $save_type = isset( $_POST['save_type'] ) ? sanitize_text_field( wp_unslash( $_POST['save_type'] ) ) : 'draft';
     430
     431        try {
     432            // Validate required fields.
     433            if ( empty( $_POST['survey_name'] ) ) {
     434                throw new Exception( __( 'Survey name is required.', 'litesurveys' ) );
     435            }
     436
     437            if ( empty( $_POST['question_content'] ) ) {
     438                throw new Exception( __( 'Survey question is required.', 'litesurveys' ) );
     439            }
     440
     441            // Prepare targeting settings.
     442            $targeting_settings = array(
     443                'targets' => array(
     444                    'show'     => sanitize_text_field( wp_unslash( $_POST['targeting_show'] ) ),
     445                    'includes' => isset( $_POST['includes'] ) ?
     446                        array_map( 'sanitize_text_field', wp_unslash( $_POST['includes'] ) ) : array(),
     447                    'excludes' => isset( $_POST['excludes'] ) ?
     448                        array_map( 'sanitize_text_field', wp_unslash( $_POST['excludes'] ) ) : array(),
     449                ),
     450                'trigger' => array(
     451                    array(
     452                        'type'        => sanitize_text_field( wp_unslash( $_POST['trigger_type'] ) ),
     453                        'auto_timing' => absint( $_POST['auto_timing'] ),
     454                    ),
     455                ),
     456            );
     457
     458            // Prepare appearance settings.
     459            $appearance_settings = array(
     460                'horizontal_position' => sanitize_text_field( wp_unslash( $_POST['horizontal_position'] ) ),
     461            );
     462
     463            // Sanitize JSON data.
     464            $targeting_json   = $this->sanitize_json_data( $targeting_settings );
     465            $appearance_json = $this->sanitize_json_data( $appearance_settings );
     466
     467            if ( false === $targeting_json || false === $appearance_json ) {
     468                throw new Exception( __( 'Invalid settings data.', 'litesurveys' ) );
     469            }
     470
     471            // Prepare survey data.
     472            $survey_data = array(
     473                'name'                => sanitize_text_field( wp_unslash( $_POST['survey_name'] ) ),
     474                'submit_message'      => sanitize_textarea_field( wp_unslash( $_POST['submit_message'] ) ),
     475                'active'             => ( 'publish' === $save_type ),
     476                'targeting_settings'  => $targeting_json,
     477                'appearance_settings' => $appearance_json,
     478            );
     479
     480            // Prepare answers for multiple choice.
     481            $answers = array();
     482            if ( 'multiple-choice' === $_POST['question_type'] && ! empty( $_POST['answers'] ) ) {
     483                $answers = array_filter( array_map( 'sanitize_text_field', wp_unslash( $_POST['answers'] ) ) );
     484                if ( count( $answers ) < 2 ) {
     485                    throw new Exception( __( 'Multiple choice questions must have at least 2 answers.', 'litesurveys' ) );
     486                }
     487            }
     488
     489            // Prepare question data.
     490            $question_data = array(
     491                'type'    => sanitize_text_field( wp_unslash( $_POST['question_type'] ) ),
     492                'content' => sanitize_textarea_field( wp_unslash( $_POST['question_content'] ) ),
     493                'answers' => $this->sanitize_json_data( $answers ),
     494            );
     495
     496            // Start transaction.
     497            $wpdb->query( 'START TRANSACTION' );
     498
     499            if ( $survey_id ) {
     500                // Update existing survey.
     501                $result = $wpdb->update(
     502                    $wpdb->prefix . 'litesurveys_surveys',
     503                    $survey_data,
     504                    array( 'id' => $survey_id )
     505                );
     506
     507                if ( false === $result ) {
     508                    throw new Exception( __( 'Failed to update survey.', 'litesurveys' ) );
     509                }
     510
     511                // Update or insert question.
     512                $existing_question = $wpdb->get_row(
     513                    $wpdb->prepare(
     514                        "SELECT id FROM {$wpdb->prefix}litesurveys_questions WHERE survey_id = %d AND deleted_at IS NULL",
     515                        $survey_id
     516                    )
     517                );
     518
     519                if ( $existing_question ) {
     520                    $result = $wpdb->update(
     521                        $wpdb->prefix . 'litesurveys_questions',
     522                        $question_data,
     523                        array( 'id' => $existing_question->id )
     524                    );
     525                    if ( false === $result ) {
     526                        throw new Exception( __( 'Failed to update survey question.', 'litesurveys' ) );
     527                    }
     528                } else {
     529                    $question_data['survey_id'] = $survey_id;
     530                    $result = $wpdb->insert( $wpdb->prefix . 'litesurveys_questions', $question_data );
     531                    if ( ! $result ) {
     532                        throw new Exception( __( 'Failed to create survey question.', 'litesurveys' ) );
     533                    }
     534                }
     535            } else {
     536                // Insert new survey.
     537                $result = $wpdb->insert( $wpdb->prefix . 'litesurveys_surveys', $survey_data );
     538                if ( ! $result ) {
     539                    throw new Exception( __( 'Failed to create survey.', 'litesurveys' ) );
     540                }
     541                $survey_id = $wpdb->insert_id;
     542
     543                // Insert question.
     544                $question_data['survey_id'] = $survey_id;
     545                $result = $wpdb->insert( $wpdb->prefix . 'litesurveys_questions', $question_data );
     546                if ( ! $result ) {
     547                    throw new Exception( __( 'Failed to create survey question.', 'litesurveys' ) );
     548                }
     549            }
     550
     551            // Commit transaction.
     552            $wpdb->query( 'COMMIT' );
     553
     554            // Clear caches after successful save.
     555            $this->clear_survey_caches();
     556
     557            // Build redirect arguments.
     558            $redirect_args = array(
     559                'page'    => 'LSAPP_litesurveys',
     560                'action'  => 'edit',
     561                'id'      => $survey_id,
     562                'message' => 'publish' === $save_type ? 'survey-published' : 'survey-saved',
     563            );
     564
     565            // Redirect with success message.
     566            wp_safe_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) );
     567            exit;
     568
     569        } catch ( Exception $e ) {
     570            // Rollback transaction.
     571            $wpdb->query( 'ROLLBACK' );
     572
     573            wp_safe_redirect(
     574                add_query_arg(
     575                    array(
     576                        'page'    => 'LSAPP_litesurveys',
     577                        'action'  => $survey_id ? 'edit' : 'new',
     578                        'id'      => $survey_id,
     579                        'message' => 'error',
     580                        'error'   => rawurlencode( $e->getMessage() ),
     581                    ),
     582                    admin_url( 'admin.php' )
     583                )
     584            );
     585            exit;
     586        }
     587    }
     588
     589    public function handle_delete_survey() {
     590        $this->verify_admin_access();
     591
     592        // Verify nonce
     593        $nonce = isset($_REQUEST['_wpnonce']) ? sanitize_text_field($_REQUEST['_wpnonce']) : '';
     594        $survey_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
     595
     596        if (!wp_verify_nonce($nonce, 'delete-survey_' . $survey_id)) {
     597            wp_die(__('Security check failed.', 'litesurveys'));
     598        }
     599
     600        if (!$survey_id) {
     601            wp_die(__('Invalid survey ID.', 'litesurveys'));
     602        }
     603
     604        global $wpdb;
     605
     606        try {
     607            // Start transaction
     608            $wpdb->query('START TRANSACTION');
     609
     610            // Soft delete the survey
     611            $result = $wpdb->update(
     612                $wpdb->prefix . 'litesurveys_surveys',
     613                array('deleted_at' => current_time('mysql')),
     614                array('id' => $survey_id),
     615                array('%s'),
     616                array('%d')
     617            );
     618
     619            if ($result === false) {
     620                throw new Exception(__('Failed to delete survey.', 'litesurveys'));
     621            }
     622
     623            // Soft delete associated questions
     624            $wpdb->update(
     625                $wpdb->prefix . 'litesurveys_questions',
     626                array('deleted_at' => current_time('mysql')),
     627                array('survey_id' => $survey_id),
     628                array('%s'),
     629                array('%d')
     630            );
     631
     632            // Commit transaction
     633            $wpdb->query('COMMIT');
     634
     635            // Clear caches after successful save
     636            $this->clear_survey_caches();
     637
     638            // Redirect with success message
     639            wp_safe_redirect(add_query_arg(
     640                array(
     641                    'page' => 'LSAPP_litesurveys',
     642                    'message' => 'survey-deleted'
     643                ),
     644                admin_url('admin.php')
     645            ));
     646            exit;
     647
     648        } catch (Exception $e) {
     649            // Rollback transaction
     650            $wpdb->query('ROLLBACK');
     651
     652            // Redirect with error message
     653            wp_safe_redirect(add_query_arg(
     654                array(
     655                    'page' => 'LSAPP_litesurveys',
     656                    'message' => 'error',
     657                    'error' => urlencode($e->getMessage())
     658                ),
     659                admin_url('admin.php')
     660            ));
     661            exit;
     662        }
     663    }
     664
     665    public function handle_delete_submission() {
     666        $this->verify_admin_access();
     667   
     668        // Verify nonce
     669        $nonce = isset($_REQUEST['_wpnonce']) ? sanitize_text_field($_REQUEST['_wpnonce']) : '';
     670        $submission_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
     671        $survey_id = isset($_REQUEST['survey_id']) ? intval($_REQUEST['survey_id']) : 0;
     672   
     673        if (!wp_verify_nonce($nonce, 'delete-submission_' . $submission_id)) {
     674            wp_die(__('Security check failed.', 'litesurveys'));
     675        }
     676   
     677        if (!$submission_id || !$survey_id) {
     678            wp_die(__('Invalid submission ID.', 'litesurveys'));
     679        }
     680   
     681        global $wpdb;
     682   
     683        try {
     684            // Start transaction
     685            $wpdb->query('START TRANSACTION');
     686   
     687            // Soft delete the submission
     688            $result = $wpdb->update(
     689                $wpdb->prefix . 'litesurveys_submissions',
     690                array('deleted_at' => current_time('mysql')),
     691                array('id' => $submission_id),
     692                array('%s'),
     693                array('%d')
     694            );
     695   
     696            if ($result === false) {
     697                throw new Exception(__('Failed to delete submission.', 'litesurveys'));
     698            }
     699   
     700            // Soft delete associated responses
     701            $wpdb->update(
     702                $wpdb->prefix . 'litesurveys_responses',
     703                array('deleted_at' => current_time('mysql')),
     704                array('submission_id' => $submission_id),
     705                array('%s'),
     706                array('%d')
     707            );
     708   
     709            // Commit transaction
     710            $wpdb->query('COMMIT');
     711   
     712            // Redirect with success message
     713            wp_safe_redirect(add_query_arg(
     714                array(
     715                    'page' => 'LSAPP_litesurveys',
     716                    'action' => 'view-responses',
     717                    'id' => $survey_id,
     718                    'message' => 'submission-deleted'
     719                ),
     720                admin_url('admin.php')
     721            ));
     722            exit;
     723   
     724        } catch (Exception $e) {
     725            // Rollback transaction
     726            $wpdb->query('ROLLBACK');
     727   
     728            // Redirect with error message
     729            wp_safe_redirect(add_query_arg(
     730                array(
     731                    'page' => 'LSAPP_litesurveys',
     732                    'action' => 'view-responses',
     733                    'id' => $survey_id,
     734                    'message' => 'error',
     735                    'error' => urlencode($e->getMessage())
     736                ),
     737                admin_url('admin.php')
     738            ));
     739            exit;
     740        }
     741    }
     742   
     743    /**
     744     * Display admin notices with proper escaping.
     745     */
     746    public function display_admin_notices() {
     747        if (!isset($_GET['page']) || 'LSAPP_litesurveys' !== $_GET['page']) {
     748            return;
     749        }
     750
     751        if (isset($_GET['message'])) {
     752            $message_type = sanitize_text_field($_GET['message']);
     753            $class = 'notice ';
     754            $message = '';
     755
     756            switch ($message_type) {
     757                case 'survey-published':
     758                    $class .= 'notice-success';
     759                    $message = __('Survey published successfully.', 'litesurveys');
     760                    break;
     761                case 'survey-unpublished':
     762                    $class .= 'notice-warning';
     763                    $message = __('Survey unpublished.', 'litesurveys');
     764                    break;
     765                case 'survey-saved':
     766                    $class .= 'notice-success';
     767                    $message = __('Survey saved successfully.', 'litesurveys');
     768                    break;
     769                case 'survey-deleted':
     770                    $class .= 'notice-success';
     771                    $message = __('Survey deleted successfully.', 'litesurveys');
     772                    break;
     773                case 'submission-deleted':
     774                    $class .= 'notice-success';
     775                    $message = __('Submission deleted successfully.', 'litesurveys');
     776                    break;
     777                case 'error':
     778                    $class .= 'notice-error';
     779                    $message = isset($_GET['error']) ?
     780                        sanitize_text_field(urldecode($_GET['error'])) :
     781                        __('An error occurred while processing your request.', 'litesurveys');
     782                    break;
     783                default:
     784                    return; // Unknown message type
     785            }
     786
     787            printf(
     788                '<div class="%1$s is-dismissible"><p>%2$s</p></div>',
     789                esc_attr($class),
     790                esc_html($message)
     791            );
     792        }
     793    }
     794
     795    public function enqueue_frontend_assets() {
     796        // Only load if there are active surveys
     797        if (!$this->has_active_surveys()) {
     798            return;
    52799        }
    53800       
    54         add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_script' ), 50 );
    55         add_filter( 'wp_script_attributes', array( __CLASS__, 'add_script_attributes' ) );
    56         add_filter( 'plugin_action_links', array( __CLASS__, 'plugin_action_links' ), 10, 2 );
    57     }
    58 
    59     /**
    60      * Code to be run during admin_init
    61      *
    62      * @since 1.0.0
    63      */
    64     public static function admin_init() {
    65         register_setting( 'LSAPP_litesurveys', 'LSAPP_litesurveys_settings' );
    66         add_settings_section(
    67             'LSAPP_litesurveys_settings_section',
    68             '',
    69             array( __CLASS__, 'litesurveys_settings_section_callback' ),
    70             'LSAPP_litesurveys'
     801        $suffix = $this->get_asset_suffix();
     802       
     803        wp_enqueue_style(
     804            'litesurveys-frontend',
     805            $this->plugin_url . "resources/css/frontend{$suffix}.css",
     806            array(),
     807            self::VERSION
    71808        );
    72         add_settings_field(
    73             'LSAPP_litesurveys_settings_site_id',
    74             'Site ID',
    75             array( __CLASS__, 'litesurveys_settings_site_id_callback' ),
    76             'LSAPP_litesurveys',
    77             'LSAPP_litesurveys_settings_section',
    78             array(
    79                 'label_for'         => 'site_id',
    80                 'class'             => 'wporg_row',
    81             )
     809       
     810        wp_enqueue_script(
     811            'litesurveys-frontend',
     812            $this->plugin_url . "resources/js/frontend{$suffix}.js",
     813            array(),
     814            self::VERSION,
     815            true
    82816        );
    83     }
    84 
    85     /**
    86      * Sets up our page in the admin menu
    87      *
    88      * @since 1.0.0
    89      */
    90     public static function setup_admin_menu() {
    91         add_options_page( 'LiteSurveys', 'LiteSurveys', 'manage_options', 'LSAPP_litesurveys', array( __CLASS__, 'generate_admin_page' ) );
    92     }
    93 
    94     /**
    95      * Callback for our main settings section
    96      *
    97      * @since 1.0.0
    98      */
    99     public static function litesurveys_settings_section_callback() {
    100         ?>
    101         <p>You will need to have an active <a href="https://litesurveys.com" target="_blank">LiteSurveys</a> account to use this plugin. Within your LiteSurveys account, go to the "Connect Website" page to get your Website ID.</p>
    102         <?php
    103     }
    104 
    105     /**
    106      * Callback for site ID settings field
    107      *
    108      * @since 1.0.0
    109      */
    110     public static function litesurveys_settings_site_id_callback($args) {
    111         $site_id = self::get_site_id();
    112         ?>
    113         <input id="<?php echo esc_attr( $args['label_for'] ); ?>" name="LSAPP_litesurveys_settings[<?php echo esc_attr( $args['label_for'] ); ?>]" type="text" value="<?php echo esc_attr( $site_id ); ?>">
    114         <p class="description">(Leave blank to disable)</p>
    115         <?php
    116     }
    117 
    118     /**
    119      * Generates our admin page
    120      *
    121      * @since 1.0.0
    122      */
    123     public static function generate_admin_page() {
    124         if ( ! current_user_can( 'manage_options' ) ) {
    125             return;
    126         }
    127 
    128         ?>
    129         <div class="wrap">
    130             <h1>LiteSurveys Integration</h1>
    131             <form action="options.php" method="post">
    132                 <?php
    133                 settings_fields( 'LSAPP_litesurveys' );
    134                 do_settings_sections( 'LSAPP_litesurveys' );
    135                 submit_button( 'Save Settings' );
    136                 ?>
    137             </form>
    138         </div>
    139         <?php
    140     }
    141 
    142     /**
    143      * Enqueues the LiteSurveys script
    144      *
    145      * @since 1.0.0
    146      */
    147     public static function enqueue_script() {
    148         if ( ! self::get_site_id() ) {
    149             return;
    150         }
    151         wp_enqueue_script( 'litesurveys', 'https://embeds.litesurveys.com/litesurveys.min.js', array(), LSAPP_PLUGIN_VERSION, array( 'strategy' => 'defer' ) );
    152     }
    153 
    154     /**
    155      * Filter the script attributes to add id and data-site-id attributes.
    156      *
    157      * @param array $attributes The script tag attributes.
    158      * @return array
    159      */
    160     public static function add_script_attributes( $attributes ) {
    161         if ( 'litesurveys-js' === $attributes['id'] ) {
    162             $attributes['data-site-id'] = self::get_site_id();
    163         }
    164         return $attributes;
    165     }
    166 
    167     /**
    168      * Retrieves the saved site id setting
    169      *
    170      * @since 1.0.0
    171      * @return string
    172      */
    173     private static function get_site_id() {
    174         return self::get_setting('site_id', '');
    175     }
    176 
    177     /**
    178      * Retrieves a specific plugin setting
    179      *
    180      * @since 1.0.0
    181      * @param string $setting Which setting to retrieve.
    182      * @param mixed  $default The value to return if setting does not exist.
    183      * @return mixed
    184      */
    185     private static function get_setting($setting, $default = false) {
    186         $settings = self::get_settings();
    187         if ( isset( $settings[$setting] ) ) {
    188             return $settings[$setting];
    189         }
    190 
    191         return $default;
    192     }
    193 
    194     /**
    195      * Retrieves our plugin settings
    196      *
    197      * @since 1.0.0
    198      * @return array Our settings
    199      */
    200     private static function get_settings() {
    201         $settings = get_option( 'LSAPP_litesurveys_settings', [] );
    202         if (! is_array( $settings ) ) {
    203             $settings = [];
    204         }
    205 
    206         return $settings;
     817       
     818        wp_localize_script('litesurveys-frontend', 'liteSurveysSettings', array(
     819            'ajaxUrl' => rest_url('litesurveys/v1/')
     820        ));
    207821    }
    208822
     
    219833            'litesurveys-wordpress-plugin/litesurveys-wordpress-plugin.php'
    220834        ];
    221         if (in_array($plugin_file, $plugin_files)) {
    222             $settings_url = sprintf( '<a href="%s">Settings</a>', esc_url( admin_url( 'options-general.php?page=LSAPP_litesurveys' ) ) );
    223             $actions = array_merge( ['litesurveys_settings' => $settings_url], $actions) ;
     835        if ( in_array( $plugin_file, $plugin_files, true ) ) {
     836            $surveys_url = esc_url( admin_url( 'admin.php?page=LSAPP_litesurveys' ) );
     837            $surveys_link = sprintf( '<a href="%s">%s</a>', $surveys_url, __( 'Surveys', 'litesurveys' ) );
     838            $actions = array_merge( array( 'surveys' => $surveys_link ), $actions );
    224839        }
    225840        return $actions;
    226841    }
     842
     843    /**
     844     * Get the REST API instance.
     845     *
     846     * @since 2.0.0
     847     * @return LSAPP_REST_API The REST API instance.
     848     */
     849    public function get_rest_api() {
     850        return $this->rest_api;
     851    }
     852
     853    /**
     854     * Check if there are any active surveys.
     855     *
     856     * @return bool True if there are active surveys, false otherwise.
     857     */
     858    private function has_active_surveys() {
     859        $cache_key = 'litesurveys_has_active';
     860        $has_active = get_transient($cache_key);
     861
     862        if (false === $has_active) {
     863            global $wpdb;
     864           
     865            $has_active = (bool)$wpdb->get_var($wpdb->prepare(
     866                "SELECT EXISTS(
     867                    SELECT 1 FROM {$wpdb->prefix}litesurveys_surveys
     868                    WHERE active = %d AND deleted_at IS NULL
     869                )",
     870                1
     871            ));
     872           
     873            set_transient($cache_key, $has_active ? '1' : '0', self::CACHE_TIME);
     874        }
     875
     876        return $has_active === '1';
     877    }
     878
     879    private function verify_admin_access() {
     880        if (!current_user_can('manage_options')) {
     881            wp_die(
     882                esc_html__('You do not have sufficient permissions to access this page.', 'litesurveys'),
     883                403
     884            );
     885        }
     886    }
     887
     888    /**
     889     * Sanitize JSON data before storage.
     890     *
     891     * @param array $data The data to be sanitized and stored as JSON.
     892     * @return string|false Sanitized JSON string or false on failure.
     893     */
     894    private function sanitize_json_data($data) {
     895        if (!is_array($data)) {
     896            return false;
     897        }
     898
     899        array_walk_recursive($data, function(&$value) {
     900            if (is_string($value)) {
     901                $value = sanitize_text_field($value);
     902            } elseif (is_int($value)) {
     903                $value = intval($value);
     904            } elseif (is_float($value)) {
     905                $value = floatval($value);
     906            } elseif (is_bool($value)) {
     907                $value = (bool)$value;
     908            } else {
     909                $value = '';
     910            }
     911        });
     912
     913        return wp_json_encode($data);
     914    }
     915
     916    /**
     917     * Get the suffix for asset files (.min in production, empty in debug).
     918     *
     919     * @return string
     920     */
     921    private function get_asset_suffix() {
     922        return defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min';
     923    }
     924
     925    /**
     926     * Clear survey caches when a survey is updated.
     927     */
     928    private function clear_survey_caches() {
     929        delete_transient('litesurveys_active_surveys');
     930        delete_transient('litesurveys_has_active');
     931    }
    227932}
    228933
    229 LSAPP_LiteSurveys_Integration::init();
     934// Initialize plugin
     935LSAPP_LiteSurveys::get_instance();
    230936?>
  • litesurveys/tags/2.0.0/readme.txt

    r3130242 r3183478  
    44Requires at least: 6.0
    55Tested up to: 6.6.1
    6 Stable tag: 1.0.3
     6Stable tag: 2.0.0
    77Requires PHP: 8.0
    88License: GPLv3
     
    1313== Description ==
    1414
    15 Easily hear from your users by adding pre-sale surveys, post-sale surveys, feedback surveys, and more to your website by integrating your site with [the LiteSurveys service](https://litesurveys.com/).
     15Learn from your site visitors using quick surveys.
    1616
    17 **Important Note**: This plugin requires a [LiteSurveys](https://litesurveys.com) plan to create surveys and collect responses. Get started for free today!
     17== Description ==
    1818
    19 ## Collect Feedback and Ideas From Your Site Visitors
     19LiteSurveys is a lightweight WordPress plugin that helps you gather feedback from your website visitors through simple, unobtrusive surveys. With LiteSurveys, you can easily create and manage single-question surveys that appear as slide-in popups on your website.
    2020
    21 You can use LiteSurveys to:
     21## Key Features
    2222
    23 * **Conduct User Research** - Using LiteSurveys small slide-in surveys, users can quickly provide information about who they are or what they are looking for.
    24 * **Identify Pain Points** - Adding in quick surveys during the sales funnel allows site visitors to provide feedback about concerns and issues.
    25 * **Get Marketing Insights** - Using surveys at key points during the user experience can provide details about the user’s decisions and thoughts which can influence your copy and strategy for your marketing efforts.
    26 * And much more!
     23* **Simple Survey Creation** - Create surveys directly in your WordPress admin with just a few clicks
     24* **Multiple Question Types** - Choose between multiple choice or open-ended questions
     25* **Customizable Display** - Control when and where your surveys appear
     26* **Easy Response Management** - View and manage survey responses right in your WordPress dashboard
     27* **Unobtrusive Design** - Surveys appear as subtle slide-in popups that won't disrupt the user experience
     28* **Mobile Friendly** - Surveys work great on all devices
     29
     30## Use LiteSurveys to:
     31
     32* **Conduct User Research** - Using small slide-in surveys, users can quickly provide information about who they are or what they are looking for
     33* **Identify Pain Points** - Adding quick surveys during the sales funnel allows site visitors to provide feedback about concerns and issues
     34* **Get Marketing Insights** - Using surveys at key points during the user experience can provide details about the user's decisions and thoughts which can influence your copy and strategy
     35* **Gather Customer Feedback** - Collect real feedback from actual users about their experience with your site or product
    2736
    2837## Some Features of LiteSurveys:
    2938
    30 * **Question Type Variety** - Select from a variety question types including multiple choice, open answer, and more.
    31 * **Analyze Results** - Easily see how individual site visitors respond but also analyze how all site visitors are responding.
    32 * **Control Timing** - LiteSurveys has many options available for controlling when the survey appears.
    33 * **Target Specific Pages** - Show your survey on your whole website or only on specific pages.
    34 * **Export Results** - Export your survey responses so you can use them in spreadsheets, databases, other tools, and more.
    35 
    36 Ready to get started? [Create your account](https://litesurveys.com/) for free to create your first survey within minutes!
     39* **Question Type Variety** - Select from multiple choice or open-ended questions to best suit your needs
     40* **Timing Control** - Set when your survey should appear to visitors
     41* **Position Control** - Choose whether your survey appears in the bottom left or bottom right
     42* **Active/Inactive States** - Easily activate or deactivate surveys as needed
     43* **Submissions Per Page** - Easily see what page the website visitor was on when they submitted the survey
    3744
    3845== Installation ==
     
    4754
    48551. An example LiteSurveys slide-in survey.
    49 2. The settings page within the WordPress plugin.
     562. Surveys admin page showing your surveys, which are active, and how many submissions each has.
    5057
    5158== Changelog ==
     59
     60= 2.0.0 (November 6, 2024) =
     61
     62* Major rewrite of plugin to be standalone instead of requiring LiteSurveys service
     63* Add ability to create and manage surveys directly in WordPress admin
     64* Add support for multiple choice and open-ended questions
     65* Add support for customizing survey timing and position
    5266
    5367= 1.0.3 (August 2, 2024) =
  • litesurveys/tags/2.0.0/uninstall.php

    r3013451 r3183478  
    11<?php
    2 // if uninstall.php is not called by WordPress, die
    3 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    4     die;
     2// If uninstall not called from WordPress, exit
     3if (!defined('WP_UNINSTALL_PLUGIN')) {
     4    exit;
    55}
    66
    7 delete_option( 'LSAPP_litesurveys_settings' );
     7global $wpdb;
     8
     9// Drop tables in reverse order of dependencies
     10$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_responses");
     11$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_submissions");
     12$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_questions");
     13$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_surveys");
     14
     15// Delete options
     16delete_option('lsapp_litesurveys_version');
     17delete_option('LSAPP_litesurveys_settings');
  • litesurveys/trunk/litesurveys-wordpress-plugin.php

    r3130242 r3183478  
    22/**
    33 * Plugin Name: LiteSurveys
    4  * Description: Adds your LiteSurveys to your WordPress site
    5  * Version: 1.0.3
    6  * Requires at least: 6.0
     4 * Description: Adds simple one-question surveys to your WordPress site
     5 * Version: 2.0.0
     6 * Requires at least: 6.1
    77 * Requires PHP: 8.0
    88 * License: GPLv3
     
    1010 * Author: LiteSurveys
    1111 * Author URI: https://litesurveys.com
     12 * Text Domain: litesurveys
    1213 *
    13  * @author LiteSurveys
     14 * @package LiteSurveys
    1415 */
    1516
    1617// Exits if accessed directly.
    17 if ( ! defined( 'ABSPATH' ) ) {
    18     exit;
    19 }
    20 
    21 // Define plugin version constant.
    22 define( 'LSAPP_PLUGIN_VERSION', '1.0.3' );
    23 
     18defined( 'ABSPATH' ) || exit;
    2419
    2520/**
    26  * The plugin's main class
     21 * Main LiteSurveys plugin class.
    2722 *
    2823 * @since 1.0.0
    2924 */
    30 class LSAPP_LiteSurveys_Integration {
    31 
    32    
    33 
    34     /**
    35      * Initializes our plugin
     25class LSAPP_LiteSurveys {
     26
     27    /**
     28     * Plugin version.
     29     *
     30     * @var string
     31     */
     32    const VERSION = '2.0.0';
     33
     34    /**
     35     * The single instance of the class.
     36     *
     37     * @var LSAPP_LiteSurveys
     38     */
     39    private static $instance = null;
     40
     41    /**
     42     * Plugin path.
     43     *
     44     * @var string
     45     */
     46    private $plugin_path;
     47
     48    /**
     49     * Plugin URL.
     50     *
     51     * @var string
     52     */
     53    private $plugin_url;
     54
     55    /**
     56     * Our REST API functionality.
     57     */
     58    private $rest_api;
     59
     60    /**
     61     * Cache time in seconds.
     62     *
     63     * @var int
     64     */
     65    const CACHE_TIME = 300;
     66
     67    /**
     68     * Main LSAPP_LiteSurveys Instance.
     69     *
     70     * Ensures only one instance of LSAPP_LiteSurveys is loaded or can be loaded.
    3671     *
    3772     * @since 1.0.0
    38      */
    39     public static function init() {
    40         self::load_hooks();
    41     }
    42 
    43     /**
    44      * Adds in any plugin-wide hooks
     73     * @return LSAPP_LiteSurveys Main instance
     74     */
     75    public static function get_instance() {
     76        if ( is_null( self::$instance ) ) {
     77            self::$instance = new self();
     78        }
     79        return self::$instance;
     80    }
     81
     82    /**
     83     * LSAPP_LiteSurveys Constructor.
     84     */
     85    private function __construct() {
     86        $this->plugin_path = plugin_dir_path( __FILE__ );
     87        $this->plugin_url  = plugin_dir_url( __FILE__ );
     88
     89        // Include the REST API class
     90        require_once $this->plugin_path . 'includes/class-rest-api.php';
     91        $this->rest_api = new LSAPP_REST_API();
     92
     93        $this->init_hooks();
     94    }
     95
     96    /**
     97     * Initialize WordPress hooks.
    4598     *
    4699     * @since 1.0.0
    47100     */
    48     public static function load_hooks() {
    49         if (is_admin()) {
    50             add_action( 'admin_menu', array( __CLASS__, 'setup_admin_menu' ) );
    51             add_action( 'admin_init', array( __CLASS__, 'admin_init' ) );
     101    private function init_hooks() {
     102        // Setting up plugin upon activation.
     103        register_activation_hook( __FILE__, array( $this, 'activate_plugin' ) );
     104
     105        // Load translations.
     106        add_action( 'init', array( $this, 'load_textdomain' ) );
     107
     108        // Add Admin code.
     109        add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
     110        add_action( 'admin_post_save_survey', array( $this, 'handle_save_survey' ) );
     111        add_action( 'admin_post_delete_survey', array( $this, 'handle_delete_survey' ) );
     112        add_action( 'admin_post_delete_submission', array( $this, 'handle_delete_submission' ) );
     113        add_action( 'admin_notices', array( $this, 'display_admin_notices' ) );
     114        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
     115        add_filter( 'plugin_action_links', array( $this, 'plugin_action_links' ), 10, 2 );
     116
     117        // Add REST API endpoints.
     118        add_action( 'rest_api_init', array( $this->rest_api, 'register_rest_routes' ) );
     119
     120        // Add frontend script.
     121        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
     122    }
     123
     124    /**
     125     * Activate plugin and create database tables.
     126     *
     127     * @since 1.0.0
     128     */
     129    public function activate_plugin() {
     130        global $wpdb;
     131
     132        if ( ! current_user_can( 'activate_plugins' ) ) {
     133            return;
     134        }
     135
     136        $charset_collate = $wpdb->get_charset_collate();
     137
     138        $this->create_database_tables( $charset_collate );
     139
     140        add_option( 'lsapp_litesurveys_version', self::VERSION );
     141    }
     142
     143    /**
     144     * Create plugin database tables.
     145     *
     146     * @since 1.0.0
     147     * @param string $charset_collate Database charset and collation.
     148     */
     149    public function create_database_tables( $charset_collate ) {
     150        global $wpdb;
     151
     152        // Create surveys table.
     153        $sql_surveys = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_surveys (
     154            id bigint(20) NOT NULL AUTO_INCREMENT,
     155            name varchar(100) NOT NULL,
     156            active tinyint(1) DEFAULT 0,
     157            submit_message text NOT NULL,
     158            targeting_settings json DEFAULT NULL,
     159            appearance_settings json DEFAULT NULL,
     160            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     161            updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     162            deleted_at timestamp NULL DEFAULT NULL,
     163            PRIMARY KEY (id)
     164        ) $charset_collate;";
     165
     166        // Create questions table.
     167        $sql_questions = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_questions (
     168            id bigint(20) NOT NULL AUTO_INCREMENT,
     169            survey_id bigint(20) NOT NULL,
     170            type varchar(25) NOT NULL,
     171            content varchar(100) NOT NULL,
     172            answers json DEFAULT NULL,
     173            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     174            updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     175            deleted_at timestamp NULL DEFAULT NULL,
     176            PRIMARY KEY (id),
     177            FOREIGN KEY (survey_id) REFERENCES {$wpdb->prefix}litesurveys_surveys(id) ON DELETE CASCADE
     178        ) $charset_collate;";
     179
     180        // Create submissions table.
     181        $sql_submissions = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_submissions (
     182            id bigint(20) NOT NULL AUTO_INCREMENT,
     183            survey_id bigint(20) NOT NULL,
     184            page varchar(255) DEFAULT NULL,
     185            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     186            deleted_at timestamp NULL DEFAULT NULL,
     187            PRIMARY KEY (id),
     188            FOREIGN KEY (survey_id) REFERENCES {$wpdb->prefix}litesurveys_surveys(id) ON DELETE CASCADE
     189        ) $charset_collate;";
     190
     191        // Create responses table.
     192        $sql_responses = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}litesurveys_responses (
     193            id bigint(20) NOT NULL AUTO_INCREMENT,
     194            submission_id bigint(20) NOT NULL,
     195            question_id bigint(20) NOT NULL,
     196            content text NOT NULL,
     197            created_at timestamp DEFAULT CURRENT_TIMESTAMP,
     198            deleted_at timestamp NULL DEFAULT NULL,
     199            PRIMARY KEY (id),
     200            FOREIGN KEY (submission_id) REFERENCES {$wpdb->prefix}litesurveys_submissions(id) ON DELETE CASCADE,
     201            FOREIGN KEY (question_id) REFERENCES {$wpdb->prefix}litesurveys_questions(id) ON DELETE CASCADE
     202        ) $charset_collate;";
     203
     204        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
     205        dbDelta( $sql_surveys );
     206        dbDelta( $sql_questions );
     207        dbDelta( $sql_submissions );
     208        dbDelta( $sql_responses );
     209    }
     210
     211    /**
     212     * Load plugin text domain for translations.
     213     *
     214     * @since 1.0.0
     215     */
     216    public function load_textdomain() {
     217        load_plugin_textdomain(
     218            'litesurveys',
     219            false,
     220            dirname( plugin_basename( __FILE__ ) ) . '/languages'
     221        );
     222    }
     223
     224    /**
     225     * Enqueue admin scripts and styles.
     226     *
     227     * @since 1.0.0
     228     * @param string $hook The current admin page.
     229     */
     230    public function enqueue_admin_assets( $hook ) {
     231        if ( false === strpos( $hook, 'litesurveys' ) ) {
     232            return;
     233        }
     234
     235        $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : 'list';
     236
     237        if ( 'edit' === $action || 'new' === $action ) {
     238            $suffix = $this->get_asset_suffix();
     239            wp_enqueue_style(
     240                'litesurveys-admin',
     241                $this->plugin_url . "resources/css/admin{$suffix}.css",
     242                array(),
     243                self::VERSION
     244            );
     245
     246            wp_enqueue_script(
     247                'litesurveys-admin',
     248                $this->plugin_url . "resources/js/admin{$suffix}.js",
     249                array( 'jquery' ),
     250                self::VERSION,
     251                true
     252            );
     253        }
     254    }
     255
     256    /**
     257     * Add admin menu items.
     258     *
     259     * @since 1.0.0
     260     */
     261    public function add_admin_menu() {
     262        add_menu_page(
     263            'LiteSurveys',
     264            'LiteSurveys',
     265            'manage_options',
     266            'LSAPP_litesurveys',
     267            array( $this, 'render_admin_page' ),
     268            'dashicons-chart-bar',
     269            30
     270        );
     271    }
     272
     273    /**
     274     * Render the admin page content.
     275     *
     276     * @since 1.0.0
     277     */
     278    public function render_admin_page() {
     279        $this->verify_admin_access();
     280
     281        global $wpdb;
     282
     283        $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : 'list';
     284
     285        switch ( $action ) {
     286            case 'view-responses':
     287                $survey_id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
     288                if ( ! $survey_id ) {
     289                    wp_die( esc_html__( 'Invalid survey ID.', 'litesurveys' ) );
     290                }
     291
     292                // Get survey data with question.
     293                $survey = $wpdb->get_row(
     294                    $wpdb->prepare(
     295                        "SELECT s.*, q.content as question_content
     296                        FROM {$wpdb->prefix}litesurveys_surveys s
     297                        LEFT JOIN {$wpdb->prefix}litesurveys_questions q ON s.id = q.survey_id
     298                        WHERE s.id = %d AND s.deleted_at IS NULL",
     299                        $survey_id
     300                    )
     301                );
     302
     303                if ( ! $survey ) {
     304                    wp_die( esc_html__( 'Survey not found.', 'litesurveys' ) );
     305                }
     306
     307                // Pagination settings.
     308                $per_page     = 20;
     309                $current_page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1;
     310                $offset       = ( $current_page - 1 ) * $per_page;
     311
     312                // Get total count for pagination.
     313                $total_items = $wpdb->get_var(
     314                    $wpdb->prepare(
     315                        "SELECT COUNT(*)
     316                        FROM {$wpdb->prefix}litesurveys_submissions s
     317                        WHERE s.survey_id = %d AND s.deleted_at IS NULL",
     318                        $survey_id
     319                    )
     320                );
     321
     322                // Get submissions with responses.
     323                $submissions = $wpdb->get_results(
     324                    $wpdb->prepare(
     325                        "SELECT s.id, s.created_at, s.page, r.content as response
     326                        FROM {$wpdb->prefix}litesurveys_submissions s
     327                        LEFT JOIN {$wpdb->prefix}litesurveys_responses r ON s.id = r.submission_id
     328                        WHERE s.survey_id = %d AND s.deleted_at IS NULL
     329                        ORDER BY s.created_at DESC
     330                        LIMIT %d OFFSET %d",
     331                        $survey_id,
     332                        $per_page,
     333                        $offset
     334                    )
     335                );
     336
     337                // Calculate pagination values.
     338                $total_pages = ceil( $total_items / $per_page );
     339
     340                include $this->plugin_path . 'views/admin/survey-submissions.php';
     341                break;
     342
     343            case 'edit':
     344            case 'new':
     345                // Get survey data if editing.
     346                $survey_id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
     347                $survey    = null;
     348
     349                if ( $survey_id ) {
     350                    $survey = $wpdb->get_row(
     351                        $wpdb->prepare(
     352                            "SELECT * FROM {$wpdb->prefix}litesurveys_surveys WHERE id = %d AND deleted_at IS NULL",
     353                            $survey_id
     354                        )
     355                    );
     356
     357                    if ( $survey ) {
     358                        $question = $wpdb->get_row(
     359                            $wpdb->prepare(
     360                                "SELECT * FROM {$wpdb->prefix}litesurveys_questions WHERE survey_id = %d AND deleted_at IS NULL",
     361                                $survey_id
     362                            )
     363                        );
     364
     365                        if ( $question ) {
     366                            $survey->question         = $question;
     367                            $survey->question->answers = json_decode( $question->answers );
     368                        }
     369
     370                        $survey->targeting_settings   = json_decode( $survey->targeting_settings );
     371                        $survey->appearance_settings = json_decode( $survey->appearance_settings );
     372                    }
     373                }
     374
     375                // Set defaults for new survey.
     376                if ( ! $survey ) {
     377                    $survey = (object) array(
     378                        'name'                => '',
     379                        'active'             => false,
     380                        'submit_message'     => 'Thanks! I appreciate you taking the time to respond.',
     381                        'targeting_settings' => (object) array(
     382                            'targets'  => (object) array(
     383                                'show'     => 'all',
     384                                'includes' => array(),
     385                                'excludes' => array(),
     386                            ),
     387                            'trigger'  => array(
     388                                (object) array(
     389                                    'type'        => 'auto',
     390                                    'auto_timing' => 5,
     391                                ),
     392                            ),
     393                        ),
     394                        'appearance_settings' => (object) array(
     395                            'horizontal_position' => 'right',
     396                        ),
     397                        'question'           => (object) array(
     398                            'type'    => 'multiple-choice',
     399                            'content' => '',
     400                            'answers' => array( '', '', '' ),
     401                        ),
     402                    );
     403                }
     404                include $this->plugin_path . 'views/admin/survey-edit.php';
     405                break;
     406
     407            default:
     408                $surveys = $wpdb->get_results(
     409                    "SELECT * FROM {$wpdb->prefix}litesurveys_surveys WHERE deleted_at IS NULL ORDER BY created_at DESC"
     410                );
     411                include $this->plugin_path . 'views/admin/surveys-admin.php';
     412                break;
     413        }
     414    }
     415
     416    /**
     417     * Handle saving survey data.
     418     *
     419     * @since 1.0.0
     420     */
     421    public function handle_save_survey() {
     422        $this->verify_admin_access();
     423
     424        check_admin_referer( 'save_survey', 'survey_nonce' );
     425
     426        global $wpdb;
     427
     428        $survey_id = isset( $_POST['survey_id'] ) ? absint( $_POST['survey_id'] ) : 0;
     429        $save_type = isset( $_POST['save_type'] ) ? sanitize_text_field( wp_unslash( $_POST['save_type'] ) ) : 'draft';
     430
     431        try {
     432            // Validate required fields.
     433            if ( empty( $_POST['survey_name'] ) ) {
     434                throw new Exception( __( 'Survey name is required.', 'litesurveys' ) );
     435            }
     436
     437            if ( empty( $_POST['question_content'] ) ) {
     438                throw new Exception( __( 'Survey question is required.', 'litesurveys' ) );
     439            }
     440
     441            // Prepare targeting settings.
     442            $targeting_settings = array(
     443                'targets' => array(
     444                    'show'     => sanitize_text_field( wp_unslash( $_POST['targeting_show'] ) ),
     445                    'includes' => isset( $_POST['includes'] ) ?
     446                        array_map( 'sanitize_text_field', wp_unslash( $_POST['includes'] ) ) : array(),
     447                    'excludes' => isset( $_POST['excludes'] ) ?
     448                        array_map( 'sanitize_text_field', wp_unslash( $_POST['excludes'] ) ) : array(),
     449                ),
     450                'trigger' => array(
     451                    array(
     452                        'type'        => sanitize_text_field( wp_unslash( $_POST['trigger_type'] ) ),
     453                        'auto_timing' => absint( $_POST['auto_timing'] ),
     454                    ),
     455                ),
     456            );
     457
     458            // Prepare appearance settings.
     459            $appearance_settings = array(
     460                'horizontal_position' => sanitize_text_field( wp_unslash( $_POST['horizontal_position'] ) ),
     461            );
     462
     463            // Sanitize JSON data.
     464            $targeting_json   = $this->sanitize_json_data( $targeting_settings );
     465            $appearance_json = $this->sanitize_json_data( $appearance_settings );
     466
     467            if ( false === $targeting_json || false === $appearance_json ) {
     468                throw new Exception( __( 'Invalid settings data.', 'litesurveys' ) );
     469            }
     470
     471            // Prepare survey data.
     472            $survey_data = array(
     473                'name'                => sanitize_text_field( wp_unslash( $_POST['survey_name'] ) ),
     474                'submit_message'      => sanitize_textarea_field( wp_unslash( $_POST['submit_message'] ) ),
     475                'active'             => ( 'publish' === $save_type ),
     476                'targeting_settings'  => $targeting_json,
     477                'appearance_settings' => $appearance_json,
     478            );
     479
     480            // Prepare answers for multiple choice.
     481            $answers = array();
     482            if ( 'multiple-choice' === $_POST['question_type'] && ! empty( $_POST['answers'] ) ) {
     483                $answers = array_filter( array_map( 'sanitize_text_field', wp_unslash( $_POST['answers'] ) ) );
     484                if ( count( $answers ) < 2 ) {
     485                    throw new Exception( __( 'Multiple choice questions must have at least 2 answers.', 'litesurveys' ) );
     486                }
     487            }
     488
     489            // Prepare question data.
     490            $question_data = array(
     491                'type'    => sanitize_text_field( wp_unslash( $_POST['question_type'] ) ),
     492                'content' => sanitize_textarea_field( wp_unslash( $_POST['question_content'] ) ),
     493                'answers' => $this->sanitize_json_data( $answers ),
     494            );
     495
     496            // Start transaction.
     497            $wpdb->query( 'START TRANSACTION' );
     498
     499            if ( $survey_id ) {
     500                // Update existing survey.
     501                $result = $wpdb->update(
     502                    $wpdb->prefix . 'litesurveys_surveys',
     503                    $survey_data,
     504                    array( 'id' => $survey_id )
     505                );
     506
     507                if ( false === $result ) {
     508                    throw new Exception( __( 'Failed to update survey.', 'litesurveys' ) );
     509                }
     510
     511                // Update or insert question.
     512                $existing_question = $wpdb->get_row(
     513                    $wpdb->prepare(
     514                        "SELECT id FROM {$wpdb->prefix}litesurveys_questions WHERE survey_id = %d AND deleted_at IS NULL",
     515                        $survey_id
     516                    )
     517                );
     518
     519                if ( $existing_question ) {
     520                    $result = $wpdb->update(
     521                        $wpdb->prefix . 'litesurveys_questions',
     522                        $question_data,
     523                        array( 'id' => $existing_question->id )
     524                    );
     525                    if ( false === $result ) {
     526                        throw new Exception( __( 'Failed to update survey question.', 'litesurveys' ) );
     527                    }
     528                } else {
     529                    $question_data['survey_id'] = $survey_id;
     530                    $result = $wpdb->insert( $wpdb->prefix . 'litesurveys_questions', $question_data );
     531                    if ( ! $result ) {
     532                        throw new Exception( __( 'Failed to create survey question.', 'litesurveys' ) );
     533                    }
     534                }
     535            } else {
     536                // Insert new survey.
     537                $result = $wpdb->insert( $wpdb->prefix . 'litesurveys_surveys', $survey_data );
     538                if ( ! $result ) {
     539                    throw new Exception( __( 'Failed to create survey.', 'litesurveys' ) );
     540                }
     541                $survey_id = $wpdb->insert_id;
     542
     543                // Insert question.
     544                $question_data['survey_id'] = $survey_id;
     545                $result = $wpdb->insert( $wpdb->prefix . 'litesurveys_questions', $question_data );
     546                if ( ! $result ) {
     547                    throw new Exception( __( 'Failed to create survey question.', 'litesurveys' ) );
     548                }
     549            }
     550
     551            // Commit transaction.
     552            $wpdb->query( 'COMMIT' );
     553
     554            // Clear caches after successful save.
     555            $this->clear_survey_caches();
     556
     557            // Build redirect arguments.
     558            $redirect_args = array(
     559                'page'    => 'LSAPP_litesurveys',
     560                'action'  => 'edit',
     561                'id'      => $survey_id,
     562                'message' => 'publish' === $save_type ? 'survey-published' : 'survey-saved',
     563            );
     564
     565            // Redirect with success message.
     566            wp_safe_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) );
     567            exit;
     568
     569        } catch ( Exception $e ) {
     570            // Rollback transaction.
     571            $wpdb->query( 'ROLLBACK' );
     572
     573            wp_safe_redirect(
     574                add_query_arg(
     575                    array(
     576                        'page'    => 'LSAPP_litesurveys',
     577                        'action'  => $survey_id ? 'edit' : 'new',
     578                        'id'      => $survey_id,
     579                        'message' => 'error',
     580                        'error'   => rawurlencode( $e->getMessage() ),
     581                    ),
     582                    admin_url( 'admin.php' )
     583                )
     584            );
     585            exit;
     586        }
     587    }
     588
     589    public function handle_delete_survey() {
     590        $this->verify_admin_access();
     591
     592        // Verify nonce
     593        $nonce = isset($_REQUEST['_wpnonce']) ? sanitize_text_field($_REQUEST['_wpnonce']) : '';
     594        $survey_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
     595
     596        if (!wp_verify_nonce($nonce, 'delete-survey_' . $survey_id)) {
     597            wp_die(__('Security check failed.', 'litesurveys'));
     598        }
     599
     600        if (!$survey_id) {
     601            wp_die(__('Invalid survey ID.', 'litesurveys'));
     602        }
     603
     604        global $wpdb;
     605
     606        try {
     607            // Start transaction
     608            $wpdb->query('START TRANSACTION');
     609
     610            // Soft delete the survey
     611            $result = $wpdb->update(
     612                $wpdb->prefix . 'litesurveys_surveys',
     613                array('deleted_at' => current_time('mysql')),
     614                array('id' => $survey_id),
     615                array('%s'),
     616                array('%d')
     617            );
     618
     619            if ($result === false) {
     620                throw new Exception(__('Failed to delete survey.', 'litesurveys'));
     621            }
     622
     623            // Soft delete associated questions
     624            $wpdb->update(
     625                $wpdb->prefix . 'litesurveys_questions',
     626                array('deleted_at' => current_time('mysql')),
     627                array('survey_id' => $survey_id),
     628                array('%s'),
     629                array('%d')
     630            );
     631
     632            // Commit transaction
     633            $wpdb->query('COMMIT');
     634
     635            // Clear caches after successful save
     636            $this->clear_survey_caches();
     637
     638            // Redirect with success message
     639            wp_safe_redirect(add_query_arg(
     640                array(
     641                    'page' => 'LSAPP_litesurveys',
     642                    'message' => 'survey-deleted'
     643                ),
     644                admin_url('admin.php')
     645            ));
     646            exit;
     647
     648        } catch (Exception $e) {
     649            // Rollback transaction
     650            $wpdb->query('ROLLBACK');
     651
     652            // Redirect with error message
     653            wp_safe_redirect(add_query_arg(
     654                array(
     655                    'page' => 'LSAPP_litesurveys',
     656                    'message' => 'error',
     657                    'error' => urlencode($e->getMessage())
     658                ),
     659                admin_url('admin.php')
     660            ));
     661            exit;
     662        }
     663    }
     664
     665    public function handle_delete_submission() {
     666        $this->verify_admin_access();
     667   
     668        // Verify nonce
     669        $nonce = isset($_REQUEST['_wpnonce']) ? sanitize_text_field($_REQUEST['_wpnonce']) : '';
     670        $submission_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
     671        $survey_id = isset($_REQUEST['survey_id']) ? intval($_REQUEST['survey_id']) : 0;
     672   
     673        if (!wp_verify_nonce($nonce, 'delete-submission_' . $submission_id)) {
     674            wp_die(__('Security check failed.', 'litesurveys'));
     675        }
     676   
     677        if (!$submission_id || !$survey_id) {
     678            wp_die(__('Invalid submission ID.', 'litesurveys'));
     679        }
     680   
     681        global $wpdb;
     682   
     683        try {
     684            // Start transaction
     685            $wpdb->query('START TRANSACTION');
     686   
     687            // Soft delete the submission
     688            $result = $wpdb->update(
     689                $wpdb->prefix . 'litesurveys_submissions',
     690                array('deleted_at' => current_time('mysql')),
     691                array('id' => $submission_id),
     692                array('%s'),
     693                array('%d')
     694            );
     695   
     696            if ($result === false) {
     697                throw new Exception(__('Failed to delete submission.', 'litesurveys'));
     698            }
     699   
     700            // Soft delete associated responses
     701            $wpdb->update(
     702                $wpdb->prefix . 'litesurveys_responses',
     703                array('deleted_at' => current_time('mysql')),
     704                array('submission_id' => $submission_id),
     705                array('%s'),
     706                array('%d')
     707            );
     708   
     709            // Commit transaction
     710            $wpdb->query('COMMIT');
     711   
     712            // Redirect with success message
     713            wp_safe_redirect(add_query_arg(
     714                array(
     715                    'page' => 'LSAPP_litesurveys',
     716                    'action' => 'view-responses',
     717                    'id' => $survey_id,
     718                    'message' => 'submission-deleted'
     719                ),
     720                admin_url('admin.php')
     721            ));
     722            exit;
     723   
     724        } catch (Exception $e) {
     725            // Rollback transaction
     726            $wpdb->query('ROLLBACK');
     727   
     728            // Redirect with error message
     729            wp_safe_redirect(add_query_arg(
     730                array(
     731                    'page' => 'LSAPP_litesurveys',
     732                    'action' => 'view-responses',
     733                    'id' => $survey_id,
     734                    'message' => 'error',
     735                    'error' => urlencode($e->getMessage())
     736                ),
     737                admin_url('admin.php')
     738            ));
     739            exit;
     740        }
     741    }
     742   
     743    /**
     744     * Display admin notices with proper escaping.
     745     */
     746    public function display_admin_notices() {
     747        if (!isset($_GET['page']) || 'LSAPP_litesurveys' !== $_GET['page']) {
     748            return;
     749        }
     750
     751        if (isset($_GET['message'])) {
     752            $message_type = sanitize_text_field($_GET['message']);
     753            $class = 'notice ';
     754            $message = '';
     755
     756            switch ($message_type) {
     757                case 'survey-published':
     758                    $class .= 'notice-success';
     759                    $message = __('Survey published successfully.', 'litesurveys');
     760                    break;
     761                case 'survey-unpublished':
     762                    $class .= 'notice-warning';
     763                    $message = __('Survey unpublished.', 'litesurveys');
     764                    break;
     765                case 'survey-saved':
     766                    $class .= 'notice-success';
     767                    $message = __('Survey saved successfully.', 'litesurveys');
     768                    break;
     769                case 'survey-deleted':
     770                    $class .= 'notice-success';
     771                    $message = __('Survey deleted successfully.', 'litesurveys');
     772                    break;
     773                case 'submission-deleted':
     774                    $class .= 'notice-success';
     775                    $message = __('Submission deleted successfully.', 'litesurveys');
     776                    break;
     777                case 'error':
     778                    $class .= 'notice-error';
     779                    $message = isset($_GET['error']) ?
     780                        sanitize_text_field(urldecode($_GET['error'])) :
     781                        __('An error occurred while processing your request.', 'litesurveys');
     782                    break;
     783                default:
     784                    return; // Unknown message type
     785            }
     786
     787            printf(
     788                '<div class="%1$s is-dismissible"><p>%2$s</p></div>',
     789                esc_attr($class),
     790                esc_html($message)
     791            );
     792        }
     793    }
     794
     795    public function enqueue_frontend_assets() {
     796        // Only load if there are active surveys
     797        if (!$this->has_active_surveys()) {
     798            return;
    52799        }
    53800       
    54         add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_script' ), 50 );
    55         add_filter( 'wp_script_attributes', array( __CLASS__, 'add_script_attributes' ) );
    56         add_filter( 'plugin_action_links', array( __CLASS__, 'plugin_action_links' ), 10, 2 );
    57     }
    58 
    59     /**
    60      * Code to be run during admin_init
    61      *
    62      * @since 1.0.0
    63      */
    64     public static function admin_init() {
    65         register_setting( 'LSAPP_litesurveys', 'LSAPP_litesurveys_settings' );
    66         add_settings_section(
    67             'LSAPP_litesurveys_settings_section',
    68             '',
    69             array( __CLASS__, 'litesurveys_settings_section_callback' ),
    70             'LSAPP_litesurveys'
     801        $suffix = $this->get_asset_suffix();
     802       
     803        wp_enqueue_style(
     804            'litesurveys-frontend',
     805            $this->plugin_url . "resources/css/frontend{$suffix}.css",
     806            array(),
     807            self::VERSION
    71808        );
    72         add_settings_field(
    73             'LSAPP_litesurveys_settings_site_id',
    74             'Site ID',
    75             array( __CLASS__, 'litesurveys_settings_site_id_callback' ),
    76             'LSAPP_litesurveys',
    77             'LSAPP_litesurveys_settings_section',
    78             array(
    79                 'label_for'         => 'site_id',
    80                 'class'             => 'wporg_row',
    81             )
     809       
     810        wp_enqueue_script(
     811            'litesurveys-frontend',
     812            $this->plugin_url . "resources/js/frontend{$suffix}.js",
     813            array(),
     814            self::VERSION,
     815            true
    82816        );
    83     }
    84 
    85     /**
    86      * Sets up our page in the admin menu
    87      *
    88      * @since 1.0.0
    89      */
    90     public static function setup_admin_menu() {
    91         add_options_page( 'LiteSurveys', 'LiteSurveys', 'manage_options', 'LSAPP_litesurveys', array( __CLASS__, 'generate_admin_page' ) );
    92     }
    93 
    94     /**
    95      * Callback for our main settings section
    96      *
    97      * @since 1.0.0
    98      */
    99     public static function litesurveys_settings_section_callback() {
    100         ?>
    101         <p>You will need to have an active <a href="https://litesurveys.com" target="_blank">LiteSurveys</a> account to use this plugin. Within your LiteSurveys account, go to the "Connect Website" page to get your Website ID.</p>
    102         <?php
    103     }
    104 
    105     /**
    106      * Callback for site ID settings field
    107      *
    108      * @since 1.0.0
    109      */
    110     public static function litesurveys_settings_site_id_callback($args) {
    111         $site_id = self::get_site_id();
    112         ?>
    113         <input id="<?php echo esc_attr( $args['label_for'] ); ?>" name="LSAPP_litesurveys_settings[<?php echo esc_attr( $args['label_for'] ); ?>]" type="text" value="<?php echo esc_attr( $site_id ); ?>">
    114         <p class="description">(Leave blank to disable)</p>
    115         <?php
    116     }
    117 
    118     /**
    119      * Generates our admin page
    120      *
    121      * @since 1.0.0
    122      */
    123     public static function generate_admin_page() {
    124         if ( ! current_user_can( 'manage_options' ) ) {
    125             return;
    126         }
    127 
    128         ?>
    129         <div class="wrap">
    130             <h1>LiteSurveys Integration</h1>
    131             <form action="options.php" method="post">
    132                 <?php
    133                 settings_fields( 'LSAPP_litesurveys' );
    134                 do_settings_sections( 'LSAPP_litesurveys' );
    135                 submit_button( 'Save Settings' );
    136                 ?>
    137             </form>
    138         </div>
    139         <?php
    140     }
    141 
    142     /**
    143      * Enqueues the LiteSurveys script
    144      *
    145      * @since 1.0.0
    146      */
    147     public static function enqueue_script() {
    148         if ( ! self::get_site_id() ) {
    149             return;
    150         }
    151         wp_enqueue_script( 'litesurveys', 'https://embeds.litesurveys.com/litesurveys.min.js', array(), LSAPP_PLUGIN_VERSION, array( 'strategy' => 'defer' ) );
    152     }
    153 
    154     /**
    155      * Filter the script attributes to add id and data-site-id attributes.
    156      *
    157      * @param array $attributes The script tag attributes.
    158      * @return array
    159      */
    160     public static function add_script_attributes( $attributes ) {
    161         if ( 'litesurveys-js' === $attributes['id'] ) {
    162             $attributes['data-site-id'] = self::get_site_id();
    163         }
    164         return $attributes;
    165     }
    166 
    167     /**
    168      * Retrieves the saved site id setting
    169      *
    170      * @since 1.0.0
    171      * @return string
    172      */
    173     private static function get_site_id() {
    174         return self::get_setting('site_id', '');
    175     }
    176 
    177     /**
    178      * Retrieves a specific plugin setting
    179      *
    180      * @since 1.0.0
    181      * @param string $setting Which setting to retrieve.
    182      * @param mixed  $default The value to return if setting does not exist.
    183      * @return mixed
    184      */
    185     private static function get_setting($setting, $default = false) {
    186         $settings = self::get_settings();
    187         if ( isset( $settings[$setting] ) ) {
    188             return $settings[$setting];
    189         }
    190 
    191         return $default;
    192     }
    193 
    194     /**
    195      * Retrieves our plugin settings
    196      *
    197      * @since 1.0.0
    198      * @return array Our settings
    199      */
    200     private static function get_settings() {
    201         $settings = get_option( 'LSAPP_litesurveys_settings', [] );
    202         if (! is_array( $settings ) ) {
    203             $settings = [];
    204         }
    205 
    206         return $settings;
     817       
     818        wp_localize_script('litesurveys-frontend', 'liteSurveysSettings', array(
     819            'ajaxUrl' => rest_url('litesurveys/v1/')
     820        ));
    207821    }
    208822
     
    219833            'litesurveys-wordpress-plugin/litesurveys-wordpress-plugin.php'
    220834        ];
    221         if (in_array($plugin_file, $plugin_files)) {
    222             $settings_url = sprintf( '<a href="%s">Settings</a>', esc_url( admin_url( 'options-general.php?page=LSAPP_litesurveys' ) ) );
    223             $actions = array_merge( ['litesurveys_settings' => $settings_url], $actions) ;
     835        if ( in_array( $plugin_file, $plugin_files, true ) ) {
     836            $surveys_url = esc_url( admin_url( 'admin.php?page=LSAPP_litesurveys' ) );
     837            $surveys_link = sprintf( '<a href="%s">%s</a>', $surveys_url, __( 'Surveys', 'litesurveys' ) );
     838            $actions = array_merge( array( 'surveys' => $surveys_link ), $actions );
    224839        }
    225840        return $actions;
    226841    }
     842
     843    /**
     844     * Get the REST API instance.
     845     *
     846     * @since 2.0.0
     847     * @return LSAPP_REST_API The REST API instance.
     848     */
     849    public function get_rest_api() {
     850        return $this->rest_api;
     851    }
     852
     853    /**
     854     * Check if there are any active surveys.
     855     *
     856     * @return bool True if there are active surveys, false otherwise.
     857     */
     858    private function has_active_surveys() {
     859        $cache_key = 'litesurveys_has_active';
     860        $has_active = get_transient($cache_key);
     861
     862        if (false === $has_active) {
     863            global $wpdb;
     864           
     865            $has_active = (bool)$wpdb->get_var($wpdb->prepare(
     866                "SELECT EXISTS(
     867                    SELECT 1 FROM {$wpdb->prefix}litesurveys_surveys
     868                    WHERE active = %d AND deleted_at IS NULL
     869                )",
     870                1
     871            ));
     872           
     873            set_transient($cache_key, $has_active ? '1' : '0', self::CACHE_TIME);
     874        }
     875
     876        return $has_active === '1';
     877    }
     878
     879    private function verify_admin_access() {
     880        if (!current_user_can('manage_options')) {
     881            wp_die(
     882                esc_html__('You do not have sufficient permissions to access this page.', 'litesurveys'),
     883                403
     884            );
     885        }
     886    }
     887
     888    /**
     889     * Sanitize JSON data before storage.
     890     *
     891     * @param array $data The data to be sanitized and stored as JSON.
     892     * @return string|false Sanitized JSON string or false on failure.
     893     */
     894    private function sanitize_json_data($data) {
     895        if (!is_array($data)) {
     896            return false;
     897        }
     898
     899        array_walk_recursive($data, function(&$value) {
     900            if (is_string($value)) {
     901                $value = sanitize_text_field($value);
     902            } elseif (is_int($value)) {
     903                $value = intval($value);
     904            } elseif (is_float($value)) {
     905                $value = floatval($value);
     906            } elseif (is_bool($value)) {
     907                $value = (bool)$value;
     908            } else {
     909                $value = '';
     910            }
     911        });
     912
     913        return wp_json_encode($data);
     914    }
     915
     916    /**
     917     * Get the suffix for asset files (.min in production, empty in debug).
     918     *
     919     * @return string
     920     */
     921    private function get_asset_suffix() {
     922        return defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min';
     923    }
     924
     925    /**
     926     * Clear survey caches when a survey is updated.
     927     */
     928    private function clear_survey_caches() {
     929        delete_transient('litesurveys_active_surveys');
     930        delete_transient('litesurveys_has_active');
     931    }
    227932}
    228933
    229 LSAPP_LiteSurveys_Integration::init();
     934// Initialize plugin
     935LSAPP_LiteSurveys::get_instance();
    230936?>
  • litesurveys/trunk/readme.txt

    r3130242 r3183478  
    44Requires at least: 6.0
    55Tested up to: 6.6.1
    6 Stable tag: 1.0.3
     6Stable tag: 2.0.0
    77Requires PHP: 8.0
    88License: GPLv3
     
    1313== Description ==
    1414
    15 Easily hear from your users by adding pre-sale surveys, post-sale surveys, feedback surveys, and more to your website by integrating your site with [the LiteSurveys service](https://litesurveys.com/).
     15Learn from your site visitors using quick surveys.
    1616
    17 **Important Note**: This plugin requires a [LiteSurveys](https://litesurveys.com) plan to create surveys and collect responses. Get started for free today!
     17== Description ==
    1818
    19 ## Collect Feedback and Ideas From Your Site Visitors
     19LiteSurveys is a lightweight WordPress plugin that helps you gather feedback from your website visitors through simple, unobtrusive surveys. With LiteSurveys, you can easily create and manage single-question surveys that appear as slide-in popups on your website.
    2020
    21 You can use LiteSurveys to:
     21## Key Features
    2222
    23 * **Conduct User Research** - Using LiteSurveys small slide-in surveys, users can quickly provide information about who they are or what they are looking for.
    24 * **Identify Pain Points** - Adding in quick surveys during the sales funnel allows site visitors to provide feedback about concerns and issues.
    25 * **Get Marketing Insights** - Using surveys at key points during the user experience can provide details about the user’s decisions and thoughts which can influence your copy and strategy for your marketing efforts.
    26 * And much more!
     23* **Simple Survey Creation** - Create surveys directly in your WordPress admin with just a few clicks
     24* **Multiple Question Types** - Choose between multiple choice or open-ended questions
     25* **Customizable Display** - Control when and where your surveys appear
     26* **Easy Response Management** - View and manage survey responses right in your WordPress dashboard
     27* **Unobtrusive Design** - Surveys appear as subtle slide-in popups that won't disrupt the user experience
     28* **Mobile Friendly** - Surveys work great on all devices
     29
     30## Use LiteSurveys to:
     31
     32* **Conduct User Research** - Using small slide-in surveys, users can quickly provide information about who they are or what they are looking for
     33* **Identify Pain Points** - Adding quick surveys during the sales funnel allows site visitors to provide feedback about concerns and issues
     34* **Get Marketing Insights** - Using surveys at key points during the user experience can provide details about the user's decisions and thoughts which can influence your copy and strategy
     35* **Gather Customer Feedback** - Collect real feedback from actual users about their experience with your site or product
    2736
    2837## Some Features of LiteSurveys:
    2938
    30 * **Question Type Variety** - Select from a variety question types including multiple choice, open answer, and more.
    31 * **Analyze Results** - Easily see how individual site visitors respond but also analyze how all site visitors are responding.
    32 * **Control Timing** - LiteSurveys has many options available for controlling when the survey appears.
    33 * **Target Specific Pages** - Show your survey on your whole website or only on specific pages.
    34 * **Export Results** - Export your survey responses so you can use them in spreadsheets, databases, other tools, and more.
    35 
    36 Ready to get started? [Create your account](https://litesurveys.com/) for free to create your first survey within minutes!
     39* **Question Type Variety** - Select from multiple choice or open-ended questions to best suit your needs
     40* **Timing Control** - Set when your survey should appear to visitors
     41* **Position Control** - Choose whether your survey appears in the bottom left or bottom right
     42* **Active/Inactive States** - Easily activate or deactivate surveys as needed
     43* **Submissions Per Page** - Easily see what page the website visitor was on when they submitted the survey
    3744
    3845== Installation ==
     
    4754
    48551. An example LiteSurveys slide-in survey.
    49 2. The settings page within the WordPress plugin.
     562. Surveys admin page showing your surveys, which are active, and how many submissions each has.
    5057
    5158== Changelog ==
     59
     60= 2.0.0 (November 6, 2024) =
     61
     62* Major rewrite of plugin to be standalone instead of requiring LiteSurveys service
     63* Add ability to create and manage surveys directly in WordPress admin
     64* Add support for multiple choice and open-ended questions
     65* Add support for customizing survey timing and position
    5266
    5367= 1.0.3 (August 2, 2024) =
  • litesurveys/trunk/uninstall.php

    r3013451 r3183478  
    11<?php
    2 // if uninstall.php is not called by WordPress, die
    3 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    4     die;
     2// If uninstall not called from WordPress, exit
     3if (!defined('WP_UNINSTALL_PLUGIN')) {
     4    exit;
    55}
    66
    7 delete_option( 'LSAPP_litesurveys_settings' );
     7global $wpdb;
     8
     9// Drop tables in reverse order of dependencies
     10$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_responses");
     11$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_submissions");
     12$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_questions");
     13$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}litesurveys_surveys");
     14
     15// Delete options
     16delete_option('lsapp_litesurveys_version');
     17delete_option('LSAPP_litesurveys_settings');
Note: See TracChangeset for help on using the changeset viewer.