get_charset_collate(); $table_categories = $wpdb->prefix . 'mystat_categories'; $table_activities = $wpdb->prefix . 'mystat_activities'; $table_event_types = $wpdb->prefix . 'mystat_event_types'; $table_equipment = $wpdb->prefix . 'mystat_equipment'; // SQL dla Kategorii $sql_cat = "CREATE TABLE $table_categories ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, icon varchar(50) NOT NULL, color varchar(20) NOT NULL, PRIMARY KEY (id) ) $charset_collate;"; // SQL dla Typów Wydarzeń $sql_event_types = "CREATE TABLE $table_event_types ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, PRIMARY KEY (id) ) $charset_collate;"; // SQL dla Sprzętu $sql_equipment = "CREATE TABLE $table_equipment ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, PRIMARY KEY (id) ) $charset_collate;"; // SQL dla Aktywności $sql_act = "CREATE TABLE $table_activities ( id bigint(20) NOT NULL AUTO_INCREMENT, category_id mediumint(9) NOT NULL, date date NOT NULL, title varchar(255) DEFAULT '' NOT NULL, distance decimal(10,2) DEFAULT 0.00, duration time DEFAULT '00:00:00', calories int(11) DEFAULT 0, comment text, strava_url varchar(255) DEFAULT NULL, avg_heart_rate smallint(5) UNSIGNED DEFAULT NULL, max_heart_rate smallint(5) UNSIGNED DEFAULT NULL, avg_speed decimal(5,2) DEFAULT NULL, max_speed decimal(5,2) DEFAULT NULL, avg_cadence smallint(5) UNSIGNED DEFAULT NULL, max_cadence smallint(5) UNSIGNED DEFAULT NULL, total_elevation_gain int(11) DEFAULT NULL, total_elevation_loss int(11) DEFAULT NULL, min_altitude int(11) DEFAULT NULL, max_altitude int(11) DEFAULT NULL, equipment_id mediumint(9) DEFAULT NULL, gpx_url varchar(255) DEFAULT NULL, event_type_id mediumint(9) DEFAULT NULL, PRIMARY KEY (id) ) $charset_collate;"; require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); dbDelta( $sql_equipment ); dbDelta( $sql_cat ); dbDelta( $sql_event_types ); dbDelta( $sql_act ); // Dodanie domyślnych kategorii, jeśli tabela jest pusta if ( $wpdb->get_var( "SELECT COUNT(*) FROM $table_categories" ) == 0 ) { $wpdb->insert( $table_categories, array( 'name' => 'Rower', 'icon' => 'dashicons-buddicons-groups', 'color' => '#3498db' ) ); $wpdb->insert( $table_categories, array( 'name' => 'Bieganie', 'icon' => 'dashicons-businessman', 'color' => '#e74c3c' ) ); } // Dodanie domyślnych typów wydarzeń, jeśli tabela jest pusta if ( $wpdb->get_var( "SELECT COUNT(*) FROM $table_event_types" ) == 0 ) { $default_event_types = ['Bez kategorii', 'Fitness', 'Geocaching', 'Podróżowanie', 'Rekreacyjny', 'Specjalne zdarzenie', 'Transport', 'Trening', 'Wyścig']; foreach ($default_event_types as $type_name) { $wpdb->insert( $table_event_types, array( 'name' => $type_name ) ); } // Ustawienie domyślnego typu "Trening" dla istniejących aktywności, które go nie mają $training_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_event_types WHERE name = %s", 'Trening' ) ); if ($training_id) { $wpdb->query( $wpdb->prepare( "UPDATE $table_activities SET event_type_id = %d WHERE event_type_id IS NULL OR event_type_id = 0", $training_id ) ); } } // Dodanie domyślnego sprzętu, jeśli tabela jest pusta if ( $wpdb->get_var( "SELECT COUNT(*) FROM $table_equipment" ) == 0 ) { $default_equipment = ['Giant Revolt', 'Cube LTD', 'Author Agang', 'Liv Tempt 4', 'Cube Acid 24', 'Mongoose BMX', 'Nextbike - Miejski']; foreach ($default_equipment as $eq_name) { $wpdb->insert( $table_equipment, array( 'name' => $eq_name ) ); } } } // --- 2. MENU ADMINA I DASHBOARD --- $mystat_plugin_hooks = []; add_action( 'admin_menu', 'mystat_add_admin_menu' ); /** * Set up admin-specific hooks. */ function mystat_admin_init_setup() { add_filter( 'upload_mimes', 'mystat_add_gpx_mime_type' ); add_filter( 'wp_check_filetype_and_ext', 'mystat_fix_gpx_upload_permission', 10, 4 ); } add_action( 'admin_init', 'mystat_admin_init_setup' ); /** * Enqueue admin-specific CSS. * * @param string $hook The current admin page hook. */ function mystat_enqueue_admin_styles( $hook ) { global $mystat_plugin_hooks; if ( in_array( $hook, $mystat_plugin_hooks, true ) ) { $plugin_version = '1.0'; // You can use filemtime() for cache-busting in development wp_enqueue_style( 'mystat-admin-styles', plugin_dir_url( __FILE__ ) . 'assets/css/admin.css', [], $plugin_version ); } } add_action( 'admin_enqueue_scripts', 'mystat_enqueue_admin_styles' ); /** * Add GPX support to WordPress Media Library. * * @param array $mimes Allowed mime types. * @return array Modified mime types. */ function mystat_add_gpx_mime_type( $mimes ) { $mimes['gpx'] = 'application/gpx+xml'; return $mimes; } /** * Bypasses WordPress's strict file type check for GPX files. * This is needed because WordPress can be overly cautious with XML-based files. * * @param array $data File data. * @param string $file Full path to the file. * @param string $filename The filename. * @param array $mimes Mime types. * @return array Modified file data. */ function mystat_fix_gpx_upload_permission( $data, $file, $filename, $mimes ) { if ( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ) === 'gpx' ) { $data['ext'] = 'gpx'; $data['type'] = 'application/gpx+xml'; } return $data; } function mystat_add_admin_menu() { global $mystat_plugin_hooks; $mystat_plugin_hooks[] = add_menu_page( 'Moje Statystyki', // Tytuł strony 'Statystyki', // Tytuł w menu 'manage_options', // Wymagane uprawnienia 'moje-statystyki', // Slug menu 'mystat_dashboard_page', // Funkcja renderująca stronę główną (dashboard) 'dashicons-chart-line', // Ikona 6 // Pozycja ); $mystat_plugin_hooks[] = add_submenu_page( 'moje-statystyki', // Slug rodzica 'Dodaj Nowy Trening', // Tytuł strony 'Nowy trening', // Tytuł w podmenu 'manage_options', // Wymagane uprawnienia 'mystat-nowy-trening', // Slug podmenu 'mystat_add_new_page' // Funkcja renderująca stronę dodawania ); $mystat_plugin_hooks[] = add_submenu_page( 'moje-statystyki', 'Typy Wydarzeń', 'Typy wydarzeń', 'manage_options', 'mystat-event-types', 'mystat_event_types_page' ); $mystat_plugin_hooks[] = add_submenu_page( 'moje-statystyki', 'Sprzęt', 'Sprzęt', 'manage_options', 'mystat-equipment', 'mystat_equipment_page' ); $mystat_plugin_hooks[] = add_submenu_page( null, // Ukryta strona, nie pojawia się w menu 'Szczegóły Treningu', // Tytuł strony 'Szczegóły Treningu', // Tytuł w menu (nieistotny) 'manage_options', // Wymagane uprawnienia 'mystat-view-activity', // Slug podmenu 'mystat_view_activity_page' // Funkcja renderująca ); $mystat_plugin_hooks[] = add_submenu_page( null, // Ukryta strona 'Edytuj Trening', // Tytuł strony 'Edytuj Trening', // Tytuł w menu (nieistotny) 'manage_options', // Wymagane uprawnienia 'mystat-edit-activity', // Slug podmenu 'mystat_edit_activity_page' // Funkcja renderująca ); $mystat_plugin_hooks[] = add_submenu_page( 'moje-statystyki', // Slug rodzica 'Podsumowanie Roczne', // Tytuł strony 'Podsumowanie Roczne', // Tytuł w podmenu 'manage_options', // Wymagane uprawnienia 'mystat-yearly-summary', // Slug podmenu 'mystat_yearly_summary_page'// Funkcja renderująca ); $mystat_plugin_hooks[] = add_submenu_page( 'moje-statystyki', // Slug rodzica 'Infografika', // Tytuł strony 'Infografika', // Tytuł w podmenu 'manage_options', // Wymagane uprawnienia 'mystat-infographic', // Slug podmenu 'mystat_infographic_page' // Funkcja renderująca ); $mystat_plugin_hooks[] = add_submenu_page( 'moje-statystyki', // Slug rodzica 'Import CSV', // Tytuł strony 'Import CSV', // Tytuł w podmenu 'manage_options', // Wymagane uprawnienia 'mystat-import-csv', // Slug podmenu 'mystat_import_csv_page' // Funkcja renderująca ); } function mystat_dashboard_page() { echo '

Moje Statystyki Sportowe

'; mystat_render_history_table(); echo '
'; } function mystat_add_new_page() { echo '

Dodaj Nowy Trening

'; // Obsługa zapisu formularza (musi być przed renderowaniem, aby wyświetlić komunikat) mystat_handle_activity_form_submission(); // Formularz dodawania mystat_render_add_form(); echo '
'; } function mystat_event_types_page() { global $wpdb; $table_event_types = $wpdb->prefix . 'mystat_event_types'; $message = ''; $notice_class = ''; // Handle POST requests (add/update) if ( isset( $_POST['submit'] ) && check_admin_referer( 'mystat_manage_event_type' ) ) { $name = sanitize_text_field( $_POST['event_type_name'] ); $type_id = isset( $_POST['event_type_id'] ) ? intval( $_POST['event_type_id'] ) : 0; if ( ! empty( $name ) ) { if ( $type_id > 0 ) { // Update $wpdb->update( $table_event_types, [ 'name' => $name ], [ 'id' => $type_id ] ); $message = 'Typ wydarzenia zaktualizowany.'; $notice_class = 'notice-success'; } else { // Insert $wpdb->insert( $table_event_types, [ 'name' => $name ] ); $message = 'Typ wydarzenia dodany.'; $notice_class = 'notice-success'; } } else { $message = 'Nazwa typu wydarzenia nie może być pusta.'; $notice_class = 'notice-error'; } } // Handle GET requests (delete) if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && $_GET['action'] === 'delete' ) { if ( wp_verify_nonce( $_GET['_wpnonce'], 'mystat_delete_event_type_' . $_GET['id'] ) ) { $wpdb->delete( $table_event_types, [ 'id' => intval( $_GET['id'] ) ] ); $message = 'Typ wydarzenia usunięty.'; $notice_class = 'notice-success'; } } // Prepare for form (for editing) $item_to_edit = null; if ( isset( $_GET['action'], $_GET['id'] ) && $_GET['action'] === 'edit' ) { $item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_event_types WHERE id = %d", intval( $_GET['id'] ) ) ); } $event_types = $wpdb->get_results( "SELECT * FROM $table_event_types ORDER BY name ASC" ); ?>

Typy Wydarzeń

NazwaAkcje
name ); ?>Edytuj | Usuń
prefix . 'mystat_equipment'; $message = ''; $notice_class = ''; // Handle POST requests (add/update) if ( isset( $_POST['submit'] ) && check_admin_referer( 'mystat_manage_equipment' ) ) { $name = sanitize_text_field( $_POST['equipment_name'] ); $item_id = isset( $_POST['equipment_id'] ) ? intval( $_POST['equipment_id'] ) : 0; if ( ! empty( $name ) ) { if ( $item_id > 0 ) { // Update $wpdb->update( $table_equipment, [ 'name' => $name ], [ 'id' => $item_id ] ); $message = 'Sprzęt zaktualizowany.'; $notice_class = 'notice-success'; } else { // Insert $wpdb->insert( $table_equipment, [ 'name' => $name ] ); $message = 'Sprzęt dodany.'; $notice_class = 'notice-success'; } } else { $message = 'Nazwa sprzętu nie może być pusta.'; $notice_class = 'notice-error'; } } // Handle GET requests (delete) if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && $_GET['action'] === 'delete' ) { if ( wp_verify_nonce( $_GET['_wpnonce'], 'mystat_delete_equipment_' . $_GET['id'] ) ) { $wpdb->delete( $table_equipment, [ 'id' => intval( $_GET['id'] ) ] ); $message = 'Sprzęt usunięty.'; $notice_class = 'notice-success'; } } // Prepare for form (for editing) $item_to_edit = null; if ( isset( $_GET['action'], $_GET['id'] ) && $_GET['action'] === 'edit' ) { $item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment WHERE id = %d", intval( $_GET['id'] ) ) ); } $equipment_list = $wpdb->get_results( "SELECT * FROM $table_equipment ORDER BY name ASC" ); ?>

Zarządzaj Sprzętem

NazwaAkcje
name ); ?>Edytuj | Usuń
prefix . 'mystat_activities'; $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' ); // Pobierz dostępne lata z bazy danych $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" ); if ( empty( $available_years ) ) { $available_years = [current_time('Y')]; // Domyślny rok, jeśli brak danych } // Zapytanie SQL do grupowania danych miesięcznie $sql = $wpdb->prepare(" SELECT MONTH(date) as month_num, SUM(distance) as total_distance, SUM(calories) as total_calories, SUM(TIME_TO_SEC(duration)) as total_seconds, COUNT(id) as activity_count FROM $table_activities WHERE YEAR(date) = %d GROUP BY month_num ORDER BY month_num ASC ", $current_year); $monthly_summary = $wpdb->get_results( $sql, OBJECT_K ); // OBJECT_K zwróci tablicę z kluczami będącymi numerami miesięcy // Przygotowanie danych dla wszystkich 12 miesięcy $full_year_summary = []; $total_year_distance = 0; $total_year_calories = 0; $total_year_seconds = 0; // Określ, ile miesięcy pokazać, aby uniknąć zer dla przyszłych miesięcy $this_year = (int) current_time('Y'); $this_month = (int) current_time('n'); $loop_until_month = 12; // Domyślnie dla lat ubiegłych if ( $current_year == $this_year ) { // Dla bieżącego roku, pokaż miesiące do bieżącego miesiąca $loop_until_month = $this_month; } elseif ( $current_year > $this_year ) { // Dla przyszłych lat, pokaż miesiące tylko do ostatniego, w którym są dane $last_month_with_data = $wpdb->get_var( $wpdb->prepare( "SELECT MAX(MONTH(date)) FROM $table_activities WHERE YEAR(date) = %d", $current_year ) ); $loop_until_month = $last_month_with_data ? (int) $last_month_with_data : 0; } for ( $i = 1; $i <= $loop_until_month; $i++ ) { $month_name = date_i18n( 'F', mktime( 0, 0, 0, $i, 10 ) ); // Nazwa miesiąca $data = isset( $monthly_summary[$i] ) ? $monthly_summary[$i] : null; $full_year_summary[$i] = (object) [ 'month_name' => $month_name, 'total_distance' => $data ? $data->total_distance : 0, 'total_calories' => $data ? (int)$data->total_calories : 0, 'total_seconds' => $data ? (int)$data->total_seconds : 0, 'activity_count' => $data ? (int)$data->activity_count : 0, ]; $total_year_distance += $full_year_summary[$i]->total_distance; $total_year_seconds += $full_year_summary[$i]->total_seconds; $total_year_calories += $full_year_summary[$i]->total_calories; } $total_year_hours = floor($total_year_seconds / 3600); $total_year_minutes = floor(($total_year_seconds % 3600) / 60); $total_year_duration_formatted = sprintf('%d godz. %d min.', $total_year_hours, $total_year_minutes); // Przygotowanie danych dla wykresu $chart_labels_js = []; $chart_datasets = [ 'distance' => [], 'duration' => [], 'calories' => [], 'activities' => [], ]; foreach ($full_year_summary as $month_data) { $chart_labels_js[] = $month_data->month_name; $chart_datasets['distance'][] = round((float)$month_data->total_distance, 2); $chart_datasets['duration'][] = round($month_data->total_seconds / 3600, 2); // w godzinach $chart_datasets['calories'][] = $month_data->total_calories; $chart_datasets['activities'][] = $month_data->activity_count; } // Włączenie skryptów dla Chart.js wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true); wp_register_script('mystat-chart-loader', false); wp_enqueue_script('mystat-chart-loader'); $chart_configs = [ 'distance' => [ 'label' => 'Dystans (km)', 'data' => $chart_datasets['distance'], 'backgroundColor' => 'rgba(52, 152, 219, 0.5)', 'borderColor' => 'rgba(52, 152, 219, 1)', 'yAxisLabel' => 'Kilometry' ], 'duration' => [ 'label' => 'Czas trwania (godz.)', 'data' => $chart_datasets['duration'], 'backgroundColor' => 'rgba(26, 188, 156, 0.5)', 'borderColor' => 'rgba(26, 188, 156, 1)', 'yAxisLabel' => 'Godziny' ], 'calories' => [ 'label' => 'Kalorie (kcal)', 'data' => $chart_datasets['calories'], 'backgroundColor' => 'rgba(231, 76, 60, 0.5)', 'borderColor' => 'rgba(231, 76, 60, 1)', 'yAxisLabel' => 'kcal' ], 'activities' => [ 'label' => 'Liczba aktywności', 'data' => $chart_datasets['activities'], 'backgroundColor' => 'rgba(241, 196, 15, 0.5)', 'borderColor' => 'rgba(241, 196, 15, 1)', 'yAxisLabel' => 'Ilość' ], ]; wp_add_inline_script('mystat-chart-loader', ' document.addEventListener("DOMContentLoaded", function() { const chartLabels = ' . json_encode($chart_labels_js) . '; const chartConfigs = ' . json_encode($chart_configs) . '; let activeChart = null; const tabs = document.querySelectorAll(".nav-tab"); tabs.forEach(tab => { tab.addEventListener("click", function(e) { e.preventDefault(); tabs.forEach(t => t.classList.remove("nav-tab-active")); this.classList.add("nav-tab-active"); const chartType = this.getAttribute("href").substring(1); renderChart(chartType); }); }); function renderChart(type) { if (activeChart) { activeChart.destroy(); } const config = chartConfigs[type]; const ctx = document.getElementById("mystatYearlyChart").getContext("2d"); activeChart = new Chart(ctx, { type: "bar", data: { labels: chartLabels, datasets: [{ label: config.label, data: config.data, backgroundColor: config.backgroundColor, borderColor: config.borderColor, borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, title: { display: true, text: config.yAxisLabel } } } } }); } // Render initial chart if (tabs.length > 0) { renderChart(tabs[0].getAttribute("href").substring(1)); } }); '); ?>

Podsumowanie Roczne

Wykresy dla

MiesiącDystans (km)Kalorie (kcal)Czas
month_name ); ?> total_distance, 2, ',', ' ' ); ?> total_calories, 0, ',', ' ' ); ?> total_seconds) ); ?>
SUMA ROCZNA
20 ] ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { return []; } $gpx_content = wp_remote_retrieve_body( $response ); if ( empty( $gpx_content ) ) { return []; } libxml_use_internal_errors( true ); $gpx = simplexml_load_string( $gpx_content ); libxml_clear_errors(); if ( $gpx === false ) { return []; } $track_data = [ 'points' => [], 'elevation_profile' => [], ]; $raw_points = []; // Extract raw points with lat, lon, ele if ( isset( $gpx->trk ) ) { foreach ( $gpx->trk->trkseg as $trkseg ) { foreach ( $trkseg->trkpt as $trkpt ) { if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) { $raw_points[] = [ 'lat' => (float) $trkpt['lat'], 'lon' => (float) $trkpt['lon'], 'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null, ]; } } } } if ( empty( $raw_points ) ) { return $track_data; } // Process raw points to calculate profile $cumulative_distance = 0; $elevation_profile = []; $map_points = []; $haversine = function( $lat1, $lon1, $lat2, $lon2 ) { $earth_radius = 6371; // in km $dLat = deg2rad( $lat2 - $lat1 ); $dLon = deg2rad( $lon2 - $lon1 ); $a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 ); $c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) ); return $earth_radius * $c; }; foreach ( $raw_points as $i => $point ) { $map_points[] = [ $point['lat'], $point['lon'] ]; if ( $i > 0 ) { $prev_point = $raw_points[ $i - 1 ]; $distance_increment = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); $cumulative_distance += $distance_increment; } if ( ! is_null( $point['ele'] ) ) { $elevation_profile[] = [ 'distance' => round( $cumulative_distance, 3 ), // in km 'elevation' => round( $point['ele'], 2 ), // in m ]; } } $track_data['points'] = $map_points; $track_data['elevation_profile'] = $elevation_profile; return $track_data; } function mystat_infographic_page() { global $wpdb; $table_activities = $wpdb->prefix . 'mystat_activities'; $table_categories = $wpdb->prefix . 'mystat_categories'; $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' ); // Pobierz dostępne lata z bazy danych $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" ); if ( empty( $available_years ) ) { $available_years = [current_time('Y')]; // Domyślny rok, jeśli brak danych } // --- 1. Statystyki ogólne (wszystkie lata) --- $overall_stats = $wpdb->get_row(" SELECT SUM(distance) as total_distance, SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration, SUM(total_elevation_gain) as total_elevation_gain, COUNT(id) as total_activities FROM $table_activities "); // --- 2. Statystyki dla wybranego roku --- $yearly_stats = $wpdb->get_row($wpdb->prepare(" SELECT SUM(distance) as total_distance, SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration, SUM(total_elevation_gain) as total_elevation_gain, COUNT(id) as total_activities FROM $table_activities WHERE YEAR(date) = %d ", $current_year)); // --- 3. Dane dla wykresu kołowego (dystans per kategoria dla wybranego roku) --- $category_distance_data = $wpdb->get_results($wpdb->prepare(" SELECT c.name as category_name, c.color, SUM(a.distance) as total_distance FROM $table_activities a LEFT JOIN $table_categories c ON a.category_id = c.id WHERE YEAR(a.date) = %d GROUP BY c.name, c.color HAVING SUM(a.distance) > 0 ORDER BY total_distance DESC ", $current_year)); $chart_labels = []; $chart_data = []; $chart_colors = []; foreach ($category_distance_data as $data) { $chart_labels[] = $data->category_name; $chart_data[] = round((float)$data->total_distance, 2); $chart_colors[] = $data->color; } // Włączenie skryptów dla Chart.js wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true); wp_register_script('mystat-infographic-chart-loader', false); wp_enqueue_script('mystat-infographic-chart-loader'); wp_add_inline_script('mystat-infographic-chart-loader', ' document.addEventListener("DOMContentLoaded", function() { const ctx = document.getElementById("mystatCategoryPieChart"); if (!ctx) return; new Chart(ctx, { type: "pie", data: { labels: ' . json_encode($chart_labels) . ', datasets: [{ data: ' . json_encode($chart_data) . ', backgroundColor: ' . json_encode($chart_colors) . ', hoverOffset: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "right", }, title: { display: true, text: "Dystans wg kategorii w ' . esc_js($current_year) . '" } } } }); }); '); ?>

Infografika Statystyk Sportowych

Statystyki Ogólne (wszystkie lata)

Dystans

total_distance, 2, ',', ' '); ?> km

Czas

total_duration); ?>

Wznios

total_elevation_gain, 0, ',', ' '); ?> m

Aktywności

total_activities, 0, ',', ' '); ?>

Statystyki dla

Dystans

total_distance, 2, ',', ' '); ?> km

Czas

total_duration); ?>

Wznios

total_elevation_gain, 0, ',', ' '); ?> m

Aktywności

total_activities, 0, ',', ' '); ?>

Rozkład Dystansu wg Kategorii w

Importuj aktywności z pliku CSV

'; // Handle the form submission if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['mystat_csv_import_nonce_field'] ) ) { mystat_handle_csv_import(); } // Display the form ?>

Instrukcje i formularz importu

Aby zaimportować dane, możesz wgrać plik CSV LUB wkleić dane bezpośrednio w pole tekstowe poniżej. Ta druga opcja jest zalecana, jeśli napotykasz błędy z plikiem.

Wymagane kolumny: Data, Tytuł, Dystans oraz Typ aktywności (lub Kategoria).

Opcjonalne kolumny: Czas (format HH:MM:SS), Kalorie, Średnie tętno, Maksymalne tętno, Średnia prędkość, Maksymalna prędkość, Średni rytm pedałowania, Maksymalny rytm pedałowania, Całkowity wznios, Całkowity spadek, Minimalna wysokość, Maksymalna wysokość, Sprzęt, Typ wydarzenia.

Ważne:


'; } function mystat_handle_csv_import() { global $wpdb; if ( ! isset( $_POST['mystat_csv_import_nonce_field'] ) || ! wp_verify_nonce( $_POST['mystat_csv_import_nonce_field'], 'mystat_csv_import_nonce' ) ) { echo '

Błąd weryfikacji bezpieczeństwa.

'; return; } if ( ! current_user_can( 'manage_options' ) ) { echo '

Nie masz wystarczających uprawnień.

'; return; } // Unify input source: prefer textarea, fall back to file upload. $csv_content = ''; if ( ! empty( $_POST['mystat_csv_data'] ) ) { $csv_content = stripslashes( $_POST['mystat_csv_data'] ); } elseif ( ! empty( $_FILES['mystat_csv_file']['tmp_name'] ) && UPLOAD_ERR_OK === $_FILES['mystat_csv_file']['error'] ) { $csv_content = file_get_contents( $_FILES['mystat_csv_file']['tmp_name'] ); } if ( empty( trim( $csv_content ) ) ) { echo '

Nie podano danych do importu. Wgraj plik lub wklej dane w pole tekstowe.

'; return; } // Mapowanie polskich i angielskich nazw kolumn na wewnętrzne klucze $column_map = [ // Polish => English 'typ aktywności' => 'category_name', 'data' => 'date', 'tytuł' => 'title', 'dystans' => 'distance', 'kalorie' => 'calories', 'czas' => 'duration', 'średnie tętno' => 'avg_heart_rate', 'maksymalne tętno' => 'max_heart_rate', 'średnia prędkość' => 'avg_speed', 'maksymalna prędkość' => 'max_speed', 'średni rytm pedałowania' => 'avg_cadence', 'maksymalny rytm pedałowania' => 'max_cadence', 'całkowity wznios' => 'total_elevation_gain', 'całkowity spadek' => 'total_elevation_loss', 'minimalna wysokość' => 'min_altitude', 'maksymalna wysokość' => 'max_altitude', 'sprzęt' => 'equipment_name', 'typ wydarzenia' => 'event_type_name', 'komentarz' => 'comment', 'link do strava' => 'strava_url', // English keys for compatibility 'category_name' => 'category_name', 'date' => 'date', 'title' => 'title', 'distance' => 'distance', 'calories' => 'calories', 'duration' => 'duration', 'avg_heart_rate' => 'avg_heart_rate', 'max_heart_rate' => 'max_heart_rate', 'avg_speed' => 'avg_speed', 'max_speed' => 'max_speed', 'avg_cadence' => 'avg_cadence', 'max_cadence' => 'max_cadence', 'total_elevation_gain' => 'total_elevation_gain', 'total_elevation_loss' => 'total_elevation_loss', 'min_altitude' => 'min_altitude', 'max_altitude' => 'max_altitude', 'equipment_name' => 'equipment_name', 'event_type_name' => 'event_type_name', 'comment' => 'comment', 'strava_url' => 'strava_url', ]; // --- START: Robust, case-insensitive lookup --- $table_categories = $wpdb->prefix . 'mystat_categories'; $table_event_types = $wpdb->prefix . 'mystat_event_types'; $table_equipment = $wpdb->prefix . 'mystat_equipment'; $create_lookup = function( $table_name ) use ( $wpdb ) { $items = $wpdb->get_results( "SELECT id, name FROM {$table_name}" ); $lookup = []; if ( is_array( $items ) ) { foreach ( $items as $item ) { // Use a robust trim to handle various whitespace characters and make it case-insensitive $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $item->name ); $lookup[ mb_strtolower( $clean_name, 'UTF-8' ) ] = $item->id; } } return $lookup; }; $categories_lookup = $create_lookup( $table_categories ); $event_types_lookup = $create_lookup( $table_event_types ); $equipment_lookup = $create_lookup( $table_equipment ); // --- END: Robust, case-insensitive lookup --- // Process the CSV file $table_activities = $wpdb->prefix . 'mystat_activities'; $imported_count = 0; $skipped_details = []; $row_number = 1; // Header is row 1 // Normalize line endings and split into lines $lines = str_replace( ["\r\n", "\r"], "\n", $csv_content ); $lines = explode( "\n", $lines ); if ( empty($lines) || empty(trim($lines[0])) ) { echo '

Podane dane CSV są puste.

'; return; } // --- Delimiter and BOM detection --- $first_line = $lines[0]; $delimiter = (substr_count($first_line, ';') > substr_count($first_line, ',')) ? ';' : ','; // --- BOM removal from first header element --- $bom = "\xEF\xBB\xBF"; if (substr($first_line, 0, 3) === $bom) { $lines[0] = substr($first_line, 3); } $header_raw = str_getcsv( array_shift( $lines ), $delimiter ); $header_raw = array_map('trim', $header_raw); // Translate header from Polish/English to internal keys $header = []; foreach ($header_raw as $col) { $header[] = $column_map[strtolower($col)] ?? 'ignored_' . uniqid(); } $required_internal_keys = ['date', 'title', 'category_name', 'distance']; $missing_keys = array_diff($required_internal_keys, $header); if (!empty($missing_keys)) { $key_to_polish_map = [ 'date' => 'Data', 'title' => 'Tytuł', 'category_name' => 'Kategoria / Typ aktywności', 'distance' => 'Dystans', ]; $missing_polish_names = array_map(fn($key) => $key_to_polish_map[$key] ?? $key, $missing_keys); echo '

Brak wymaganych kolumn w danych CSV: ' . esc_html(implode(', ', $missing_polish_names)) . '

'; return; } $parse_and_round_int = fn($val) => round(floatval(str_replace(',', '.', $val))); $null_if_empty = fn($value) => $value !== '' ? $value : null; foreach ( $lines as $line ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found $row_number++; if ( empty( trim( $line ) ) ) { continue; // Skip empty lines } $data = str_getcsv( $line, $delimiter ); if (count($data) !== count($header)) { $skipped_details[] = [ 'row' => $row_number, 'reason' => 'Nieprawidłowa liczba kolumn (oczekiwano ' . count($header) . ', otrzymano ' . count($data) . ').', 'data' => $line, ]; continue; } $row_data = array_combine( $header, $data ); // Detailed validation $validation_errors = []; if ( empty( $row_data['date'] ) ) { $validation_errors[] = 'brak daty'; } if ( empty( $row_data['title'] ) ) { $validation_errors[] = 'brak tytułu'; } if ( ! isset( $row_data['distance'] ) || $row_data['distance'] === '' ) { $validation_errors[] = 'brak dystansu'; } $category_name = $row_data['category_name'] ?? ''; // Use a robust trim to handle various whitespace characters and make it case-insensitive $clean_category_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $category_name ); $category_id = $categories_lookup[ mb_strtolower( $clean_category_name, 'UTF-8' ) ] ?? null; if ( empty( $clean_category_name ) ) { $validation_errors[] = 'brak nazwy kategorii'; } elseif ( is_null( $category_id ) ) { $available_categories_from_db = $wpdb->get_col( "SELECT name FROM $table_categories ORDER BY name" ); $validation_errors[] = 'nieznana kategoria: "' . esc_html( $category_name ) . '". Sprawdź, czy nazwa jest poprawna. Dostępne w bazie: "' . esc_html( implode( '", "', $available_categories_from_db ) ) . '".'; } if ( ! empty( $validation_errors ) ) { $skipped_details[] = [ 'row' => $row_number, 'reason' => ucfirst( implode( ', ', $validation_errors ) ) . '.', 'data' => $line, ]; continue; } // Get IDs for optional fields using the same robust method $get_id = function( $name, $lookup_table ) { $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $name ); return $lookup_table[ mb_strtolower( $clean_name, 'UTF-8' ) ] ?? null; }; $equipment_id = $get_id( $row_data['equipment_name'] ?? '', $equipment_lookup ); $event_type_id = $get_id( $row_data['event_type_name'] ?? '', $event_types_lookup ); $insert_data = [ 'date' => sanitize_text_field( $row_data['date'] ), 'title' => sanitize_text_field( $row_data['title'] ), 'category_id' => $category_id, 'distance' => floatval( str_replace( ',', '.', $row_data['distance'] ) ), 'duration' => isset( $row_data['duration'] ) ? sanitize_text_field( $row_data['duration'] ) : '00:00:00', 'calories' => isset( $row_data['calories'] ) ? intval( str_replace( ',', '.', $row_data['calories'] ) ) : 0, 'comment' => isset( $row_data['comment'] ) ? sanitize_textarea_field( $row_data['comment'] ) : null, 'strava_url' => isset( $row_data['strava_url'] ) ? $null_if_empty( esc_url_raw( $row_data['strava_url'] ) ) : null, 'avg_heart_rate' => isset( $row_data['avg_heart_rate'] ) ? $null_if_empty( $parse_and_round_int( $row_data['avg_heart_rate'] ) ) : null, 'max_heart_rate' => isset( $row_data['max_heart_rate'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_heart_rate'] ) ) : null, 'avg_speed' => isset( $row_data['avg_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $row_data['avg_speed'] ) ) ) : null, 'max_speed' => isset( $row_data['max_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $row_data['max_speed'] ) ) ) : null, 'avg_cadence' => isset( $row_data['avg_cadence'] ) ? $null_if_empty( $parse_and_round_int( $row_data['avg_cadence'] ) ) : null, 'max_cadence' => isset( $row_data['max_cadence'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_cadence'] ) ) : null, 'total_elevation_gain' => isset( $row_data['total_elevation_gain'] ) ? $null_if_empty( $parse_and_round_int( $row_data['total_elevation_gain'] ) ) : null, 'total_elevation_loss' => isset( $row_data['total_elevation_loss'] ) ? $null_if_empty( $parse_and_round_int( $row_data['total_elevation_loss'] ) ) : null, 'min_altitude' => isset( $row_data['min_altitude'] ) ? $null_if_empty( $parse_and_round_int( $row_data['min_altitude'] ) ) : null, 'max_altitude' => isset( $row_data['max_altitude'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_altitude'] ) ) : null, 'equipment_id' => $equipment_id, 'event_type_id' => $event_type_id, ]; if ( $wpdb->insert( $table_activities, $insert_data ) ) { $imported_count++; } else { $skipped_details[] = [ 'row' => $row_number, 'reason' => 'Błąd zapisu do bazy danych. (' . esc_html( $wpdb->last_error ) . ')', 'data' => $line, ]; } } if ( $imported_count > 0 ) echo '

Pomyślnie zaimportowano ' . esc_html( $imported_count ) . ' aktywności.

'; if ( ! empty( $skipped_details ) ) { echo '
'; echo '

Pominięto ' . count( $skipped_details ) . ' wierszy z powodu błędów

'; echo '
'; echo ''; foreach ( $skipped_details as $error ) { echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
WierszPowód pominięciaDane wiersza
' . esc_html( $error['row'] ) . '' . esc_html( $error['reason'] ) . '' . esc_html( wp_trim_words( $error['data'], 25, '...' ) ) . '
'; echo '
'; } if ( $imported_count === 0 && empty($skipped_details) && $row_number > 1) echo '

Dane CSV nie zawierały żadnych poprawnych wierszy do importu.

'; elseif ($row_number === 1) echo '

Dane CSV były puste lub zawierały tylko nagłówek.

'; } function mystat_edit_activity_page() { global $wpdb; $activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; if ( $activity_id === 0 ) { echo '

Błąd

Nie podano ID aktywności do edycji.

'; return; } // Handle form submission for update mystat_handle_activity_form_submission(); $table_activities = $wpdb->prefix . 'mystat_activities'; $activity = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_activities WHERE id = %d", $activity_id ) ); if ( ! $activity ) { echo '

Błąd

Nie znaleziono aktywności o podanym ID.

'; return; } echo '

Edytuj Trening

'; mystat_render_add_form( $activity ); echo '
'; } function mystat_view_activity_page() { global $wpdb; $activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; if ( $activity_id === 0 ) { echo '

Błąd

Nie podano ID aktywności.

'; return; } $table_activities = $wpdb->prefix . 'mystat_activities'; $table_categories = $wpdb->prefix . 'mystat_categories'; $table_event_types = $wpdb->prefix . 'mystat_event_types'; $table_equipment = $wpdb->prefix . 'mystat_equipment'; $sql = $wpdb->prepare(" SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name FROM $table_activities a LEFT JOIN $table_categories c ON a.category_id = c.id LEFT JOIN $table_event_types et ON a.event_type_id = et.id LEFT JOIN $table_equipment eq ON a.equipment_id = eq.id WHERE a.id = %d ", $activity_id); $activity = $wpdb->get_row( $sql ); if ( ! $activity ) { echo '

Błąd

Nie znaleziono aktywności o podanym ID.

'; return; } // Funkcja pomocnicza do wyświetlania wiersza, jeśli wartość istnieje $render_row = function($label, $value, $unit = '') { if ( ! is_null($value) && $value !== '' ) { echo ''; echo '' . esc_html($label) . ''; echo '' . esc_html($value) . ($unit ? ' ' . esc_html($unit) : '') . ''; echo ''; } }; // Prepare map and chart data if GPX exists $gpx_data = []; if ( ! empty( $activity->gpx_url ) ) { $gpx_data = mystat_parse_gpx_data( $activity->gpx_url ); if ( ! empty( $gpx_data['points'] ) ) { 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', [], '1.9.4', true); wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true); wp_register_script('mystat-details-loader', false); wp_enqueue_script('mystat-details-loader'); $map_script = ' const track_points = ' . json_encode($gpx_data['points']) . '; if (typeof L !== "undefined" && track_points.length > 0) { const map = L.map("mystat-activity-map"); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© OpenStreetMap contributors\' }).addTo(map); const polyline = L.polyline(track_points, {color: "' . esc_js( $activity->color ) . '"}).addTo(map); map.fitBounds(polyline.getBounds().pad(0.1)); L.marker(track_points[0]).addTo(map).bindPopup("Start"); L.marker(track_points[track_points.length - 1]).addTo(map).bindPopup("Koniec"); } '; $elevation_chart_script = ''; if ( ! empty( $gpx_data['elevation_profile'] ) ) { $elevation_chart_script = ' const elevation_data = ' . json_encode( $gpx_data['elevation_profile'] ) . '; if (typeof Chart !== "undefined" && elevation_data.length > 0) { const ctx = document.getElementById("mystat-elevation-chart").getContext("2d"); new Chart(ctx, { type: "line", data: { labels: elevation_data.map(p => p.distance), datasets: [{ label: "Wysokość (m)", data: elevation_data.map(p => p.elevation), borderColor: "' . esc_js( $activity->color ) . '", backgroundColor: "' . esc_js( $activity->color ) . '20", fill: true, pointRadius: 0, tension: 0.1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { title: { display: true, text: "Dystans (km)" } }, y: { title: { display: true, text: "Wysokość (m n.p.m.)" } } }, plugins: { legend: { display: false } } } }); } '; } wp_add_inline_script('mystat-details-loader', 'document.addEventListener("DOMContentLoaded", function() {' . $map_script . $elevation_chart_script . '});' ); } } ?>

Szczegóły treningu: title ); ?> Edytuj

← Powrót do listy aktywności

Podsumowanie

Główne dane

category_name); ?> date); ?> distance, 2, ',', ' '), 'km'); ?> duration); ?> calories, 'kcal'); ?> event_type_name); ?> equipment_name); ?>

Dane szczegółowe

avg_speed, 1, ',', ' '), 'km/h'); ?> max_speed, 1, ',', ' '), 'km/h'); ?> avg_heart_rate, 'bpm'); ?> max_heart_rate, 'bpm'); ?> avg_cadence, 'rpm'); ?> max_cadence, 'rpm'); ?> total_elevation_gain, 'm'); ?> total_elevation_loss, 'm'); ?> min_altitude, 'm n.p.m.'); ?> max_altitude, 'm n.p.m.'); ?>

Notatki i linki

comment)); ?> strava_url ) ) : ?> gpx_url ) ) : ?>
StravaZobacz aktywność na Strava
Plik GPXPobierz plik GPX

Mapa Trasy

Profil Wysokości

gpx_url ) ) : ?>

Mapa Trasy

Nie udało się wczytać danych z pliku GPX lub plik jest uszkodzony/pusty.

0 ? 'mystat_edit_entry_' . $activity_id : 'mystat_add_entry'; // Weryfikacja bezpieczeństwa (Nonce) if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], $nonce_action ) ) { echo '

Błąd weryfikacji bezpieczeństwa formularza.

'; return; } $table_activities = $wpdb->prefix . 'mystat_activities'; // Przygotowanie danych (zamiana przecinka na kropkę w dystansie) $distance = isset($_POST['distance']) ? floatval( str_replace( ',', '.', $_POST['distance'] ) ) : 0; // Funkcja pomocnicza do zamiany pustych wartości na NULL, aby poprawnie zapisać je w bazie $null_if_empty = function($value) { return $value !== '' ? $value : null; }; $data = array( 'category_id' => intval( $_POST['category_id'] ), 'date' => sanitize_text_field( $_POST['date'] ), 'title' => sanitize_text_field( $_POST['title'] ), 'distance' => $distance, 'duration' => sanitize_text_field( $_POST['duration'] ), 'calories' => intval( $_POST['calories'] ), 'comment' => sanitize_textarea_field( $_POST['comment'] ), 'strava_url' => $null_if_empty( esc_url_raw( $_POST['strava_url'] ) ), 'avg_heart_rate' => $null_if_empty( intval( $_POST['avg_heart_rate'] ) ), 'max_heart_rate' => $null_if_empty( intval( $_POST['max_heart_rate'] ) ), 'avg_speed' => $null_if_empty( floatval( str_replace( ',', '.', $_POST['avg_speed'] ) ) ), 'max_speed' => $null_if_empty( floatval( str_replace( ',', '.', $_POST['max_speed'] ) ) ), 'avg_cadence' => $null_if_empty( intval( $_POST['avg_cadence'] ) ), 'max_cadence' => $null_if_empty( intval( $_POST['max_cadence'] ) ), 'total_elevation_gain' => $null_if_empty( intval( $_POST['total_elevation_gain'] ) ), 'total_elevation_loss' => $null_if_empty( intval( $_POST['total_elevation_loss'] ) ), 'min_altitude' => $null_if_empty( intval( $_POST['min_altitude'] ) ), 'max_altitude' => $null_if_empty( intval( $_POST['max_altitude'] ) ), 'equipment_id' => $null_if_empty( intval( $_POST['equipment_id'] ) ), 'gpx_url' => $null_if_empty( esc_url_raw( $_POST['gpx_url'] ) ), 'event_type_id' => $null_if_empty( intval( $_POST['event_type_id'] ) ), ); // Format danych dla $wpdb->insert $format = array( '%d', '%s', '%s', '%f', '%s', '%d', '%s', // Pola podstawowe '%s', '%d', '%d', '%f', '%f', '%d', '%d', // Tętno, prędkość, kadencja '%d', '%d', '%d', '%d', '%d', '%s', '%d' // Wysokość, sprzęt, linki, typ wydarzenia ); if ( $activity_id > 0 ) { // UPDATE $result = $wpdb->update( $table_activities, $data, [ 'id' => $activity_id ], $format, [ '%d' ] ); $message = 'Trening zaktualizowany pomyślnie!'; } else { // INSERT $result = $wpdb->insert( $table_activities, $data, $format ); $message = 'Trening dodany pomyślnie!'; } if ( $result !== false ) { echo '

' . esc_html( $message ) . '

'; } else { echo '

Wystąpił błąd podczas zapisu do bazy.

'; } } /** * Renderowanie formularza HTML */ function mystat_render_add_form( $activity = null ) { // Enqueue media scripts for the uploader wp_enqueue_media(); global $wpdb; $table_categories = $wpdb->prefix . 'mystat_categories'; $table_event_types = $wpdb->prefix . 'mystat_event_types'; $table_equipment = $wpdb->prefix . 'mystat_equipment'; $categories = $wpdb->get_results( "SELECT * FROM $table_categories ORDER BY name ASC" ); $event_types = $wpdb->get_results( "SELECT * FROM $table_event_types ORDER BY name ASC" ); $equipment_list = $wpdb->get_results( "SELECT * FROM $table_equipment ORDER BY name ASC" ); $is_edit_mode = ! is_null( $activity ); $nonce_action = $is_edit_mode ? 'mystat_edit_entry_' . $activity->id : 'mystat_add_entry'; $form_title = $is_edit_mode ? 'Edytuj Aktywność' : 'Dodaj Nową Aktywność'; $button_text = $is_edit_mode ? 'Zaktualizuj Trening' : 'Zapisz Trening'; ?>


Dane szczegółowe (opcjonalne)


Linki zewnętrzne (opcjonalne)

prefix // Jeśli tabele są "sztywne" (bez prefixu wp_), usuń $wpdb->prefix. $table_activities = $wpdb->prefix . 'mystat_activities'; $table_categories = $wpdb->prefix . 'mystat_categories'; // --- 1. OBSŁUGA USUWANIA (DELETE) --- if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && $_GET['action'] === 'mystat_delete' ) { $activity_id = intval( $_GET['id'] ); // Weryfikacja bezpieczeństwa (Nonce) if ( wp_verify_nonce( $_GET['_wpnonce'], 'mystat_delete_' . $activity_id ) ) { $result = $wpdb->delete( $table_activities, array( 'id' => $activity_id ), array( '%d' ) ); if ( $result ) { echo '

Aktywność została usunięta.

'; } else { echo '

Wystąpił błąd podczas usuwania.

'; } } else { echo '

Błąd weryfikacji bezpieczeństwa (Nonce).

'; } } // --- 2. USTAWIENIA PAGINACJI --- $items_per_page = 20; // Ile wpisów na stronę $current_page = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1; $offset = ( $current_page - 1 ) * $items_per_page; // --- 3. POBIERANIE DANYCH (SELECT) --- // Pobranie całkowitej liczby wpisów do paginacji $total_items = $wpdb->get_var( "SELECT COUNT(id) FROM $table_activities" ); $total_pages = ceil( $total_items / $items_per_page ); // Pobieramy wpisy dla bieżącej strony $sql = $wpdb->prepare(" SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name FROM $table_activities a LEFT JOIN $table_categories c ON a.category_id = c.id LEFT JOIN {$wpdb->prefix}mystat_event_types et ON a.event_type_id = et.id LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id ORDER BY a.date DESC, a.id DESC LIMIT %d OFFSET %d ", $items_per_page, $offset); $activities = $wpdb->get_results( $sql ); // --- 4. WIDOK TABELI (HTML) --- ?>

Historia Aktywności

1 ) : ?>
aktywności add_query_arg( 'paged', '%#%' ), 'format' => '', 'total' => $total_pages, 'current' => $current_page, 'prev_text' => '« Poprzednia', 'next_text' => 'Następna »', ) ); ?>
'mystat_delete', 'id' => $row->id, ) ), 'mystat_delete_' . $row->id ); $edit_url = add_query_arg( array( 'page' => 'mystat-edit-activity', 'id' => $row->id ), admin_url( 'admin.php' ) ); $details_url = add_query_arg( array( 'page' => 'mystat-view-activity', 'id' => $row->id ), admin_url( 'admin.php' ) ); ?>
Ikona Data Tytuł Kategoria Typ Sprzęt Dystans (km) Czas Śr. prędkość Akcja
icon ) ) : ?> date ); ?> title ? wp_trim_words( $row->title, 6 ) : '(bez tytułu)' ); ?> category_name ); ?> event_type_name ); ?> equipment_name ); ?> distance, 2, ',', ' ' ); ?> duration ); ?> avg_speed ? number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h' : '-'; ?> Edytuj Szczegóły Usuń
Brak zarejestrowanych aktywności. Dodaj pierwszy trening powyżej!
1 ) : ?>
aktywności add_query_arg( 'paged', '%#%' ), 'format' => '', 'total' => $total_pages, 'current' => $current_page, 'prev_text' => '« Poprzednia', 'next_text' => 'Następna »', ) ); ?>
current_time( 'Y' ), 'month' => current_time( 'n' ), ), $atts, 'moje_statystyki' ); $year = intval( $atts['year'] ); $month = intval( $atts['month'] ); // Pobieranie danych z bazy $table_activities = $wpdb->prefix . 'mystat_activities'; $sql = $wpdb->prepare(" SELECT a.*, c.name as category_name, eq.name as equipment_name FROM $table_activities a LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id LEFT JOIN {$wpdb->prefix}mystat_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(); ?>

Podsumowanie miesiąca

Całkowity dystans Całkowity czas
km

Lista aktywności

avg_speed || $row->avg_heart_rate || $row->avg_cadence ) : ?>
Data Tytuł Kategoria Dystans Czas Sprzęt
date ) ) ); ?> title ); ?> category_name ); ?> distance, 2, ',', ' ' ); ?> km duration ); ?> equipment_name ); ?>
avg_speed) echo ''; ?> avg_heart_rate) echo ''; ?> avg_cadence) echo ''; ?> avg_speed) echo ''; ?> avg_heart_rate) echo ''; ?> avg_cadence) echo ''; ?>
Śr. prędkośćŚr. tętnoŚr. kadencja
' . number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h' . $row->avg_heart_rate . ' bpm' . $row->avg_cadence . ' rpm
Brak aktywności w tym miesiącu.
0, ), $atts, 'moje_statystyki_wpis' ); $activity_id = intval( $atts['id'] ); if ( $activity_id === 0 ) { return '

Błąd: Nie podano ID wpisu w shortcode.

'; } // Pobieranie danych z bazy $table_activities = $wpdb->prefix . 'mystat_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}mystat_categories c ON a.category_id = c.id LEFT JOIN {$wpdb->prefix}mystat_event_types et ON a.event_type_id = et.id LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id WHERE a.id = %d ", $activity_id); $activity = $wpdb->get_row( $sql ); if ( ! $activity ) { return '

Błąd: Nie znaleziono wpisu o ID ' . esc_html($activity_id) . '.

'; } // Funkcja pomocnicza do wyświetlania wiersza $render_row = function($label, $value, $unit = '') { if ( ! is_null($value) && $value !== '' && $value != 0) { echo ''; echo '' . esc_html($label) . ''; echo '' . esc_html($value) . ($unit ? ' ' . esc_html($unit) : '') . ''; echo ''; } }; ob_start(); // Prepare map data if GPX exists $gpx_data = []; if ( ! empty( $activity->gpx_url ) ) { $gpx_data = mystat_parse_gpx_data( $activity->gpx_url ); if ( ! empty( $gpx_data['points'] ) ) { // Enqueue scripts for the frontend 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', [], '1.9.4', true); wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true); $map_id = 'mystat-map-' . esc_attr( $activity->id ); $chart_id = 'mystat-chart-' . esc_attr( $activity->id ); // Pass track data to a script wp_register_script('mystat-shortcode-loader-' . $activity->id, false); wp_enqueue_script('mystat-shortcode-loader-' . $activity->id); $map_script = ' const trackPoints = ' . json_encode( $gpx_data['points'] ) . '; const mapId = "' . esc_js($map_id) . '"; var container = L.DomUtil.get(mapId); if(container != null) { container._leaflet_id = null; } const map = L.map(mapId); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© OpenStreetMap\' }).addTo(map); const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map); map.fitBounds(polyline.getBounds().pad(0.1)); '; $elevation_chart_script = ''; if ( ! empty( $gpx_data['elevation_profile'] ) ) { $elevation_chart_script = ' const elevationData = ' . json_encode( $gpx_data['elevation_profile'] ) . '; const chartId = "' . esc_js($chart_id) . '"; const ctx = document.getElementById(chartId).getContext("2d"); new Chart(ctx, { type: "line", data: { labels: elevationData.map(p => p.distance), datasets: [{ label: "Wysokość (m)", data: elevationData.map(p => p.elevation), borderColor: "' . esc_js( $activity->category_color ) . '", backgroundColor: "' . esc_js( $activity->category_color ) . '20", fill: true, pointRadius: 0, tension: 0.1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { title: { display: true, text: "Dystans (km)" } }, y: { title: { display: true, text: "Wysokość (m n.p.m.)" } } }, plugins: { legend: { display: false } } } }); '; } wp_add_inline_script('mystat-shortcode-loader-' . $activity->id, '(function() { document.addEventListener("DOMContentLoaded", function() { if (typeof L === "undefined" || typeof Chart === "undefined") return; ' . $map_script . ' ' . $elevation_chart_script . ' }); })();' ); } } ?>

title ); ?>

date ) ) ); ?>

distance, 2, ',', ' '), 'km'); ?> duration); ?> avg_speed, 1, ',', ' '), 'km/h'); ?> total_elevation_gain, 'm'); ?>
category_name); ?> equipment_name); ?> strava_url ) ) : ?>
StravaZobacz aktywność