string, 'clicks' => int, 'change' => ?int, 'title' => ?string]
* @param string $variant 'gain' | 'loss' — controls accent color only.
*/
public static function render(array $rows, string $variant = 'gain'): string {
if (empty($rows)) {
return '';
}
$accent = $variant === 'loss' ? '#dc2626' : '#16a34a';
$html = '
';
$html .= ''
. self::th(__('Page', 'thinkrank'), 'left')
. self::th(__('Clicks', 'thinkrank'), 'right')
. self::th(__('Change', 'thinkrank'), 'right')
. '
';
foreach ($rows as $row) {
$url = (string) ($row['url'] ?? $row['page'] ?? '');
$title = (string) ($row['title'] ?? '');
$clicks = (int) ($row['clicks'] ?? 0);
$display = $title !== '' ? $title : $url;
$html .= ''
. '| '
. ($url !== ''
? '' . esc_html($display) . ''
: '' . esc_html($display) . '')
. ' | '
. ''
. esc_html(number_format_i18n($clicks))
. ' | '
. self::change_cell($row['change'] ?? null, $accent)
. '
';
}
$html .= '
';
return $html;
}
/**
* Render a keyword table (query, position, clicks, click change).
*
* @param array $rows Each row: ['query' => string, 'position' => ?float, 'clicks' => int, 'change' => ?int]
* @param string $variant 'gain' | 'loss'
*/
public static function render_keywords(array $rows, string $variant = 'gain'): string {
if (empty($rows)) {
return '';
}
$accent = $variant === 'loss' ? '#dc2626' : '#16a34a';
$html = '';
$html .= ''
. self::th(__('Keyword', 'thinkrank'), 'left')
. self::th(__('Position', 'thinkrank'), 'right')
. self::th(__('Clicks', 'thinkrank'), 'right')
. self::th(__('Change', 'thinkrank'), 'right')
. '
';
foreach ($rows as $row) {
$query = (string) ($row['query'] ?? ($row['keys'][0] ?? ''));
// Null position means "no data this period" (e.g. a keyword that
// dropped out entirely) — a literal 0.0 would read as rank #1.
$position = isset($row['position'])
? number_format_i18n((float) $row['position'], 1)
: '—';
$clicks = (int) ($row['clicks'] ?? 0);
if ($query === '') {
continue;
}
$html .= ''
. '| ' . esc_html($query) . ' | '
. '' . esc_html($position) . ' | '
. '' . esc_html(number_format_i18n($clicks)) . ' | '
. self::change_cell($row['change'] ?? null, $accent)
. '
';
}
$html .= '
';
return $html;
}
/**
* Table header cell.
*/
private static function th(string $label, string $align): string {
return ''
. esc_html($label) . ' | ';
}
/**
* Signed click-change cell. Positive renders green with ▲, negative red
* with ▼. A null/zero change renders a neutral dash.
*/
private static function change_cell($change, string $accent): string {
$td = '%s | ';
if ($change === null || (int) $change === 0) {
return sprintf($td, '#9ca3af', '—');
}
$change = (int) $change;
$arrow = $change > 0 ? '▲' : '▼';
return sprintf($td, esc_attr($accent), esc_html($arrow . ' ' . number_format_i18n(abs($change))));
}
}