Files
wp-cycling-stats/includes/frontend/shortcodes.php
T
2026-04-22 13:11:06 +02:00

414 lines
20 KiB
PHP

<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Rejestruje shortcode [statpress_summary] and [statpress_activity].
*/
function statpress_register_shortcode() {
add_shortcode( 'statpress_summary', 'statpress_shortcode_handler' );
add_shortcode( 'statpress_activity', 'statpress_single_activity_shortcode_handler' );
}
/**
* Funkcja obsługująca shortcode.
* @param array $atts Atrybuty shortcode'u (np. year, month).
* @return string HTML do wyświetlenia.
*/
function statpress_shortcode_handler( $atts ) {
wp_enqueue_style( 'statpress-frontend-styles' );
global $wpdb;
// Ustawienie domyślnych atrybutów (bieżący rok i miesiąc)
$atts = shortcode_atts(
array(
'year' => current_time( 'Y' ),
'month' => current_time( 'n' ),
),
$atts,
'statpress_summary'
);
$year = intval( $atts['year'] );
$month = intval( $atts['month'] );
// Pobieranie danych z bazy
$table_activities = $wpdb->prefix . 'statpress_activities';
$sql = $wpdb->prepare(
"
SELECT a.*, c.name as category_name, c.icon, c.color, eq.name as equipment_name
FROM $table_activities a
LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
WHERE YEAR(a.date) = %d AND MONTH(a.date) = %d
ORDER BY a.date ASC
",
$year,
$month
);
$activities = $wpdb->get_results( $sql );
// Obliczanie podsumowań
$total_distance = 0;
$total_seconds = 0;
foreach ( $activities as $activity ) {
$total_distance += $activity->distance;
if ( ! empty( $activity->duration ) ) {
list($h, $m, $s) = explode( ':', $activity->duration );
$total_seconds += $h * 3600 + $m * 60 + $s;
}
}
$hours = floor( $total_seconds / 3600 );
$minutes = floor( ( $total_seconds % 3600 ) / 60 );
$total_duration_formatted = sprintf( '%d godz. %d min.', $hours, $minutes );
// Rozpoczęcie buforowania wyjścia
ob_start();
?>
<style>
.statpress-md-container { font-family: 'Roboto', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.statpress-md-container h3 { color: #202124; font-weight: 400; margin: 24px 0 16px; font-size: 20px; }
/* Karty podsumowania */
.statpress-summary-cards { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 32px; }
.statpress-summary-card { background: #fff; border-radius: 8px; padding: 20px; flex: 1; min-width: 140px; box-shadow: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15); border-left: 4px solid #1a73e8; }
.statpress-summary-card-title { font-size: 12px; color: #5f6368; text-transform: uppercase; font-weight: 500; margin-bottom: 8px; letter-spacing: 0.5px; }
.statpress-summary-card-value { font-size: 28px; color: #202124; font-weight: 400; }
/* Karty aktywności */
.statpress-activity-list { display: flex; flex-direction: column; gap: 16px; }
.statpress-activity-card { background: #fff; border-radius: 8px; box-shadow: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15); display: flex; padding: 16px 20px; align-items: center; flex-wrap: wrap; gap: 20px; transition: transform 0.2s, box-shadow 0.2s; }
.statpress-activity-card:hover { transform: translateY(-2px); box-shadow: 0 1px 3px 0 rgba(60,64,67,0.3), 0 4px 8px 3px rgba(60,64,67,0.15); }
.statpress-activity-icon { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; flex-shrink: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
.statpress-activity-icon .material-icons { font-size: 24px; }
.statpress-activity-info { flex: 1; min-width: 200px; }
.statpress-activity-title { font-size: 16px; font-weight: 500; color: #202124; margin: 0 0 6px 0; line-height: 1.3; }
.statpress-activity-meta { font-size: 13px; color: #5f6368; margin: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.statpress-activity-meta-dot { font-size: 8px; color: #dadce0; }
.statpress-activity-stats { display: flex; gap: 24px; flex-wrap: wrap; }
.statpress-stat-item { display: flex; flex-direction: column; }
.statpress-stat-label { font-size: 11px; color: #5f6368; text-transform: uppercase; font-weight: 500; margin-bottom: 2px; }
.statpress-stat-value { font-size: 15px; color: #202124; font-weight: 500; }
/* Responsywność */
@media (max-width: 600px) {
.statpress-activity-card { align-items: flex-start; padding: 16px; gap: 16px; }
.statpress-activity-stats { width: 100%; justify-content: space-between; border-top: 1px solid #f1f3f4; padding-top: 16px; margin-top: 4px; }
.statpress-summary-card { min-width: 100%; border-left: none; border-top: 4px solid #1a73e8; }
}
</style>
<div class="statpress-shortcode-container statpress-md-container">
<h3>Podsumowanie miesiąca</h3>
<div class="statpress-summary-cards">
<div class="statpress-summary-card">
<div class="statpress-summary-card-title">Całkowity dystans</div>
<div class="statpress-summary-card-value"><?php echo number_format( $total_distance, 2, ',', ' ' ); ?> km</div>
</div>
<div class="statpress-summary-card" style="border-color: #188038;">
<div class="statpress-summary-card-title">Całkowity czas</div>
<div class="statpress-summary-card-value"><?php echo esc_html( $total_duration_formatted ); ?></div>
</div>
</div>
<h3>Lista aktywności</h3>
<div class="statpress-activity-list">
<?php if ( ! empty( $activities ) ) : ?>
<?php foreach ( $activities as $row ) :
$material_icon = 'fitness_center';
if ( strpos( $row->icon, 'groups' ) !== false ) { $material_icon = 'directions_bike'; }
elseif ( strpos( $row->icon, 'businessman' ) !== false ) { $material_icon = 'directions_run'; }
$bg_color = !empty($row->color) ? $row->color : '#1a73e8';
?>
<div class="statpress-activity-card">
<div class="statpress-activity-icon" style="background-color: <?php echo esc_attr( $bg_color ); ?>;">
<span class="material-icons"><?php echo esc_html( $material_icon ); ?></span>
</div>
<div class="statpress-activity-info">
<h4 class="statpress-activity-title"><?php echo esc_html( $row->title ); ?></h4>
<p class="statpress-activity-meta">
<span><?php echo esc_html( date_i18n( 'd.m.Y', strtotime( $row->date ) ) ); ?></span>
<span class="statpress-activity-meta-dot">•</span>
<span><?php echo esc_html( $row->category_name ); ?></span>
<?php if ( $row->equipment_name ) : ?>
<span class="statpress-activity-meta-dot">•</span>
<span><?php echo esc_html( $row->equipment_name ); ?></span>
<?php endif; ?>
</p>
</div>
<div class="statpress-activity-stats">
<div class="statpress-stat-item"><span class="statpress-stat-label">Dystans</span><span class="statpress-stat-value"><?php echo number_format( $row->distance, 2, ',', ' ' ); ?> km</span></div>
<div class="statpress-stat-item"><span class="statpress-stat-label">Czas</span><span class="statpress-stat-value"><?php echo esc_html( $row->duration ); ?></span></div>
<?php if ( $row->avg_speed ) : ?><div class="statpress-stat-item"><span class="statpress-stat-label">Śr. prędkość</span><span class="statpress-stat-value"><?php echo number_format( $row->avg_speed, 1, ',', ' ' ); ?> km/h</span></div><?php endif; ?>
<?php if ( $row->avg_heart_rate ) : ?><div class="statpress-stat-item"><span class="statpress-stat-label">Śr. tętno</span><span class="statpress-stat-value"><?php echo esc_html( $row->avg_heart_rate ); ?> bpm</span></div><?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php else : ?>
<div class="statpress-summary-card">
<p style="margin: 0; color: #5f6368;">Brak aktywności w tym miesiącu.</p>
</div>
<?php endif; ?>
</div>
</div>
<?php
// Zwrócenie zawartości bufora
return ob_get_clean();
}
function statpress_single_activity_shortcode_handler( $atts ) {
wp_enqueue_style( 'statpress-frontend-styles' );
global $wpdb;
$atts = shortcode_atts(
array(
'id' => 0,
),
$atts,
'statpress_activity'
);
$activity_id = intval( $atts['id'] );
if ( 0 === $activity_id ) {
return '<p><strong>Błąd:</strong> Nie podano ID wpisu w shortcode.</p>';
}
// Pobieranie danych z bazy
$table_activities = $wpdb->prefix . 'statpress_activities';
$sql = $wpdb->prepare(
"
SELECT a.*, c.name as category_name, c.color as category_color, et.name as event_type_name, eq.name as equipment_name
FROM $table_activities a
LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id
LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
WHERE a.id = %d
",
$activity_id
);
$activity = $wpdb->get_row( $sql );
if ( ! $activity ) {
return '<p><strong>Błąd:</strong> Nie znaleziono wpisu o ID ' . esc_html( $activity_id ) . '.</p>';
}
// Funkcja pomocnicza do wyświetlania wiersza
$render_row = function( $label, $value, $unit = '' ) {
if ( ! is_null( $value ) && '' !== $value && 0 != $value ) {
echo '<div class="statpress-single-data-item">';
echo '<span class="statpress-single-data-label">' . esc_html( $label ) . '</span>';
echo '<span class="statpress-single-data-value">' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . '</span>';
echo '</div>';
}
};
ob_start();
// Prepare map and chart data if GPX exists
$gpx_data = array();
$has_gpx_data = false;
if ( ! empty( $activity->gpx_url ) ) {
$gpx_data = statpress_parse_gpx_data( $activity->gpx_url );
$has_gpx_data = ! empty( $gpx_data['points'] );
}
$unique_id = 'statpress-activity-' . esc_attr( $activity->id );
if ( $has_gpx_data ) {
wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', array(), '1.9.4', true );
wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
$map_id = 'map-' . $unique_id;
$chart_id = 'chart-' . $unique_id;
$available_profiles = array();
if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
$available_profiles['elevation'] = 'Wysokość';}
if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
$available_profiles['speed'] = 'Prędkość';}
if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
$available_profiles['hr'] = 'Tętno';}
if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
$available_profiles['cadence'] = 'Kadencja';}
$has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
wp_register_script( 'statpress-shortcode-loader-' . $activity->id, false );
wp_enqueue_script( 'statpress-shortcode-loader-' . $activity->id );
$js_script = '
(function() {
document.addEventListener("DOMContentLoaded", function() {
const uniqueId = "' . esc_js( $unique_id ) . '";
const containerEl = document.getElementById(uniqueId);
if (!containerEl || typeof L === "undefined" || typeof Chart === "undefined") return;
const trackPoints = ' . json_encode( $gpx_data['points'] ) . ';
const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
let activeChart = null;
const mapId = "' . esc_js( $map_id ) . '";
const mapEl = document.getElementById(mapId);
if (mapEl && trackPoints.length > 0) {
if (mapEl._leaflet_id) mapEl._leaflet_id = null;
const map = L.map(mapId);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>\' }).addTo(map);
const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map);
map.fitBounds(polyline.getBounds().pad(0.1));
}
const chartId = "' . esc_js( $chart_id ) . '";
const chartEl = document.getElementById(chartId);
if (!chartEl) return;
const chartConfigs = {
elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
};
const xAxisConfigs = {
distance: { label: "Dystans (km)", data: profiles.distance },
time: { label: "Czas", data: profiles.time, formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8) }
};
function renderChart() {
if (activeChart) activeChart.destroy();
const activeTab = containerEl.querySelector(".statpress-chart-tab.active");
if (!activeTab) return;
const chartType = activeTab.dataset.type;
const xAxisRadio = containerEl.querySelector(\'input[name="xaxis-\' + uniqueId + \'"]:checked\');
if (!xAxisRadio) return;
const xAxisType = xAxisRadio.value;
const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
const filteredY = [], filteredX = [];
if(yData) {
for(let i=0; i<yData.length; i++) {
if (yData[i] !== null) {
filteredY.push(yData[i]);
filteredX.push(xData[i]);
}
}
}
const ctx = chartEl.getContext("2d");
activeChart = new Chart(ctx, {
type: "line",
data: {
labels: filteredX,
datasets: [{
label: chartConfigs[chartType].label, data: filteredY,
borderColor: chartConfigs[chartType].color, backgroundColor: chartConfigs[chartType].color + "20",
fill: true, pointRadius: 0, tension: 0.1
}]
},
options: {
responsive: true, maintainAspectRatio: false,
scales: {
x: { title: { display: true, text: xAxisConfigs[xAxisType].label }, ticks: { callback: xAxisType === "time" ? xAxisConfigs.time.formatter : (v) => v, maxRotation: 0, autoSkip: true, maxTicksLimit: 7 } },
y: { title: { display: true, text: chartConfigs[chartType].unit } }
},
plugins: { legend: { display: false } },
interaction: { intersect: false, mode: "index" },
}
});
}
containerEl.querySelectorAll(".statpress-chart-tab").forEach(t => t.addEventListener("click", e => {
e.preventDefault();
containerEl.querySelector(".statpress-chart-tab.active").classList.remove("active");
e.currentTarget.classList.add("active");
renderChart();
}));
containerEl.querySelectorAll(\'input[name="xaxis-\' + uniqueId + \'"]\').forEach(r => r.addEventListener("change", renderChart));
if (containerEl.querySelector(".statpress-chart-tab")) renderChart();
});
})();';
wp_add_inline_script( 'statpress-shortcode-loader-' . $activity->id, $js_script );
}
?>
<style>
.statpress-single-card { background: #fff; border-radius: 8px; box-shadow: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15); padding: 24px; margin-bottom: 24px; font-family: 'Roboto', -apple-system, sans-serif; }
.statpress-single-header { border-bottom: 1px solid #e8eaed; padding-bottom: 16px; margin-bottom: 20px; }
.statpress-single-header h4 { margin: 0 0 8px 0; font-size: 22px; color: #202124; font-weight: 400; }
.statpress-single-header p { margin: 0; color: #5f6368; font-size: 14px; }
.statpress-single-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 20px; }
.statpress-single-data-item { display: flex; flex-direction: column; }
.statpress-single-data-label { font-size: 12px; color: #5f6368; text-transform: uppercase; font-weight: 500; margin-bottom: 4px; }
.statpress-single-data-value { font-size: 16px; color: #202124; font-weight: 500; }
</style>
<div class="statpress-single-activity-shortcode statpress-single-card" id="<?php echo esc_attr( $unique_id ); ?>">
<div class="statpress-single-header">
<h4><?php echo esc_html( $activity->title ); ?></h4>
<p><?php echo esc_html( date_i18n( 'j F Y', strtotime( $activity->date ) ) ); ?></p>
</div>
<div class="statpress-single-grid">
<?php $render_row( 'Dystans', number_format( $activity->distance, 2, ',', ' ' ), 'km' ); ?>
<?php $render_row( 'Czas trwania', $activity->duration ); ?>
<?php $render_row( 'Średnia prędkość', number_format( $activity->avg_speed, 1, ',', ' ' ), 'km/h' ); ?>
<?php $render_row( 'Suma wzniosów', $activity->total_elevation_gain, 'm' ); ?>
<?php $render_row( 'Kategoria', $activity->category_name ); ?>
<?php $render_row( 'Sprzęt', $activity->equipment_name ); ?>
<?php if ( ! empty( $activity->strava_url ) ) : ?>
<div class="statpress-single-data-item">
<span class="statpress-single-data-label">Strava</span>
<span class="statpress-single-data-value"><a href="<?php echo esc_url( $activity->strava_url ); ?>" target="_blank" rel="noopener noreferrer" style="color: #1a73e8; text-decoration: none;">Zobacz aktywność</a></span>
</div>
<?php endif; ?>
</div>
<?php if ( $has_gpx_data ) : ?>
<div id="<?php echo esc_attr( $map_id ); ?>" class="statpress-single-map" style="height: 350px; width: 100%; margin-top: 15px; border-radius: 5px; margin-bottom: 20px;"></div>
<?php if ( ! empty( $available_profiles ) ) : ?>
<div class="statpress-charts-container">
<div class="statpress-chart-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 10px; gap: 10px;">
<div class="statpress-chart-tabs" style="display: flex; flex-wrap: wrap; gap: 5px;">
<?php
$is_first = true;
foreach ( $available_profiles as $key => $label ) :
?>
<button data-type="<?php echo esc_attr( $key ); ?>" class="statpress-chart-tab <?php
if ( $is_first ) {
echo 'active';
$is_first = false; }
?>
"><?php echo esc_html( $label ); ?></button>
<?php endforeach; ?>
</div>
<?php if ( $has_time_data ) : ?>
<div class="statpress-xaxis-switcher" style="padding-top: 5px; font-size: 0.9em; white-space: nowrap;">
<strong>Oś X:</strong>&nbsp;
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked> Dystans</label>
&nbsp;
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="time"> Czas</label>
</div>
<?php else : ?>
<input type="hidden" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked>
<?php endif; ?>
</div>
<div class="statpress-chart-wrapper" style="position: relative; height:250px; width:100%;">
<canvas id="<?php echo esc_attr( $chart_id ); ?>"></canvas>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}