'generated-transient', 'plural' => 'generated-transients', 'ajax' => true, 'screen' => WP_Helper::get_wp_screen(), ) ); self::$columns = self::manage_columns( array() ); self::$table_name = 'advanced_transients'; } /** * Inits the module hook. * * @return void * * @since 2.8.1 */ public static function hooks_init() { \add_action( 'admin_print_styles-' . self::PAGE_SLUG, array( Settings::class, 'print_styles' ) ); \add_action( 'admin_post_' . self::UPDATE_ACTION, array( Transients_View::class, 'update_transient' ) ); \add_action( 'admin_post_' . self::NEW_ACTION, array( Transients_View::class, 'new_transient' ) ); \add_action( 'load-' . self::PAGE_SLUG, array( Transients_View::class, 'page_load' ) ); } /** * Adds the module to the main plugin menu * * @return void * * @since 2.8.1 */ public static function menu_add() { $transients_hook = \add_submenu_page( Logs_List::MENU_SLUG, ADVAN_INNER_NAME, \esc_html__( 'Transients viewer', '0-day-analytics' ), 'manage_options', self::TRANSIENTS_MENU_SLUG, array( Transients_View::class, 'analytics_transients_page' ), 5 ); self::add_screen_options( $transients_hook ); \add_filter( 'manage_' . $transients_hook . '_columns', array( self::class, 'manage_columns' ) ); \add_action( 'load-' . $transients_hook, array( Settings::class, 'aadvana_common_help' ) ); // Process bulk/table actions early before any output. \add_action( 'load-' . $transients_hook, array( self::class, 'process_actions_load' ) ); } /** * Handle any table actions during the early page load hook to allow safe redirects. * * @return void * * @since 4.2.0 */ public static function process_actions_load() { if ( ! \current_user_can( 'manage_options' ) ) { return; } $table = new self( array() ); $table->handle_table_actions(); } /** * Displays the search box. * * @since 1.7.0 * * @param string $text The 'submit' button label. * @param string $input_id ID attribute value for the search input field. */ public function search_box( $text, $input_id ) { // Use sanitized accessor instead of direct superglobal. if ( '' === self::escaped_search_input() && ! $this->has_items() ) { return; } $input_id = $input_id . '-search-input'; ?>
'search-submit' ) ); ?>
'', // to display the checkbox. 'transient_name' => __( 'Name', '0-day-analytics' ), 'schedule' => esc_html( sprintf( /* translators: %s: UTC offset */ __( 'Expiration (%s)', '0-day-analytics' ), WP_Helper::get_timezone_location() ), ), 'value' => __( 'Value', '0-day-analytics' ), ); $screen_options = $admin_fields; return \array_merge( $screen_options, $columns ); } /** * Returns the table name. * * @since 1.7.0 */ public static function get_table_name(): string { return self::$table_name; } /** * Prepares the list of items for displaying. * * Query, filter data, handle sorting, and pagination, and any other data-manipulation required prior to rendering * * @since 1.7.0 */ public function prepare_items() { $columns = $this->get_columns(); $hidden = array(); $sortable = $this->get_sortable_columns(); $this->_column_headers = array( $columns, $hidden, $sortable ); // Vars. // phpcs:disable Generic.Formatting.MultipleStatementAlignment $search = self::escaped_search_input(); /* phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query param for view state. */ $per_page = isset( $_GET['per_page'] ) ? max( 1, absint( $_GET['per_page'] ) ) : self::get_screen_option_per_page(); /* phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query param for view state. */ $orderby = isset( $_GET['orderby'] ) ? sanitize_key( \wp_unslash( $_GET['orderby'] ) ) : ''; $allowed_orderby = array( 'transient_name', 'schedule', 'value' ); if ( ! in_array( $orderby, $allowed_orderby, true ) ) { $orderby = ''; } /* phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query param for view state. */ $order = isset( $_GET['order'] ) ? strtoupper( sanitize_key( \wp_unslash( $_GET['order'] ) ) ) : 'DESC'; if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) { $order = 'DESC'; } $page = $this->get_pagenum(); $offset = $per_page * ( $page - 1 ); // phpcs:enable Generic.Formatting.MultipleStatementAlignment /* phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query param for view state. */ $type = isset( $_GET['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_GET['event_type'] ) ) : ''; $this->count = self::get_total_transients( $type, $search ); $this->fetch_table_data( array( 'search' => $search, 'offset' => $offset, 'per_page' => $per_page, 'orderby' => $orderby, 'order' => $order, 'type' => $type, ) ); $hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false ); if ( ! $hidden ) { $hidden = array(); } $this->_column_headers = array( self::$columns, $hidden, $sortable ); // Set the pagination. $this->set_pagination_args( array( 'total_items' => $this->count, 'per_page' => $per_page, 'total_pages' => ceil( $this->count / $per_page ), ) ); } /** * Returns the currently hidden column headers for the current user * * @return array * * @since 1.7.0 */ public static function get_hidden_columns() { return array_filter( (array) \get_user_option( 'manage' . Settings::get_main_menu_page_hook() . 'columnshidden', false ) ); } /** * Get a list of sortable columns. The format is: * 'internal-name' => 'orderby' * or * 'internal-name' => array( 'orderby', true ). * * The second format will make the initial sorting order be descending * * @since 1.7.0 * * @return array */ public function get_sortable_columns() { // Sorting disabled due to storage format constraints. return array(); } /** * Text displayed when no user data is available. * * @since 1.7.0 * * @return void */ public function no_items() { \esc_html_e( 'No transients found', '0-day-analytics' ); } /** * Fetch table data from the WordPress database. * * @param array $args - The arguments collected / passed. * * @since 1.7.0 * * @return array */ public function fetch_table_data( array $args = array() ) { $this->items = Transients_Helper::get_transient_items( $args ); return $this->items; } /** * Parse the query arguments * * @param array $args - Array to parse. * * @return array * * @since 1.7.0 */ public static function parse_args( $args = array() ) { // Parse. $parsed_args = \wp_parse_args( $args, array( 'offset' => 0, 'per_page' => self::get_screen_option_per_page(), 'search' => '', 'count' => false, ) ); // Return. return $parsed_args; } /** * Retrieve the total number transients in the database * * If a search is performed, it returns the number of found results * * @param string $type - The type of the transients to collect data for. * @param string $search - Search string. * * @return int * * @since 1.7.0 */ private static function get_total_transients( string $type, $search = '' ) { // Query. $count = Transients_Helper::get_transient_items( array( 'count' => true, 'search' => $search, 'type' => $type, ) ); // Return int. return absint( $count ); } /** * Render a column when no column specific method exists. * * Use that method for common rendering and separate columns logic in different methods. See below. * * @param array $item - Array with the current row values. * @param string $column_name - The name of the currently processed column. * * @return mixed * * @since 1.7.0 */ public function column_default( $item, $column_name ) { return self::format_column_value( $item, $column_name ); } /** * Render a column when no column specific method exists. * * Use that method for common rendering and separate columns logic in different methods. See below. * * @param array $item - Array with the current row values. * @param string $column_name - The name of the currently processed column. * * @return mixed * * @since 1.7.0 */ public static function format_column_value( $item, $column_name ) { $is_redis = isset( $item['source'] ) && 'redis' === $item['source']; switch ( $column_name ) { case 'transient_name': $query_args_view_data = array(); $query_args_view_data['hash'] = $item['id']; $query_args_view_data['_wpnonce'] = \wp_create_nonce( 'bulk-custom-delete' ); $actions = array(); if ( ! $is_redis ) { $actions['delete'] = '' . \esc_html__( 'Delete', '0-day-analytics' ) . ''; $actions['view'] = '' . \esc_html__( 'View', '0-day-analytics' ) . ''; $edit_url = \remove_query_arg( array( 'updated', 'deleted' ), \add_query_arg( array( 'action' => 'edit_transient', 'trans_id' => $item['id'], self::SEARCH_INPUT => self::escaped_search_input(), '_wpnonce' => $query_args_view_data['_wpnonce'], ) ) ); $actions['edit'] = '' . \esc_html__( 'Edit', '0-day-analytics' ) . ''; } else { // Redis transients can be deleted through WordPress API. $delete_url = \wp_nonce_url( \add_query_arg( array( 'page' => self::TRANSIENTS_MENU_SLUG, 'action' => 'delete_redis_transient', 'transient_name' => rawurlencode( $item['transient_name'] ), ), \admin_url( 'admin.php' ) ), 'bulk-custom-delete' ); $actions['delete'] = '' . \esc_html__( 'Delete', '0-day-analytics' ) . ''; } $core_trans = ''; if ( in_array( $item['transient_name'], Transients_Helper::WP_CORE_TRANSIENTS, true ) ) { $core_trans = ' '; } else { foreach ( Transients_Helper::WP_CORE_TRANSIENTS as $trans_name ) { if ( \str_starts_with( $item['transient_name'], $trans_name ) ) { $core_trans = ' '; break; } } } $source_badge = ''; if ( $is_redis ) { $source_badge = ' Redis'; } $title_attr = $is_redis ? \esc_attr__( 'Stored in Redis object cache', '0-day-analytics' ) : sprintf( \esc_attr__( 'Option ID: %d', '0-day-analytics' ), (int) $item['id'] ); // translators: %s is the transient. return '' . $core_trans . '' . \esc_html( (string) $item['transient_name'] ) . '' . $source_badge . '' . self::single_row_actions( $actions ); case 'schedule': if ( 0 === $item['schedule'] ) { return '—