';
echo '| ' . esc_html( $id === null ? '' : (string) $id ) . ' | ';
echo '' . esc_html( AdminTimeFormatter::format( (string) $createdAt ) ) . ' | ';
echo '' . esc_html( (string) $channel ) . ' | ';
echo '' . esc_html( (string) $level ) . ' | ';
echo '' . esc_html( $eventType === null ? '' : (string) $eventType ) . ' | ';
echo '' . esc_html( (string) $message ) . ' | ';
echo '' . esc_html( $requestId === null ? '' : (string) $requestId ) . ' | ';
echo '' . esc_html( $correlationId === null ? '' : (string) $correlationId ) . ' | ';
echo '' . esc_html( $jobId === null ? '' : (string) $jobId ) . ' | ';
echo '' . esc_html( $relatedProductId === null ? '' : (string) $relatedProductId ) . ' | ';
echo '' . esc_html( $relatedJobId === null ? '' : (string) $relatedJobId ) . ' | ';
echo '
';
}
/**
* Render the textual `Page X of Y` pagination nav with prev /
* next anchors that preserve the current filter state.
*
* @param LogListQuery $query Originating query.
* @param int $totalPages Total page count.
* @param string $menuSlug Admin page slug for generated links.
* @param string $anchor Optional URL fragment to preserve tab focus.
*/
private function renderPagination( LogListQuery $query, int $totalPages, string $menuSlug = self::MENU_SLUG, string $anchor = '' ): void {
$current = $query->page();
echo '';
echo '';
echo esc_html( sprintf( '%d / %d ページ', $current, max( 1, $totalPages ) ) );
echo '';
if ( $totalPages > 1 ) {
echo ' ';
}
echo '
';
}
/**
* Build the per-page URL preserving the current filter state.
*
* Prefers `add_query_arg()` (the WordPress helper handles
* existing query-string fragments correctly) and falls back to
* manual `http_build_query()` concatenation in pure-unit
* harnesses where WordPress is not loaded.
*
* @param int $page Target page (>= 1).
* @param LogListQuery $query Current query state.
* @param string $menuSlug Admin page slug for generated links.
* @param string $anchor Optional URL fragment to preserve tab focus.
*/
private static function buildPageUrl( int $page, LogListQuery $query, string $menuSlug = self::MENU_SLUG, string $anchor = '' ): string {
$args = array(
'page' => $menuSlug,
LogsListQueryParser::KEY_PAGE => $page,
);
if ( $query->level() !== null ) {
$args[ LogsListQueryParser::KEY_LEVEL ] = $query->level();
}
if ( $query->channel() !== null ) {
$args[ LogsListQueryParser::KEY_CHANNEL ] = $query->channel();
}
if ( $query->relatedProductId() !== null ) {
$args[ LogsListQueryParser::KEY_RELATED_PRODUCT_ID ] = $query->relatedProductId();
}
if ( $query->relatedJobId() !== null ) {
$args[ LogsListQueryParser::KEY_RELATED_JOB_ID ] = $query->relatedJobId();
}
if ( $query->perPage() !== LogListQuery::DEFAULT_PER_PAGE ) {
$args[ LogsListQueryParser::KEY_PER_PAGE ] = $query->perPage();
}
$base = self::resolveAdminUrl( 'admin.php' );
if ( function_exists( 'add_query_arg' ) ) {
return (string) add_query_arg( $args, $base ) . $anchor;
}
$separator = str_contains( $base, '?' ) ? '&' : '?';
return $base . $separator . http_build_query( $args ) . $anchor;
}
/**
* Resolve a form action URL for embedded filter forms.
*
* @param string $anchor Optional URL fragment to preserve tab focus.
*/
private static function formActionUrl( string $anchor ): string {
return $anchor === '' ? '' : self::resolveAdminUrl( 'admin.php' ) . $anchor;
}
/**
* Resolve an admin URL via `admin_url()` when available,
* falling back to a synthetic absolute URL in pure-unit
* harnesses where WordPress is not loaded so `esc_url()` does
* not strip the value.
*
* @param string $path Admin path (e.g. `'admin.php'`).
*/
private static function resolveAdminUrl( string $path ): string {
if ( function_exists( 'admin_url' ) ) {
return (string) admin_url( $path );
}
return 'http://example.test/wp-admin/' . ltrim( $path, '/' );
}
}