__('Unlimited voting', 'acf-onyx-poll'), 2 => __('Per device', 'acf-onyx-poll'), 30 => __('Every 30 minutes', 'acf-onyx-poll'), 60 => __('Every 1 hour', 'acf-onyx-poll'), 120 => __('Every 2 hours', 'acf-onyx-poll'), 180 => __('Every 3 hours', 'acf-onyx-poll'), 240 => __('Every 4 hours', 'acf-onyx-poll'), 300 => __('Every 5 hours', 'acf-onyx-poll'), 720 => __('Every 12 hours', 'acf-onyx-poll'), 1440 => __('Every 24 hours', 'acf-onyx-poll')); } private function select($name, $label, $value, $choices, $description = '') { echo ''; } private function description($name, $description) { if ($description) { echo '' . esc_html($description) . ''; } } private function toggle($name, $label, $value, $description = '') { echo ''; } public function form($post) { if ($post->post_type !== 'onyxpolls') { return; } $poll = OnyxPollsStorage::get($post->ID); if (!$poll) { return; } wp_nonce_field('onyx_poll_save', 'onyx_poll_nonce'); $snapshot = wp_json_encode(array('version' => $poll['version'], 'answers' => $poll['answers'])); echo ''; echo ''; echo '

' . esc_html__('Answers', 'acf-onyx-poll') . ' *

'; echo '
'; $answers = $poll['answers']; while (count($answers) < 2) { $answers[] = array('id' => 0, 'answer' => '', 'image_id' => 0, 'votes' => 0); } foreach ($answers as $i => $answer) { $this->row($answer, $i); } echo '
' . esc_html__('Order', 'acf-onyx-poll') . '' . esc_html__('Image', 'acf-onyx-poll') . '' . esc_html__('Answer', 'acf-onyx-poll') . '' . esc_html__('Votes', 'acf-onyx-poll') . '' . esc_html__('Actions', 'acf-onyx-poll') . '

'; echo '
'; $this->select('limit_vote', __('Limit voting', 'acf-onyx-poll'), $poll['limit_vote'], self::limits(), __('Controls repeat votes per browser session using server-side records. Unlimited voting removes the poll interval; each session can submit up to 10 voting requests per minute.', 'acf-onyx-poll')); $this->select('results', __('Results', 'acf-onyx-poll'), $poll['results'], array(1 => __('Show percentages', 'acf-onyx-poll'), 2 => __('Show vote counts', 'acf-onyx-poll'), 3 => __('Show both', 'acf-onyx-poll')), __('Display answer totals as percentages, numbers or both.', 'acf-onyx-poll')); $end = $poll['end'] ? substr(str_replace(' ', 'T', $poll['end']), 0, 16) : ''; /* translators: %s: Site timezone name or UTC offset. */ $end_help = sprintf(__('Voting closes at this time in the site timezone (%s), even if cron is delayed.', 'acf-onyx-poll'), wp_timezone_string()); echo ''; echo ''; $this->toggle('images', __('Enable images', 'acf-onyx-poll'), $poll['images'], __('Show an image with each answer. Every answer needs an image when enabled.', 'acf-onyx-poll')); $this->toggle('modal', __('Poll modal', 'acf-onyx-poll'), $poll['modal'], __('Allow this poll in the site modal. The latest active poll with this option is used.', 'acf-onyx-poll')); $this->toggle('show_results', __('Show results', 'acf-onyx-poll'), $poll['show_results'], __('Let visitors view results before the deadline. Expired polls always show results.', 'acf-onyx-poll')); $this->toggle('expired', __('Expired', 'acf-onyx-poll'), $poll['expired'], __('Close voting manually. To reopen, disable this option and set a future deadline.', 'acf-onyx-poll')); echo '
'; } private function row($answer, $i) { $name = 'onyx_poll[answers][' . $i . ']'; $image_label = $answer['image_id'] ? __('Change image', 'acf-onyx-poll') : __('Select image', 'acf-onyx-poll'); echo '' . esc_html(is_numeric($i) ? $i + 1 : '') . ''; echo '
'; echo ''; echo ''; echo '
'; $actions = array('up' => array(__('Move up', 'acf-onyx-poll'), 'arrow-up-alt2'), 'down' => array(__('Move down', 'acf-onyx-poll'), 'arrow-down-alt2'), 'remove' => array(__('Remove answer', 'acf-onyx-poll'), 'trash')); foreach ($actions as $action => $button) { echo ''; } echo '
'; } public function assets() { $screen = get_current_screen(); if (!$screen || $screen->post_type !== 'onyxpolls') { return; } wp_enqueue_media(); wp_enqueue_script('onyx-poll-admin', plugins_url('assets/js/admin.js', ACF_ONYX_POLL_FILE), array('jquery-ui-sortable'), OnyxPolls::get_asset_vars('assets/js/admin.js')->ver, true); wp_localize_script('onyx-poll-admin', 'onyxPollAdmin', array('imageTitle' => __('Select image', 'acf-onyx-poll'), 'imageButton' => __('Use image', 'acf-onyx-poll'), 'imageChoose' => __('Select image', 'acf-onyx-poll'), 'imageChange' => __('Change image', 'acf-onyx-poll'))); } public function save($id, $post) { if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($id) || !isset($_POST['onyx_poll_nonce'])) { return; } if (!current_user_can('edit_post', $id) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['onyx_poll_nonce'])), 'onyx_poll_save')) { return; } $json = base64_decode(isset($_POST['onyx_poll_snapshot']) && is_string($_POST['onyx_poll_snapshot']) ? wp_unslash($_POST['onyx_poll_snapshot']) : '', true); $signature = isset($_POST['onyx_poll_signature']) ? sanitize_text_field(wp_unslash($_POST['onyx_poll_signature'])) : ''; $snapshot = $json ? json_decode($json, true) : null; if (!$snapshot || !isset($snapshot['version'], $snapshot['answers']) || !hash_equals(hash_hmac('sha256', $id . ':' . $json, wp_salt('nonce')), $signature)) { $this->error = __('Invalid poll parameters.', 'acf-onyx-poll'); return; } $input = isset($_POST['onyx_poll']) ? wp_unslash($_POST['onyx_poll']) : array(); $validated = self::validate($input); if (is_wp_error($validated)) { $this->error = $validated->get_error_message(); return; } $result = OnyxPollsStorage::save($id, $validated['poll'], $validated['answers'], $snapshot); if (is_wp_error($result)) { $this->error = $result->get_error_message(); } } public static function validate($input) { $invalid = new WP_Error('onyx_poll_invalid', __('Complete all required poll fields. At least two answers are required.', 'acf-onyx-poll')); if (!is_array($input) || !isset($input['answers']) || !is_array($input['answers']) || count($input['answers']) < 2) { return $invalid; } $limit = isset($input['limit_vote']) && is_scalar($input['limit_vote']) ? (int) $input['limit_vote'] : 0; $results = isset($input['results']) && is_scalar($input['results']) ? (int) $input['results'] : 0; $end = isset($input['end']) && is_string($input['end']) ? $input['end'] : ''; $date = DateTime::createFromFormat('!Y-m-d\TH:i', $end, wp_timezone()); if (!isset(self::limits()[$limit]) || !in_array($results, array(1, 2, 3), true) || !$date || $date->format('Y-m-d\TH:i') !== $end) { return $invalid; } $data = array('limit_vote' => $limit, 'results' => $results, 'end' => $date->format('Y-m-d H:i:s')); foreach (array('images', 'modal', 'show_results', 'expired') as $key) { $data[$key] = empty($input[$key]) ? 0 : 1; } $answers = array(); foreach ($input['answers'] as $answer) { if (!is_array($answer) || !isset($answer['answer'], $answer['votes']) || !is_string($answer['answer']) || !is_scalar($answer['votes']) || !preg_match('/^\d+$/', (string) $answer['votes'])) { return $invalid; } $text = sanitize_text_field($answer['answer']); $image = isset($answer['image_id']) && is_scalar($answer['image_id']) ? absint($answer['image_id']) : 0; if ($text === '' || ($data['images'] && (!$image || !wp_attachment_is_image($image)))) { return $invalid; } $answers[] = array('id' => isset($answer['id']) && is_scalar($answer['id']) ? absint($answer['id']) : 0, 'answer' => $text, 'image_id' => $image, 'votes' => (int) $answer['votes']); } return array('poll' => $data, 'answers' => $answers); } public function redirect($location) { return $this->error ? add_query_arg('onyx_poll_error', $this->error, $location) : $location; } public function notice() { $screen = get_current_screen(); if ($screen && $screen->post_type === 'onyxpolls' && !empty($_GET['onyx_poll_error'])) { echo '

' . esc_html(sanitize_text_field(wp_unslash($_GET['onyx_poll_error']))) . '

'; } if (get_option(OnyxPollsStorage::VERSION) !== OnyxPollsStorage::DB_VERSION || !get_option(OnyxPollsStorage::MIGRATION)) { echo '

' . esc_html__('Poll data migration is pending. It will resume automatically; existing data is preserved.', 'acf-onyx-poll') . '

'; } } public function menu() { add_submenu_page('edit.php?post_type=onyxpolls', __('Poll settings', 'acf-onyx-poll'), __('Settings', 'acf-onyx-poll'), 'manage_options', 'onyx-poll-settings', array($this, 'settings_form')); } public function settings_form() { if (!current_user_can('manage_options')) { return; } $settings = OnyxPollsStorage::settings(); echo '

' . esc_html__('Poll settings', 'acf-onyx-poll') . '

'; if (isset($_GET['updated'])) { echo '

' . esc_html__('Settings saved.', 'acf-onyx-poll') . '

'; } echo '
'; wp_nonce_field('onyx_poll_settings'); echo '
'; $intervals = array('' => __('Not set', 'acf-onyx-poll')); foreach (array(1, 2, 3, 4, 5, 12, 24) as $hours) { /* translators: %d: Number of hours between modal appearances. */ $intervals[$hours] = sprintf(_n('Every %d hour', 'Every %d hours', $hours, 'acf-onyx-poll'), $hours); } $this->select('modal_time', __('Voting modal interval', 'acf-onyx-poll'), $settings['modal_time'], $intervals, __('How often the poll modal can reappear on the same browser. Not set allows it to reappear without an interval.', 'acf-onyx-poll')); $sizes = array_keys(wp_get_registered_image_subsizes()); $this->select('image_size', __('Select the thumbnail size', 'acf-onyx-poll'), $settings['image_size'], array_combine($sizes, $sizes), __('WordPress image size used for poll answers. A larger file does not change its display size; adjust your CSS if needed.', 'acf-onyx-poll')); $this->toggle('css', __('Use custom CSS', 'acf-onyx-poll'), $settings['css'], __('Disables the default poll stylesheet so your theme can provide all styles. You can also keep it enabled and override the CSS variables shown in Help.', 'acf-onyx-poll')); echo '
'; if (current_user_can('manage_options')) { echo '
'; $this->select('delete_on_uninstall', __('Data on uninstall', 'acf-onyx-poll'), $settings['delete_on_uninstall'], array(0 => __('Keep data (default)', 'acf-onyx-poll'), 1 => __('Permanently delete plugin data', 'acf-onyx-poll')), __('Applies only when the plugin is deleted, not deactivated. Keeping data lets you reinstall without losing polls and votes.', 'acf-onyx-poll')); echo '

' . esc_html__('When you delete this plugin, all Onyx polls, answers, votes and settings will be permanently deleted. This cannot be undone. Other posts, pages and media will be preserved. Deactivation does not delete data.', 'acf-onyx-poll') . '

'; echo '

'; } submit_button(); echo '
'; require_once __DIR__ . '/poll-settings-help.php'; OnyxPollsSettingsHelp::render(); echo '
'; } public function save_settings() { if (!current_user_can('manage_options')) { wp_die(esc_html__('You are not allowed to change these settings.', 'acf-onyx-poll')); } check_admin_referer('onyx_poll_settings'); $input = isset($_POST['onyx_poll']) ? wp_unslash($_POST['onyx_poll']) : array(); if (!is_array($input)) { wp_die(esc_html__('Invalid poll parameters.', 'acf-onyx-poll')); } $settings = OnyxPollsStorage::settings(); $settings['modal_time'] = isset($input['modal_time']) && is_scalar($input['modal_time']) && in_array((int) $input['modal_time'], array(1, 2, 3, 4, 5, 12, 24), true) ? (int) $input['modal_time'] : ''; $settings['css'] = empty($input['css']) ? 0 : 1; $settings['image_size'] = isset($input['image_size']) && is_string($input['image_size']) && isset(wp_get_registered_image_subsizes()[$input['image_size']]) ? $input['image_size'] : 'thumbnail'; if (isset($input['delete_on_uninstall'])) { if (!current_user_can('manage_options')) { wp_die(esc_html__('You are not allowed to change these settings.', 'acf-onyx-poll')); } if (!empty($input['delete_on_uninstall']) && empty($_POST['onyx_delete_confirm'])) { wp_die(esc_html__('Confirm that you understand the permanent deletion warning before saving.', 'acf-onyx-poll')); } $settings['delete_on_uninstall'] = empty($input['delete_on_uninstall']) ? 0 : 1; } update_option(OnyxPollsStorage::SETTINGS, $settings, false); wp_safe_redirect(admin_url('edit.php?post_type=onyxpolls&page=onyx-poll-settings&updated=1')); exit; } } if (is_admin()) { new OnyxPollsAdmin(); }