current_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'dashboard';
// AJAX handlers
add_action('wp_ajax_tpoll_save_poll', array($this, 'ajax_save_poll'));
add_action('wp_ajax_tpoll_delete_poll', array($this, 'ajax_delete_poll'));
add_action('wp_ajax_tpoll_duplicate_poll', array($this, 'ajax_duplicate_poll'));
add_action('wp_ajax_tpoll_reset_votes', array($this, 'ajax_reset_votes'));
}
/**
* Add admin menu
*/
public function add_menu() {
// Main menu
add_menu_page(
__('3task Polls', '3task-polls'),
__('3task Polls', '3task-polls'),
'create_tpoll',
'tpoll',
array($this, 'render_admin_page'),
'dashicons-chart-bar',
30
);
}
/**
* Get available tabs
*/
private function get_tabs() {
return array(
'dashboard' => array(
'title' => __('Dashboard', '3task-polls'),
'icon' => 'dashicons-dashboard'
),
'polls' => array(
'title' => __('Polls', '3task-polls'),
'icon' => 'dashicons-chart-bar'
),
'results' => array(
'title' => __('Results', '3task-polls'),
'icon' => 'dashicons-analytics'
),
'settings' => array(
'title' => __('Settings', '3task-polls'),
'icon' => 'dashicons-admin-settings'
),
);
}
/**
* Render main admin page with tabs
*/
public function render_admin_page() {
$tabs = $this->get_tabs();
$tab_icons = array(
'dashboard' => '🏠',
'polls' => '📊',
'results' => '📈',
'settings' => '⚙️',
);
$poll_count = TPoll_Poll::get_count();
?>
$tab ) :
$active_class = ( $this->current_tab === $tab_key ) ? 'nav-tab-active' : '';
$url = admin_url( 'admin.php?page=tpoll&tab=' . $tab_key );
$icon = isset( $tab_icons[ $tab_key ] ) ? $tab_icons[ $tab_key ] : '';
?>
0 ) : ?>
render_tab_content(); ?>
current_tab) {
case 'dashboard':
$this->render_dashboard();
break;
case 'polls':
$this->render_polls();
break;
case 'results':
$this->render_results();
break;
case 'settings':
$this->render_settings();
break;
default:
$this->render_dashboard();
}
}
/**
* Render dashboard tab
*/
private function render_dashboard() {
$stats = $this->get_poll_stats();
?>
render_edit_poll();
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- View routing, read-only.
if (isset($_GET['action']) && $_GET['action'] === 'new') {
$this->render_new_poll();
return;
}
// List all polls
$polls = $this->get_polls();
?>
type ) ); ?>
question, 15 ) ); ?>
' . esc_html__('Poll not found.', '3task-polls') . '
';
return;
}
require_once TPOLL_PLUGIN_DIR . 'admin/views/poll-edit.php';
}
/**
* Render new poll form
*/
private function render_new_poll() {
$poll = new TPoll_Poll();
require_once TPOLL_PLUGIN_DIR . 'admin/views/poll-edit.php';
}
/**
* Render results tab
*/
private function render_results() {
$polls = $this->get_polls();
?>
id );
if ( ! $results || empty( $results['total_voters'] ) ) {
continue;
}
?>
get_var("SELECT COUNT(*) FROM {$wpdb->prefix}tpoll_polls");
$published_polls = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}tpoll_polls WHERE status = 'published'");
$draft_polls = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}tpoll_polls WHERE status = 'draft'");
$total_votes = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}tpoll_votes");
// phpcs:enable
return array(
'total_polls' => $total_polls ?: 0,
'published_polls' => $published_polls ?: 0,
'draft_polls' => $draft_polls ?: 0,
'total_votes' => $total_votes ?: 0
);
}
/**
* Get polls
*/
private function get_polls() {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table.
$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}tpoll_polls ORDER BY created_at DESC");
return $results ?: array();
}
/**
* Enqueue admin assets
*/
public function enqueue_assets($hook) {
// Only on our pages
if (strpos($hook, 'tpoll') === false) {
return;
}
wp_enqueue_style(
'tpoll-admin',
TPOLL_PLUGIN_URL . 'assets/css/admin.css',
array(),
TPOLL_VERSION
);
wp_enqueue_script(
'tpoll-admin',
TPOLL_PLUGIN_URL . 'assets/js/admin.js',
array('jquery'),
TPOLL_VERSION,
true
);
wp_localize_script('tpoll-admin', 'tpollAdmin', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('tpoll_admin'),
'strings' => array(
'confirmDelete' => __('Are you sure you want to delete this poll? This cannot be undone.', '3task-polls'),
'confirmReset' => __('Are you sure you want to reset all votes for this poll?', '3task-polls'),
'saved' => __('Poll saved successfully!', '3task-polls'),
'error' => __('An error occurred. Please try again.', '3task-polls'),
'addAnswer' => __('Add Answer', '3task-polls'),
'removeAnswer' => __('Remove', '3task-polls')
)
));
// Media uploader for image polls (Pro feature indicator)
wp_enqueue_media();
// Color picker
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('wp-color-picker');
}
/**
* Handle admin actions
*/
public function handle_actions() {
if (!current_user_can('edit_tpoll')) {
return;
}
// Handle single poll delete
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['poll'])) {
if (!current_user_can('delete_tpoll')) {
wp_die(esc_html__('Permission denied.', '3task-polls'));
}
$tpoll_poll_id = absint($_GET['poll']);
if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'tpoll_delete_' . $tpoll_poll_id)) {
wp_die(esc_html__('Security check failed', '3task-polls'));
}
$poll = TPoll_Poll::get($tpoll_poll_id);
if ($poll) {
$poll->delete();
}
wp_safe_redirect(admin_url('admin.php?page=tpoll&tab=polls&deleted=1&tpoll_notice=' . wp_create_nonce('tpoll_admin_notice')));
exit;
}
// Handle bulk actions
if (isset($_POST['action']) && isset($_POST['poll_ids']) && is_array($_POST['poll_ids'])) {
if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'] ?? '')), 'bulk-tpoll')) {
wp_die(esc_html__('Security check failed', '3task-polls'));
}
$action = sanitize_text_field(wp_unslash($_POST['action']));
$poll_ids = array_map('absint', wp_unslash($_POST['poll_ids']));
$tpoll_notice_nonce = wp_create_nonce('tpoll_admin_notice');
switch ($action) {
case 'delete':
foreach ($poll_ids as $poll_id) {
$poll = TPoll_Poll::get($poll_id);
if ($poll) {
$poll->delete();
}
}
wp_safe_redirect(admin_url('admin.php?page=tpoll&tab=polls&deleted=' . count($poll_ids) . '&tpoll_notice=' . $tpoll_notice_nonce));
exit;
case 'publish':
foreach ($poll_ids as $poll_id) {
$poll = TPoll_Poll::get($poll_id);
if ($poll) {
$poll->status = 'published';
$poll->save();
}
}
wp_safe_redirect(admin_url('admin.php?page=tpoll&tab=polls&updated=' . count($poll_ids) . '&tpoll_notice=' . $tpoll_notice_nonce));
exit;
case 'close':
foreach ($poll_ids as $poll_id) {
$poll = TPoll_Poll::get($poll_id);
if ($poll) {
$poll->status = 'closed';
$poll->save();
}
}
wp_safe_redirect(admin_url('admin.php?page=tpoll&tab=polls&updated=' . count($poll_ids) . '&tpoll_notice=' . $tpoll_notice_nonce));
exit;
}
}
}
/**
* Register settings
*/
public function register_settings() {
register_setting('tpoll_settings_group', 'tpoll_settings', array(
'sanitize_callback' => array($this, 'sanitize_settings')
));
}
/**
* Sanitize settings
*/
public function sanitize_settings($input) {
$sanitized = array();
$sanitized['vote_restriction'] = sanitize_text_field($input['vote_restriction'] ?? 'ip');
$sanitized['show_results'] = sanitize_text_field($input['show_results'] ?? 'after_vote');
$sanitized['allow_revote'] = !empty($input['allow_revote']);
$sanitized['results_bar_color'] = sanitize_hex_color($input['results_bar_color'] ?? '#4F46E5');
$sanitized['results_bar_bg'] = sanitize_hex_color($input['results_bar_bg'] ?? '#E5E7EB');
$sanitized['button_text'] = sanitize_text_field($input['button_text'] ?? __('Vote', '3task-polls'));
$sanitized['results_text'] = sanitize_text_field($input['results_text'] ?? __('View Results', '3task-polls'));
$sanitized['voted_text'] = sanitize_text_field($input['voted_text'] ?? __('You have already voted.', '3task-polls'));
$sanitized['closed_text'] = sanitize_text_field($input['closed_text'] ?? __('This poll is closed.', '3task-polls'));
$sanitized['theme'] = sanitize_text_field($input['theme'] ?? 'default');
$sanitized['dark_mode'] = sanitize_text_field($input['dark_mode'] ?? 'auto');
$sanitized['animation'] = sanitize_text_field($input['animation'] ?? 'fade');
$sanitized['show_voters_count'] = !empty($input['show_voters_count']);
$sanitized['randomize_answers'] = !empty($input['randomize_answers']);
return $sanitized;
}
/**
* Render polls list page
*/
public function render_polls_page() {
// Check if editing
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- View routing, read-only.
if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['poll'])) {
$this->render_edit_page();
return;
}
require_once TPOLL_PLUGIN_DIR . 'admin/views/polls-list.php';
}
/**
* Render edit page
*/
public function render_edit_page() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Poll ID for editing, read-only.
$poll_id = isset($_GET['poll']) ? absint($_GET['poll']) : 0;
$poll = $poll_id ? TPoll_Poll::get($poll_id) : new TPoll_Poll();
require_once TPOLL_PLUGIN_DIR . 'admin/views/poll-edit.php';
}
/**
* Render settings page
*/
public function render_settings_page() {
require_once TPOLL_PLUGIN_DIR . 'admin/views/settings.php';
}
/**
* AJAX: Save poll
*/
public function ajax_save_poll() {
check_ajax_referer('tpoll_admin', 'nonce');
if (!current_user_can('create_tpoll')) {
wp_send_json_error(array('message' => __('Permission denied.', '3task-polls')));
}
$poll_id = isset($_POST['poll_id']) ? absint($_POST['poll_id']) : 0;
$poll = $poll_id ? TPoll_Poll::get($poll_id) : new TPoll_Poll();
if (!$poll) {
$poll = new TPoll_Poll();
}
// Basic fields
$poll->title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '';
$poll->question = isset($_POST['question']) ? sanitize_textarea_field(wp_unslash($_POST['question'])) : '';
$poll->type = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : 'single';
$poll->status = isset($_POST['status']) ? sanitize_text_field(wp_unslash($_POST['status'])) : 'draft';
// Dates
$poll->start_date = !empty($_POST['start_date']) ? sanitize_text_field(wp_unslash($_POST['start_date'])) : null;
$poll->end_date = !empty($_POST['end_date']) ? sanitize_text_field(wp_unslash($_POST['end_date'])) : null;
// Settings
$poll->settings = array(
'vote_restriction' => isset($_POST['vote_restriction']) ? sanitize_text_field(wp_unslash($_POST['vote_restriction'])) : 'ip',
'show_results' => isset($_POST['show_results']) ? sanitize_text_field(wp_unslash($_POST['show_results'])) : 'after_vote',
'allow_revote' => !empty($_POST['allow_revote']),
'max_choices' => isset($_POST['max_choices']) ? absint($_POST['max_choices']) : 0,
'max_rating' => isset($_POST['max_rating']) ? absint($_POST['max_rating']) : 5,
'randomize_answers' => !empty($_POST['randomize_answers']),
'show_voters_count' => !empty($_POST['show_voters_count']),
'theme' => isset($_POST['theme']) ? sanitize_text_field(wp_unslash($_POST['theme'])) : 'default',
'dark_mode' => isset($_POST['dark_mode']) ? sanitize_text_field(wp_unslash($_POST['dark_mode'])) : 'auto'
);
// Styles
$poll->styles = array(
'bar_color' => isset($_POST['bar_color']) ? sanitize_hex_color(wp_unslash($_POST['bar_color'])) : '#4F46E5',
'bar_bg' => isset($_POST['bar_bg']) ? sanitize_hex_color(wp_unslash($_POST['bar_bg'])) : '#E5E7EB',
'text_color' => isset($_POST['text_color']) ? sanitize_hex_color(wp_unslash($_POST['text_color'])) : '#1F2937',
'bg_color' => isset($_POST['bg_color']) ? sanitize_hex_color(wp_unslash($_POST['bg_color'])) : '#FFFFFF'
);
// Save poll
$poll_id = $poll->save();
// Check for WP_Error (poll limit reached)
if (is_wp_error($poll_id)) {
wp_send_json_error(array('message' => $poll_id->get_error_message()));
}
if (!$poll_id) {
wp_send_json_error(array('message' => __('Error saving poll.', '3task-polls')));
}
// Save answers
$answers = array();
if (isset($_POST['answers']) && is_array($_POST['answers'])) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in loop below.
$raw_answers = wp_unslash($_POST['answers']);
foreach ($raw_answers as $answer) {
if (!empty($answer['text'])) {
$answers[] = array(
'text' => sanitize_text_field($answer['text']),
'image' => isset($answer['image']) ? esc_url_raw($answer['image']) : ''
);
}
}
}
// For rating/voting types, ensure at least one answer
if (in_array($poll->type, array('rating', 'voting')) && empty($answers)) {
$answers[] = array('text' => $poll->question, 'image' => '');
}
$poll->save_answers($answers);
wp_send_json_success(array(
'message' => __('Poll saved successfully!', '3task-polls'),
'poll_id' => $poll_id,
'redirect' => admin_url('admin.php?page=tpoll&tab=polls&action=edit&poll=' . $poll_id . '&saved=1')
));
}
/**
* AJAX: Delete poll
*/
public function ajax_delete_poll() {
check_ajax_referer('tpoll_admin', 'nonce');
if (!current_user_can('delete_tpoll')) {
wp_send_json_error(array('message' => __('Permission denied.', '3task-polls')));
}
$poll_id = isset($_POST['poll_id']) ? absint($_POST['poll_id']) : 0;
$poll = TPoll_Poll::get($poll_id);
if (!$poll) {
wp_send_json_error(array('message' => __('Poll not found.', '3task-polls')));
}
$poll->delete();
wp_send_json_success(array(
'message' => __('Poll deleted successfully!', '3task-polls')
));
}
/**
* AJAX: Duplicate poll
*/
public function ajax_duplicate_poll() {
check_ajax_referer('tpoll_admin', 'nonce');
if (!current_user_can('create_tpoll')) {
wp_send_json_error(array('message' => __('Permission denied.', '3task-polls')));
}
$poll_id = isset($_POST['poll_id']) ? absint($_POST['poll_id']) : 0;
$poll = TPoll_Poll::get($poll_id);
if (!$poll) {
wp_send_json_error(array('message' => __('Poll not found.', '3task-polls')));
}
$new_poll = $poll->duplicate();
wp_send_json_success(array(
'message' => __('Poll duplicated successfully!', '3task-polls'),
'poll_id' => $new_poll->id,
'redirect' => admin_url('admin.php?page=tpoll&tab=polls&action=edit&poll=' . $new_poll->id)
));
}
/**
* AJAX: Reset votes
*/
public function ajax_reset_votes() {
check_ajax_referer('tpoll_admin', 'nonce');
if (!current_user_can('manage_tpoll')) {
wp_send_json_error(array('message' => __('Permission denied.', '3task-polls')));
}
$poll_id = isset($_POST['poll_id']) ? absint($_POST['poll_id']) : 0;
$poll = TPoll_Poll::get($poll_id);
if (!$poll) {
wp_send_json_error(array('message' => __('Poll not found.', '3task-polls')));
}
// Delete votes
TPoll_Vote::delete_votes($poll_id);
// Reset answer vote counts
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table.
$wpdb->update(
$wpdb->prefix . 'tpoll_answers',
array('votes' => 0),
array('poll_id' => $poll_id),
array('%d'),
array('%d')
);
wp_send_json_success(array(
'message' => __('Votes reset successfully!', '3task-polls')
));
}
}