load_dependencies(); $this->init_hooks(); } /** * Load dependencies */ private function load_dependencies() { // Core classes require_once TPOLL_PLUGIN_DIR . 'includes/class-activator.php'; require_once TPOLL_PLUGIN_DIR . 'includes/class-deactivator.php'; require_once TPOLL_PLUGIN_DIR . 'includes/class-poll.php'; require_once TPOLL_PLUGIN_DIR . 'includes/class-vote.php'; require_once TPOLL_PLUGIN_DIR . 'includes/class-shortcode.php'; require_once TPOLL_PLUGIN_DIR . 'includes/class-feedback.php'; require_once TPOLL_PLUGIN_DIR . 'includes/class-quiz.php'; require_once TPOLL_PLUGIN_DIR . 'includes/class-survey.php'; // Admin classes if (is_admin()) { require_once TPOLL_PLUGIN_DIR . 'admin/class-admin.php'; } // Public classes require_once TPOLL_PLUGIN_DIR . 'public/class-public.php'; } /** * Initialize hooks */ private function init_hooks() { // Admin if (is_admin()) { $this->admin = new TPoll_Admin(); } // Public $this->public = new TPoll_Public(); // Shortcode new TPoll_Shortcode(); // Feedback below posts TPoll_Feedback::init(); // Quiz question type TPoll_Quiz::init(); // Questionnaire type TPoll_Survey::init(); // Bundled German translation, used until translate.wordpress.org provides one. add_action('init', array($this, 'load_textdomain'), 1); // Register block add_action('init', array($this, 'register_block')); add_action('enqueue_block_editor_assets', array($this, 'block_editor_data')); // Updates, retention and privacy add_action('plugins_loaded', array('TPoll_Activator', 'maybe_upgrade'), 20); add_action('tpoll_daily_cleanup', array($this, 'daily_cleanup')); add_filter('wp_privacy_personal_data_exporters', array($this, 'register_exporter')); add_filter('wp_privacy_personal_data_erasers', array($this, 'register_eraser')); add_action('admin_init', array($this, 'privacy_policy_text')); } /** * Load the bundled translations. Files from translate.wordpress.org in * wp-content/languages/plugins take precedence. */ public function load_textdomain() { // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Bundled de_DE files until the official language pack exists. load_plugin_textdomain('3task-polls', false, dirname(TPOLL_PLUGIN_BASENAME) . '/languages'); } /** * Remove voter fingerprints after the retention period. */ public function daily_cleanup() { $settings = get_option('tpoll_settings', array()); $days = isset($settings['retention_days']) ? absint($settings['retention_days']) : 30; TPoll_Vote::forget_fingerprints($days); } /** * Register personal data exporter (votes of registered users). */ public function register_exporter($exporters) { $exporters['3task-polls'] = array( 'exporter_friendly_name' => __('3task Polls votes', '3task-polls'), 'callback' => array($this, 'export_personal_data'), ); return $exporters; } /** * Register personal data eraser. */ public function register_eraser($erasers) { $erasers['3task-polls'] = array( 'eraser_friendly_name' => __('3task Polls votes', '3task-polls'), 'callback' => array($this, 'erase_personal_data'), ); return $erasers; } /** * Export votes linked to a user account. */ public function export_personal_data($email, $page = 1) { global $wpdb; $user = get_user_by('email', $email); $items = array(); if ($user) { // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom tables. $rows = $wpdb->get_results($wpdb->prepare( "SELECT v.id, v.created_at, v.vote_value, p.title, a.answer_text FROM {$wpdb->prefix}tpoll_votes v LEFT JOIN {$wpdb->prefix}tpoll_polls p ON p.id = v.poll_id LEFT JOIN {$wpdb->prefix}tpoll_answers a ON a.id = v.answer_id WHERE v.user_id = %d", $user->ID )); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching foreach ((array) $rows as $row) { $items[] = array( 'group_id' => 'tpoll-votes', 'group_label' => __('Poll votes', '3task-polls'), 'item_id' => 'tpoll-vote-' . $row->id, 'data' => array( array('name' => __('Poll', '3task-polls'), 'value' => (string) $row->title), array('name' => __('Answer', '3task-polls'), 'value' => (string) $row->answer_text), array('name' => __('Value', '3task-polls'), 'value' => (string) $row->vote_value), array('name' => __('Date', '3task-polls'), 'value' => (string) $row->created_at), ), ); } } return array('data' => $items, 'done' => true); } /** * Unlink votes from a user account. The anonymous vote count stays. */ public function erase_personal_data($email, $page = 1) { global $wpdb; $user = get_user_by('email', $email); $removed = 0; if ($user) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table. $removed = (int) $wpdb->query($wpdb->prepare( "UPDATE {$wpdb->prefix}tpoll_votes SET user_id = NULL, ip_hash = '', user_agent_hash = NULL WHERE user_id = %d", $user->ID )); } return array( 'items_removed' => $removed > 0, 'items_retained' => false, 'messages' => array(), 'done' => true, ); } /** * Suggested text for the privacy policy. */ public function privacy_policy_text() { if (!function_exists('wp_add_privacy_policy_content')) { return; } $content = '
' . esc_html__('When you vote in a poll, this site stores your answer, the time and a salted SHA-256 hash of your IP address and browser identifier to prevent duplicate votes. The plain IP address is not stored. If you are logged in, the vote is linked to your user account. If the poll uses the cookie restriction, a cookie named tpoll_voted_ followed by the poll number is set for one year. The hashes are removed automatically after the poll has ended and the retention period set by the site owner has passed. No data is sent to third parties.', '3task-polls') . '
'; wp_add_privacy_policy_content('3task Polls', wp_kses_post($content)); } /** * Register Gutenberg block from block.json */ public function register_block() { if (!function_exists('register_block_type')) { return; } register_block_type(TPOLL_PLUGIN_DIR . 'blocks/poll-block', array( 'render_callback' => array($this, 'render_block'), // Front-end styles also in the editor preview. 'style' => 'tpoll-frontend', )); $handle = function_exists('generate_block_asset_handle') ? generate_block_asset_handle('tpoll/poll', 'editorScript') : ''; if ($handle && function_exists('wp_set_script_translations')) { wp_set_script_translations($handle, '3task-polls', TPOLL_PLUGIN_DIR . 'languages'); } } /** * Poll list for the block, only loaded in the editor. */ public function block_editor_data() { $polls = TPoll_Poll::get_all(array('per_page' => 200, 'orderby' => 'title', 'order' => 'ASC')); $poll_options = array( array('value' => 0, 'label' => __('-- Select Poll --', '3task-polls')) ); foreach ($polls as $poll) { $label = $poll->title; if ($poll->status !== 'published') { $statuses = TPoll_Poll::get_statuses(); $label .= ' (' . (isset($statuses[$poll->status]) ? $statuses[$poll->status] : $poll->status) . ')'; } $poll_options[] = array( 'value' => (int) $poll->id, 'label' => $label ); } $handle = generate_block_asset_handle('tpoll/poll', 'editorScript'); wp_add_inline_script($handle, 'window.tpollBlockData = ' . wp_json_encode(array('polls' => $poll_options)) . ';', 'before'); } /** * Render block callback */ public function render_block($attributes) { if (empty($attributes['pollId'])) { if (defined('REST_REQUEST') && REST_REQUEST) { return '' . esc_html__('Please select a poll.', '3task-polls') . '
'; } return ''; } $html = do_shortcode('[tpoll id="' . absint($attributes['pollId']) . '"]'); if ($html === '') { return ''; } $wrapper = function_exists('get_block_wrapper_attributes') ? get_block_wrapper_attributes() : 'class="wp-block-tpoll-poll"'; return '