HEX
Server: LiteSpeed
System: Linux srv158.niagahoster.com 4.18.0-553.30.1.lve.el8.x86_64 #1 SMP Tue Dec 3 01:21:19 UTC 2024 x86_64
User: u1694298 (3732)
PHP: 7.4.33
Disabled: symlink,shell_exec,exec,popen,system,dl,passthru,escapeshellarg,escapeshellcmd,show_source,pcntl_exec
Upload Files
File: /home/u1694298/www/wp-content/plugins/yop-poll/includes/Admin/class-admin-page-logs.php
<?php
namespace YopPoll\Admin;

use YopPoll\Models\Model_Log;
use YopPoll\Database\Migrator;
use YopPoll\Helpers\Permissions;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

class Logs_List_Table extends \WP_List_Table {

	public function __construct() {
		parent::__construct( array(
			'singular' => 'log',
			'plural'   => 'logs',
			'ajax'     => false,
		) );
	}

	public function get_columns() {
		return array(
			'cb'         => '<input type="checkbox" />',
			'poll'       => __( 'Poll', 'yop-poll' ),
			'username'   => __( 'Username', 'yop-poll' ),
			'email'      => __( 'Email', 'yop-poll' ),
			'user_type'  => __( 'User Type', 'yop-poll' ),
			'ipaddress'  => __( 'IP Address', 'yop-poll' ),
			'added_date' => __( 'Date', 'yop-poll' ),
			'message'    => __( 'Message', 'yop-poll' ),
		);
	}

	public function get_sortable_columns() {
		return array(
			'added_date' => array( 'l.added_date', true ),
		);
	}

	public function get_bulk_actions() {
		return array(
			'delete' => __( 'Trash', 'yop-poll' ),
		);
	}

	public function no_items() {
		esc_html_e( 'No Logs Available.', 'yop-poll' );
	}

	public function prepare_items() {
		$model    = new Model_Log();
		$per_page = $this->get_items_per_page( 'yop_poll_logs_per_page', 20 );
		$page     = $this->get_pagenum();

		// phpcs:disable WordPress.Security.NonceVerification -- Read-only list-table sort params.
		$order_raw = isset( $_GET['order'] ) ? strtoupper( sanitize_key( wp_unslash( $_GET['order'] ) ) ) : 'DESC';
		$order     = in_array( $order_raw, array( 'ASC', 'DESC' ), true ) ? $order_raw : 'DESC';
		$orderby   = ! empty( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'l.id';
		// phpcs:enable WordPress.Security.NonceVerification

		$args = array(
			'per_page' => $per_page,
			'page'     => $page,
			'orderby'  => $orderby,
			'order'    => $order,
		);

		if ( ! empty( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			$args['search'] = sanitize_text_field( wp_unslash( $_GET['s'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
		}

		$author_filter = Permissions::list_filter_author_id();
		if ( null !== $author_filter ) {
			$args['poll_author'] = $author_filter;
		}

		$this->items = $model->get_list( $args );
		$total       = $model->count_active( $args );

		$this->set_pagination_args( array(
			'total_items' => $total,
			'per_page'    => $per_page,
			'total_pages' => ceil( $total / $per_page ),
		) );

		$this->_column_headers = $this->get_column_info();
	}

	public function column_cb( $item ) {
		return sprintf( '<input type="checkbox" name="log_ids[]" value="%d" />', $item['id'] );
	}

	public function column_poll( $item ) {
		$poll_name = ! empty( $item['poll_name'] ) ? $item['poll_name'] : __( '(deleted)', 'yop-poll' );

		$base_url = admin_url( 'admin.php?page=yop-poll&action=logs' );

		// phpcs:disable WordPress.Security.NonceVerification -- Read-only list-table row state.
		$log_action    = isset( $_GET['log_action'] ) ? sanitize_key( wp_unslash( $_GET['log_action'] ) ) : '';
		$active_log_id = 'view_details' === $log_action && isset( $_GET['log_id'] )
			? absint( wp_unslash( $_GET['log_id'] ) )
			: 0;
		// phpcs:enable WordPress.Security.NonceVerification

		$is_open = $active_log_id === (int) $item['id'];

		if ( $is_open ) {
			$details_url = $base_url;
		} else {
			$details_url = add_query_arg( array(
				'log_action' => 'view_details',
				'log_id'     => $item['id'],
			), $base_url );
		}

		$details_label = $is_open ? __( 'Hide Details', 'yop-poll' ) : __( 'View Details', 'yop-poll' );

		$delete_url = wp_nonce_url(
			admin_url( 'admin.php?page=yop-poll&action=logs&log_action=delete&log_id=' . $item['id'] ),
			'yop_poll_delete_log_' . $item['id']
		);

		$actions = array(
			'view_details' => sprintf( '<a href="%s">%s</a>', esc_url( $details_url ), $details_label ),
			'delete'       => sprintf(
				'<a href="#" class="yop-poll-logs-confirm-action" data-href="%s" data-message="%s" data-label="%s">%s</a>',
				esc_url( $delete_url ),
				esc_attr( __( 'Are you sure you want to trash this log entry?', 'yop-poll' ) ),
				esc_attr( __( 'Trash', 'yop-poll' ) ),
				__( 'Trash', 'yop-poll' )
			),
		);

		$output = sprintf( '<strong>%s</strong>', esc_html( $poll_name ) );
		$output .= $this->row_actions( $actions );

		if ( $is_open ) {
			$output .= $this->get_log_details_html( $item );
		}

		return $output;
	}

	private function get_log_details_html( array $item ): string {
		global $wpdb;

		$vote_data = Migrator::decode_meta( (string) ( $item['vote_data'] ?? '' ) );
		if ( empty( $vote_data['elements'] ) || ! is_array( $vote_data['elements'] ) ) {
			return '<div class="yop-log-details" style="margin-top:6px;padding-left:4px"><em>' . esc_html__( 'No details available.', 'yop-poll' ) . '</em></div>';
		}

		$elements_table    = $wpdb->prefix . YOP_POLL_TABLE_PREFIX . 'elements';
		$subelements_table = $wpdb->prefix . YOP_POLL_TABLE_PREFIX . 'subelements';

		$allowed_tags = array_merge(
			wp_kses_allowed_html( 'post' ),
			array(
				'iframe' => array(
					'src'             => true,
					'title'           => true,
					'width'           => true,
					'height'          => true,
					'frameborder'     => true,
					'allow'           => true,
					'allowfullscreen' => true,
				),
			)
		);

		$html = '<div class="yop-log-details" style="margin-top:6px;padding-left:4px">';

		foreach ( $vote_data['elements'] as $element ) {
			$element_id = (int) ( $element['id'] ?? 0 );

			// Get element (question) text.
			$etext = '';
			if ( $element_id ) {
				$etext = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $elements_table built from $wpdb->prefix . YOP_POLL_TABLE_PREFIX.
					"SELECT etext FROM {$elements_table} WHERE id = %d", // phpcs:ignore WordPress.DB.PreparedSQL
					$element_id
				) );
			}
			$question_label = $etext ?: __( 'Question', 'yop-poll' ) . ' #' . $element_id;

			// Resolve answers.
			$answer_texts = array();
			if ( ! empty( $element['data'] ) && is_array( $element['data'] ) ) {
				foreach ( $element['data'] as $answer ) {
					$answer_id    = (int) ( $answer['id'] ?? 0 );
					$answer_value = ! empty( $answer['data'] ) && is_array( $answer['data'] ) ? implode( ', ', $answer['data'] ) : '';
					$sub_meta     = null;

					if ( $answer_id > 0 ) {
						$sub_row = $wpdb->get_row( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $subelements_table built from $wpdb->prefix . YOP_POLL_TABLE_PREFIX.
							"SELECT stext, meta_data FROM {$subelements_table} WHERE id = %d", // phpcs:ignore WordPress.DB.PreparedSQL
							$answer_id
						), ARRAY_A );
						if ( $sub_row ) {
							$sub_meta = json_decode( $sub_row['meta_data'] ?? '', true );

							// Video embed from meta_data.
							if ( ! empty( $sub_meta['videoEmbed'] ) ) {
								$answer_texts[] = wp_kses( $sub_meta['videoEmbed'], $allowed_tags );
								continue;
							}

							if ( ! empty( $sub_row['stext'] ) ) {
								$stext = $sub_row['stext'];
								// Image URL in stext.
								if ( preg_match( '/^https?:\/\/.+\.(jpe?g|png|gif|webp|svg|bmp)(\?.*)?$/i', trim( $stext ) ) ) {
									$answer_texts[] = '<img src="' . esc_url( trim( $stext ) ) . '" style="max-width:300px;height:auto" />';
								} else {
									$answer_texts[] = $stext;
								}
								continue;
							}
						}
					}

					if ( '' !== $answer_value ) {
						// Image URL in answer_value.
						if ( preg_match( '/^https?:\/\/.+\.(jpe?g|png|gif|webp|svg|bmp)(\?.*)?$/i', trim( $answer_value ) ) ) {
							$answer_texts[] = '<img src="' . esc_url( trim( $answer_value ) ) . '" style="max-width:300px;height:auto" />';
						} else {
							$answer_texts[] = $answer_value;
						}
					} else {
						$answer_texts[] = '—';
					}
				}
			}

			$html .= sprintf(
				'<div style="margin-bottom:4px"><strong>%s:</strong> %s',
				esc_html__( 'Question', 'yop-poll' ),
				wp_kses( $question_label, $allowed_tags )
			);
			foreach ( $answer_texts as $answer_text ) {
				$html .= sprintf(
					'<div style="padding-left:16px"><strong>%s:</strong> %s</div>',
					esc_html__( 'Answer', 'yop-poll' ),
					wp_kses( $answer_text, $allowed_tags )
				);
			}
			$html .= '</div>';
		}

		$html .= '</div>';
		return $html;
	}

	public function column_username( $item ) {
		if ( 'wordpress' === $item['user_type'] && ! empty( $item['user_id'] ) ) {
			$user = get_userdata( (int) $item['user_id'] );
			return $user ? esc_html( $user->display_name ) : esc_html( $item['user_email'] );
		}
		return esc_html( $item['user_email'] ?: '-' );
	}

	public function column_email( $item ) {
		return esc_html( $item['user_email'] ?: '-' );
	}

	public function column_user_type( $item ) {
		return esc_html( $item['user_type'] );
	}

	public function column_ipaddress( $item ) {
		return esc_html( $item['ipaddress'] );
	}

	public function column_added_date( $item ) {
		return esc_html( mysql2date( 'F j, Y g:i a', $item['added_date'] ) );
	}

	public function column_message( $item ) {
		$msg = $item['vote_message'];
		if ( empty( $msg ) ) {
			return '-';
		}
		$decoded = Migrator::decode_meta( $msg ?? '' );
		if ( ! empty( $decoded ) ) {
			$msg = implode( ', ', array_values( $decoded ) );
		}
		return esc_html( wp_trim_words( $msg, 12, '...' ) );
	}

	public function column_default( $item, $column_name ) {
		return esc_html( $item[ $column_name ] ?? '' );
	}
}

class Admin_Page_Logs {

	public function setup() {
		add_screen_option( 'per_page', array(
			'label'   => __( 'Number of logs per page', 'yop-poll' ),
			'default' => 20,
			'option'  => 'yop_poll_logs_per_page',
		) );
		new Logs_List_Table();

		// All redirect-causing actions must run here, before any output is sent.
		// phpcs:disable WordPress.Security.NonceVerification -- Nonce verified below via check_admin_referer().
		$log_action = isset( $_GET['log_action'] ) ? sanitize_key( wp_unslash( $_GET['log_action'] ) ) : '';
		$log_id     = isset( $_GET['log_id'] ) ? absint( wp_unslash( $_GET['log_id'] ) ) : 0;
		// phpcs:enable WordPress.Security.NonceVerification

		// Handle single delete.
		if ( 'delete' === $log_action && $log_id ) {
			check_admin_referer( 'yop_poll_delete_log_' . $log_id );
			$model = new Model_Log();
			$log   = $model->find( $log_id );
			if ( ! $log || ! Permissions::can_delete_item( (int) $log['poll_author'] ) ) {
				wp_die( esc_html__( 'You do not have permission to perform this action.', 'yop-poll' ), 403 );
			}
			$model->update( $log_id, array( 'status' => 'deleted' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=yop-poll&action=logs&deleted=1' ) );
			exit;
		}

		// Handle bulk delete.
		// phpcs:disable WordPress.Security.NonceVerification -- Nonce verified below via check_admin_referer().
		$post_action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : '';
		$log_ids     = isset( $_POST['log_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['log_ids'] ) ) : array();
		// phpcs:enable WordPress.Security.NonceVerification
		if ( 'delete' === $post_action && ! empty( $log_ids ) ) {
			check_admin_referer( 'bulk-logs' );
			$model = new Model_Log();
			foreach ( $log_ids as $id ) {
				$log = $model->find( $id );
				if ( ! $log || ! Permissions::can_delete_item( (int) $log['poll_author'] ) ) {
					continue;
				}
				$model->update( $id, array( 'status' => 'deleted' ) );
			}
			wp_safe_redirect( admin_url( 'admin.php?page=yop-poll&action=logs&deleted=1' ) );
			exit;
		}
	}

	public function render() {
		$table = new Logs_List_Table();
		$table->prepare_items();

		echo '<div class="wrap">';
		echo '<h1 class="wp-heading-inline">' . esc_html__( 'All Logs', 'yop-poll' ) . '</h1>';
		echo '<hr class="wp-header-end">';

		if ( ! empty( $_GET['deleted'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Log entry deleted.', 'yop-poll' ) . '</p></div>';
		}

		echo '<form method="get">';
		echo '<input type="hidden" name="page" value="yop-poll" />';
	echo '<input type="hidden" name="action" value="logs" />';
		$table->search_box( __( 'Search Logs', 'yop-poll' ), 'yop-log-search' );
		$table->display();
		echo '</form>';
		echo '</div>';

		$this->render_confirm_modal();
	}

	private function render_confirm_modal() {
		?>
		<style>
		#yop-poll-logs-modal-overlay {
			position: fixed;
			inset: 0;
			background: rgba(0,0,0,.5);
			z-index: 100000;
			display: flex;
			align-items: center;
			justify-content: center;
		}
		#yop-poll-logs-modal {
			background: #fff;
			width: 420px;
			max-width: 90vw;
			border-radius: 3px;
			overflow: hidden;
			box-shadow: 0 8px 32px rgba(0,0,0,.28);
		}
		#yop-poll-logs-modal-header {
			background: #e07b39;
			padding: 14px 20px;
			color: #fff;
			font-size: 14px;
			font-weight: 600;
		}
		#yop-poll-logs-modal-body { padding: 20px 20px 24px; font-size: 13px; color: #1d2327; }
		#yop-poll-logs-modal-footer {
			border-top: 1px solid #dcdcde;
			padding: 12px 20px;
			display: flex;
			justify-content: flex-end;
			gap: 8px;
		}
		#yop-poll-logs-modal-footer .button { min-width: 80px; text-align: center; }
		</style>
		<div id="yop-poll-logs-modal-overlay" style="display:none;">
			<div id="yop-poll-logs-modal">
				<div id="yop-poll-logs-modal-header"><?php esc_html_e( 'Warning', 'yop-poll' ); ?></div>
				<div id="yop-poll-logs-modal-body"><p id="yop-poll-logs-modal-msg" style="margin:0;"></p></div>
				<div id="yop-poll-logs-modal-footer">
					<button id="yop-poll-logs-modal-cancel" class="button"><?php esc_html_e( 'Cancel', 'yop-poll' ); ?></button>
					<a id="yop-poll-logs-modal-ok" href="#" class="button"><?php esc_html_e( 'OK', 'yop-poll' ); ?></a>
				</div>
			</div>
		</div>
		<script>
		(function() {
			var overlay   = document.getElementById('yop-poll-logs-modal-overlay');
			var msgEl     = document.getElementById('yop-poll-logs-modal-msg');
			var okBtn     = document.getElementById('yop-poll-logs-modal-ok');
			var cancelBtn = document.getElementById('yop-poll-logs-modal-cancel');
			function close() { overlay.style.display = 'none'; }
			cancelBtn.addEventListener('click', close);
			overlay.addEventListener('click', function(e) { if (e.target === overlay) close(); });
			document.querySelectorAll('.yop-poll-logs-confirm-action').forEach(function(link) {
				link.addEventListener('click', function(e) {
					e.preventDefault();
					msgEl.textContent = link.getAttribute('data-message');
					okBtn.href        = link.getAttribute('data-href');
					okBtn.textContent = link.getAttribute('data-label');
					overlay.style.display = 'flex';
				});
			});
		})();
		</script>
		<?php
	}
}