Update repo

This commit is contained in:
2026-04-22 12:51:16 +02:00
parent d303a55638
commit d31591e287
24 changed files with 3994 additions and 3501 deletions
+21 -21
View File
@@ -1,21 +1,21 @@
MIT License MIT License
Copyright (c) 2026 Jacek Fefliński Copyright (c) 2026 Jacek Fefliński
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
+2 -2
View File
@@ -1,2 +1,2 @@
# wp-cycling-ststs # wp-cycling-ststs
WP plugin for stats WP plugin for stats
+101 -60
View File
@@ -1,60 +1,101 @@
/* Styles for WordPress Activity Stats Plugin - Admin Area */ /* Styles for WordPress Activity Stats Plugin - Admin Area */
/* Infografika */ /* Infografika */
.statpress-infographic-grid { .statpress-infographic-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px; gap: 20px;
} }
.statpress-infographic-card { .statpress-infographic-card {
background: #fff; background: #fff;
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
border-radius: 5px; border-radius: 5px;
padding: 15px; padding: 15px;
text-align: center; text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.05); box-shadow: 0 2px 4px rgba(0,0,0,0.05);
} }
.statpress-infographic-card h3 { .statpress-infographic-card h3 {
margin-top: 0; margin-top: 0;
color: #555; color: #555;
font-size: 1.1em; font-size: 1.1em;
} }
.statpress-infographic-card p { .statpress-infographic-card p {
font-size: 1.8em; font-size: 1.8em;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
margin-bottom: 0; margin-bottom: 0;
} }
/* Kontener dla szczegółów aktywności */ /* Kontener dla szczegółów aktywności */
#statpress-details-container { #statpress-details-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 2%; gap: 2%;
} }
/* Szczegóły aktywności */ /* Szczegóły aktywności */
.statpress-details-col { .statpress-details-col {
flex: 1 1 48%; /* Pozwala na elastyczne dopasowanie, z bazową szerokością 48% */ flex: 1 1 48%; /* Pozwala na elastyczne dopasowanie, z bazową szerokością 48% */
min-width: 300px; /* Zapobiega zbytniemu ściskaniu kolumn na mniejszych ekranach */ min-width: 300px; /* Zapobiega zbytniemu ściskaniu kolumn na mniejszych ekranach */
} }
/* Chart Controls */ /* Chart Controls */
.statpress-chart-controls { .statpress-chart-controls {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 15px; margin-bottom: 15px;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
padding-bottom: 10px; padding-bottom: 10px;
} }
.statpress-chart-tabs.nav-tab-wrapper { .statpress-chart-tabs.nav-tab-wrapper {
border-bottom: none; border-bottom: none;
margin-bottom: 0; margin-bottom: 0;
} }
.statpress-xaxis-switcher { .statpress-xaxis-switcher {
padding-top: 5px; padding-top: 5px;
white-space: nowrap; white-space: nowrap;
} }
/* Form layout improvements */
.statpress-form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 1em;
}
.statpress-form-group {
background: #fdfdfd;
border: 1px solid #e5e5e5;
padding: 15px;
border-radius: 4px;
}
.statpress-form-group h3 {
margin-top: 0;
font-size: 1em;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 15px;
}
.statpress-form-group p {
margin: 0 0 1em;
}
.statpress-form-group label {
display: block;
margin-bottom: 5px;
}
/* GPX Parser Status */
#gpx_parse_status {
vertical-align: middle;
color: #50575e;
}
#gpx_parse_status .spinner {
margin-top: -2px;
}
+103 -103
View File
@@ -1,103 +1,103 @@
/* Styles for WordPress Activity Stats Plugin - Frontend Shortcodes */ /* Styles for WordPress Activity Stats Plugin - Frontend Shortcodes */
/* Shortcode [moje_statystyki] */ /* Shortcode [statpress_summary] */
.mystats-shortcode-container table { .statpress-shortcode-container table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin-bottom: 2em; margin-bottom: 2em;
} }
.mystats-shortcode-container th, .statpress-shortcode-container th,
.mystats-shortcode-container td { .statpress-shortcode-container td {
padding: 8px 12px; padding: 8px 12px;
border: 1px solid #ddd; border: 1px solid #ddd;
text-align: left; text-align: left;
} }
.mystats-shortcode-container th { .statpress-shortcode-container th {
background-color: #f4f4f4; background-color: #f4f4f4;
} }
.mystats-activity-table td:nth-child(4), .statpress-activity-table td:nth-child(4),
.mystats-activity-table td:nth-child(5) { .statpress-activity-table td:nth-child(5) {
text-align: right; text-align: right;
} }
/* Styles for the nested details table */ /* Styles for the nested details table */
.mystats-activity-details-row > .mystats-activity-details-cell { .statpress-activity-details-row > .statpress-activity-details-cell {
padding: 0 !important; padding: 0 !important;
border-top: none; border-top: none;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
.mystats-nested-details-table { .statpress-nested-details-table {
width: 100%; width: 100%;
margin: 0; margin: 0;
background-color: #fdfdfd; background-color: #fdfdfd;
border: none !important; border: none !important;
border-collapse: collapse; border-collapse: collapse;
} }
.mystats-nested-details-table th, .statpress-nested-details-table th,
.mystats-nested-details-table td { .statpress-nested-details-table td {
border: none !important; border: none !important;
padding: 6px 12px; padding: 6px 12px;
text-align: center; text-align: center;
font-size: 0.9em; font-size: 0.9em;
width: 33.33%; width: 33.33%;
} }
.mystats-nested-details-table th { .statpress-nested-details-table th {
background-color: transparent; background-color: transparent;
font-weight: normal; font-weight: normal;
color: #777; color: #777;
padding-top: 8px; padding-top: 8px;
padding-bottom: 2px; padding-bottom: 2px;
} }
.mystats-nested-details-table td { .statpress-nested-details-table td {
font-weight: bold; font-weight: bold;
padding-top: 0; padding-top: 0;
padding-bottom: 8px; padding-bottom: 8px;
} }
/* Shortcode [moje_statystyki_wpis] */ /* Shortcode [statpress_activity] */
.mystat-single-activity-shortcode { .statpress-single-activity-shortcode {
border: 1px solid #eee; border: 1px solid #eee;
padding: 15px; padding: 15px;
margin-bottom: 1.5em; margin-bottom: 1.5em;
border-radius: 5px; border-radius: 5px;
background: #f9f9f9; background: #f9f9f9;
} }
.mystat-single-activity-shortcode h4 { .statpress-single-activity-shortcode h4 {
margin-top: 0; margin-top: 0;
} }
.mystat-single-activity-shortcode p em { .statpress-single-activity-shortcode p em {
color: #777; color: #777;
font-size: 0.9em; font-size: 0.9em;
} }
.mystat-single-columns-container { .statpress-single-columns-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 30px; gap: 30px;
margin-bottom: 15px; margin-bottom: 15px;
} }
.mystat-single-col { .statpress-single-col {
flex: 1; flex: 1;
min-width: 240px; min-width: 240px;
} }
.mystat-single-summary-table { .statpress-single-summary-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
.mystat-single-summary-table th, .statpress-single-summary-table th,
.mystat-single-summary-table td { .statpress-single-summary-table td {
padding: 4px 0; padding: 4px 0;
border: none; border: none;
text-align: left; text-align: left;
vertical-align: top; vertical-align: top;
} }
.mystat-single-summary-table th { .statpress-single-summary-table th {
font-weight: bold; font-weight: bold;
padding-right: 1em; padding-right: 1em;
white-space: nowrap; white-space: nowrap;
width: 1%; width: 1%;
} }
+139 -139
View File
@@ -1,140 +1,140 @@
<?php <?php
/** /**
* Funkcje związane z aktywacją wtyczki, np. tworzenie tabel w bazie danych. * Funkcje związane z aktywacją wtyczki, np. tworzenie tabel w bazie danych.
* *
* @package WordPress Activity Stats * @package WordPress Activity Stats
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly. exit; // Exit if accessed directly.
} }
/** /**
* Główna funkcja aktywacyjna. Tworzy wszystkie niezbędne tabele w bazie danych. * Główna funkcja aktywacyjna. Tworzy wszystkie niezbędne tabele w bazie danych.
* *
* @return void * @return void
*/ */
function mystat_activate() { function statpress_activate() {
global $wpdb; global $wpdb;
$charset_collate = $wpdb->get_charset_collate(); $charset_collate = $wpdb->get_charset_collate();
$table_categories = $wpdb->prefix . 'mystat_categories'; $table_categories = $wpdb->prefix . 'statpress_categories';
$table_activities = $wpdb->prefix . 'mystat_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$table_event_types = $wpdb->prefix . 'mystat_event_types'; $table_event_types = $wpdb->prefix . 'statpress_event_types';
$table_equipment = $wpdb->prefix . 'mystat_equipment'; $table_equipment = $wpdb->prefix . 'statpress_equipment';
$table_equipment_log = $wpdb->prefix . 'mystat_equipment_log'; $table_equipment_log = $wpdb->prefix . 'statpress_equipment_log';
$table_goals = $wpdb->prefix . 'mystat_goals'; $table_goals = $wpdb->prefix . 'statpress_goals';
// SQL dla Kategorii // SQL dla Kategorii
$sql_cat = "CREATE TABLE $table_categories ( $sql_cat = "CREATE TABLE $table_categories (
id mediumint(9) NOT NULL AUTO_INCREMENT, id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(50) NOT NULL, name varchar(50) NOT NULL,
icon varchar(50) NOT NULL, icon varchar(50) NOT NULL,
color varchar(20) NOT NULL, color varchar(20) NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)
) $charset_collate;"; ) $charset_collate;";
// SQL dla Typów Wydarzeń // SQL dla Typów Wydarzeń
$sql_event_types = "CREATE TABLE $table_event_types ( $sql_event_types = "CREATE TABLE $table_event_types (
id mediumint(9) NOT NULL AUTO_INCREMENT, id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL, name varchar(100) NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)
) $charset_collate;"; ) $charset_collate;";
// SQL dla Sprzętu // SQL dla Sprzętu
$sql_equipment = "CREATE TABLE $table_equipment ( $sql_equipment = "CREATE TABLE $table_equipment (
id mediumint(9) NOT NULL AUTO_INCREMENT, id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL, name varchar(100) NOT NULL,
type varchar(50) DEFAULT 'Rower' NOT NULL, type varchar(50) DEFAULT 'Rower' NOT NULL,
purchase_date date DEFAULT NULL, purchase_date date DEFAULT NULL,
initial_cost decimal(10,2) DEFAULT NULL, initial_cost decimal(10,2) DEFAULT NULL,
status varchar(20) DEFAULT 'aktywny' NOT NULL, -- 'aktywny', 'sprzedany', 'wycofany' status varchar(20) DEFAULT 'aktywny' NOT NULL, -- 'aktywny', 'sprzedany', 'wycofany'
notes text, notes text,
PRIMARY KEY (id) PRIMARY KEY (id)
) $charset_collate;"; ) $charset_collate;";
// SQL dla Dziennika Serwisowego Sprzętu // SQL dla Dziennika Serwisowego Sprzętu
$sql_equipment_log = "CREATE TABLE $table_equipment_log ( $sql_equipment_log = "CREATE TABLE $table_equipment_log (
id bigint(20) NOT NULL AUTO_INCREMENT, id bigint(20) NOT NULL AUTO_INCREMENT,
equipment_id mediumint(9) NOT NULL, equipment_id mediumint(9) NOT NULL,
log_date date NOT NULL, log_date date NOT NULL,
log_type varchar(50) NOT NULL, -- np. Naprawa, Zakup, Przegląd, Modyfikacja log_type varchar(50) NOT NULL, -- np. Naprawa, Zakup, Przegląd, Modyfikacja
description text NOT NULL, description text NOT NULL,
cost decimal(10,2) DEFAULT NULL, cost decimal(10,2) DEFAULT NULL,
mileage int(11) DEFAULT NULL, -- Przebieg sprzętu w momencie serwisu mileage int(11) DEFAULT NULL, -- Przebieg sprzętu w momencie serwisu
PRIMARY KEY (id), PRIMARY KEY (id),
KEY equipment_id (equipment_id) KEY equipment_id (equipment_id)
) $charset_collate;"; ) $charset_collate;";
// SQL dla Celów // SQL dla Celów
$sql_goals = "CREATE TABLE $table_goals ( $sql_goals = "CREATE TABLE $table_goals (
id mediumint(9) NOT NULL AUTO_INCREMENT, id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL, name varchar(255) NOT NULL,
goal_type varchar(20) NOT NULL, -- 'distance', 'duration_sec', 'count' goal_type varchar(20) NOT NULL, -- 'distance', 'duration_sec', 'count'
target_value decimal(10,2) NOT NULL, target_value decimal(10,2) NOT NULL,
year smallint(4) NOT NULL, year smallint(4) NOT NULL,
month tinyint(2) UNSIGNED DEFAULT NULL, month tinyint(2) UNSIGNED DEFAULT NULL,
category_id mediumint(9) DEFAULT NULL, category_id mediumint(9) DEFAULT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
KEY category_id (category_id) KEY category_id (category_id)
) $charset_collate;"; ) $charset_collate;";
// SQL dla Aktywności // SQL dla Aktywności
$sql_act = "CREATE TABLE $table_activities ( $sql_act = "CREATE TABLE $table_activities (
id bigint(20) NOT NULL AUTO_INCREMENT, id bigint(20) NOT NULL AUTO_INCREMENT,
category_id mediumint(9) NOT NULL, category_id mediumint(9) NOT NULL,
date date NOT NULL, date date NOT NULL,
title varchar(255) DEFAULT '' NOT NULL, title varchar(255) DEFAULT '' NOT NULL,
distance decimal(10,2) DEFAULT 0.00, distance decimal(10,2) DEFAULT 0.00,
duration time DEFAULT '00:00:00', duration time DEFAULT '00:00:00',
calories int(11) DEFAULT 0, calories int(11) DEFAULT 0,
comment text, comment text,
strava_url varchar(255) DEFAULT NULL, strava_url varchar(255) DEFAULT NULL,
avg_heart_rate smallint(5) UNSIGNED DEFAULT NULL, avg_heart_rate smallint(5) UNSIGNED DEFAULT NULL,
max_heart_rate smallint(5) UNSIGNED DEFAULT NULL, max_heart_rate smallint(5) UNSIGNED DEFAULT NULL,
avg_speed decimal(5,2) DEFAULT NULL, avg_speed decimal(5,2) DEFAULT NULL,
max_speed decimal(5,2) DEFAULT NULL, max_speed decimal(5,2) DEFAULT NULL,
avg_cadence smallint(5) UNSIGNED DEFAULT NULL, avg_cadence smallint(5) UNSIGNED DEFAULT NULL,
max_cadence smallint(5) UNSIGNED DEFAULT NULL, max_cadence smallint(5) UNSIGNED DEFAULT NULL,
total_elevation_gain int(11) DEFAULT NULL, total_elevation_gain int(11) DEFAULT NULL,
total_elevation_loss int(11) DEFAULT NULL, total_elevation_loss int(11) DEFAULT NULL,
min_altitude int(11) DEFAULT NULL, min_altitude int(11) DEFAULT NULL,
max_altitude int(11) DEFAULT NULL, max_altitude int(11) DEFAULT NULL,
equipment_id mediumint(9) DEFAULT NULL, equipment_id mediumint(9) DEFAULT NULL,
gpx_url varchar(255) DEFAULT NULL, gpx_url varchar(255) DEFAULT NULL,
event_type_id mediumint(9) DEFAULT NULL, event_type_id mediumint(9) DEFAULT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)
) $charset_collate;"; ) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql_goals ); dbDelta( $sql_goals );
dbDelta( $sql_equipment ); dbDelta( $sql_equipment );
dbDelta( $sql_equipment_log ); dbDelta( $sql_equipment_log );
dbDelta( $sql_cat ); dbDelta( $sql_cat );
dbDelta( $sql_event_types ); dbDelta( $sql_event_types );
dbDelta( $sql_act ); dbDelta( $sql_act );
// Dodanie domyślnych kategorii, jeśli tabela jest pusta // Dodanie domyślnych kategorii, jeśli tabela jest pusta
if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_categories" ) ) { if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_categories" ) ) {
$wpdb->insert( $table_categories, array( 'name' => 'Rower', 'icon' => 'dashicons-buddicons-groups', 'color' => '#3498db' ) ); $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' ) ); $wpdb->insert( $table_categories, array( 'name' => 'Bieganie', 'icon' => 'dashicons-businessman', 'color' => '#e74c3c' ) );
} }
// Dodanie domyślnych typów wydarzeń, jeśli tabela jest pusta // Dodanie domyślnych typów wydarzeń, jeśli tabela jest pusta
if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_event_types" ) ) { if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_event_types" ) ) {
$default_event_types = array( 'Bez kategorii', 'Fitness', 'Geocaching', 'Podróżowanie', 'Rekreacyjny', 'Specjalne zdarzenie', 'Transport', 'Trening', 'Wyścig' ); $default_event_types = array( 'Bez kategorii', 'Fitness', 'Geocaching', 'Podróżowanie', 'Rekreacyjny', 'Specjalne zdarzenie', 'Transport', 'Trening', 'Wyścig' );
foreach ( $default_event_types as $type_name ) { foreach ( $default_event_types as $type_name ) {
$wpdb->insert( $table_event_types, array( 'name' => $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ą // 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' ) ); $training_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_event_types WHERE name = %s", 'Trening' ) );
if ( $training_id ) { 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 ) ); $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 // Dodanie domyślnego sprzętu, jeśli tabela jest pusta
if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_equipment" ) ) { if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_equipment" ) ) {
$wpdb->insert( $table_equipment, array( 'name' => 'Giant Revolt', 'type' => 'Rower', 'status' => 'aktywny' ) ); $wpdb->insert( $table_equipment, array( 'name' => 'Giant Revolt', 'type' => 'Rower', 'status' => 'aktywny' ) );
} }
} }
+26 -26
View File
@@ -1,27 +1,27 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/** /**
* Set up admin-specific hooks. * Set up admin-specific hooks.
*/ */
function mystat_admin_init_setup() { function statpress_admin_init_setup() {
add_filter( 'upload_mimes', 'mystat_add_gpx_mime_type' ); add_filter( 'upload_mimes', 'statpress_add_gpx_mime_type' );
add_filter( 'wp_check_filetype_and_ext', 'mystat_fix_gpx_upload_permission', 10, 4 ); add_filter( 'wp_check_filetype_and_ext', 'statpress_fix_gpx_upload_permission', 10, 4 );
mystat_register_settings(); statpress_register_settings();
} }
/** /**
* Enqueue admin-specific CSS. * Enqueue admin-specific CSS.
* *
* @param string $hook The current admin page hook. * @param string $hook The current admin page hook.
*/ */
function mystat_enqueue_admin_styles( $hook ) { function statpress_enqueue_admin_styles( $hook ) {
global $mystat_plugin_hooks; global $statpress_plugin_hooks;
if ( in_array( $hook, $mystat_plugin_hooks, true ) ) { if ( is_array( $statpress_plugin_hooks ) && in_array( $hook, $statpress_plugin_hooks, true ) ) {
$plugin_version = '1.0'; // You can use filemtime() for cache-busting in development $plugin_version = '1.0'; // You can use filemtime() for cache-busting in development
wp_enqueue_style( 'mystat-admin-styles', MYSTAT_PLUGIN_URL . 'assets/css/admin.css', array(), $plugin_version ); wp_enqueue_style( 'statpress-admin-styles', STATPRESS_PLUGIN_URL . 'assets/css/admin.css', array(), $plugin_version );
} }
} }
+122 -122
View File
@@ -1,123 +1,123 @@
<?php <?php
/** /**
* Admin Menu setup for the plugin. * Admin Menu setup for the plugin.
* *
* @package WordPress Activity Stats * @package WordPress Activity Stats
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly. exit; // Exit if accessed directly.
} }
/** /**
* Adds the main menu and sub-menu pages for the plugin. * Adds the main menu and sub-menu pages for the plugin.
* *
* @return void * @return void
*/ */
function mystat_add_admin_menu() { function statpress_add_admin_menu() {
global $mystat_plugin_hooks; global $statpress_plugin_hooks;
$mystat_plugin_hooks[] = add_menu_page( $statpress_plugin_hooks[] = add_menu_page(
'Moje Statystyki', // Tytuł strony 'StatPress Dashboard', // Tytuł strony
'Statystyki', // Tytuł w menu 'StatPress', // Tytuł w menu
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'moje-statystyki', // Slug menu 'statpress-dashboard', // Slug menu
'mystat_dashboard_page', // Funkcja renderująca stronę główną (dashboard) 'statpress_dashboard_page', // Funkcja renderująca stronę główną (dashboard)
'dashicons-chart-line', // Ikona 'dashicons-chart-line', // Ikona
6 // Pozycja 6 // Pozycja
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', // Slug rodzica 'statpress-dashboard', // Slug rodzica
'Dodaj Nowy Trening', // Tytuł strony 'Dodaj Nowy Trening', // Tytuł strony
'Nowy trening', // Tytuł w podmenu 'Nowy trening', // Tytuł w podmenu
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'mystat-nowy-trening', // Slug podmenu 'statpress-add-new', // Slug podmenu
'mystat_add_new_page' // Funkcja renderująca stronę dodawania 'statpress_add_new_page' // Funkcja renderująca stronę dodawania
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', 'statpress-dashboard',
'Typy Wydarzeń', 'Typy Wydarzeń',
'Typy wydarzeń', 'Typy wydarzeń',
'manage_options', 'manage_options',
'mystat-event-types', 'statpress-event-types',
'mystat_event_types_page' 'statpress_event_types_page'
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', 'statpress-dashboard',
'Sprzęt', 'Sprzęt',
'Sprzęt', 'Sprzęt',
'manage_options', 'manage_options',
'mystat-equipment', 'statpress-equipment',
'mystat_equipment_page' 'statpress_equipment_page'
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', 'statpress-dashboard',
'Cele', 'Cele',
'Cele', 'Cele',
'manage_options', 'manage_options',
'mystat-goals', 'statpress-goals',
'mystat_goals_page' 'statpress_goals_page'
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
null, 'Dziennik Serwisowy', 'Dziennik Serwisowy', 'manage_options', 'mystat-equipment-details', 'mystat_equipment_details_page' null, 'Dziennik Serwisowy', 'Dziennik Serwisowy', 'manage_options', 'statpress-equipment-details', 'statpress_equipment_details_page'
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
null, // Ukryta strona, nie pojawia się w menu null, // Ukryta strona, nie pojawia się w menu
'Szczegóły Treningu', // Tytuł strony 'Szczegóły Treningu', // Tytuł strony
'Szczegóły Treningu', // Tytuł w menu (nieistotny) 'Szczegóły Treningu', // Tytuł w menu (nieistotny)
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'mystat-view-activity', // Slug podmenu 'statpress-view-activity', // Slug podmenu
'mystat_view_activity_page' // Funkcja renderująca 'statpress_view_activity_page' // Funkcja renderująca
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
null, // Ukryta strona null, // Ukryta strona
'Edytuj Trening', // Tytuł strony 'Edytuj Trening', // Tytuł strony
'Edytuj Trening', // Tytuł w menu (nieistotny) 'Edytuj Trening', // Tytuł w menu (nieistotny)
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'mystat-edit-activity', // Slug podmenu 'statpress-edit-activity', // Slug podmenu
'mystat_edit_activity_page' // Funkcja renderująca 'statpress_edit_activity_page' // Funkcja renderująca
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', // Slug rodzica 'statpress-dashboard', // Slug rodzica
'Podsumowanie Roczne', // Tytuł strony 'Podsumowanie Roczne', // Tytuł strony
'Podsumowanie Roczne', // Tytuł w podmenu 'Podsumowanie Roczne', // Tytuł w podmenu
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'mystat-yearly-summary', // Slug podmenu 'statpress-yearly-summary', // Slug podmenu
'mystat_yearly_summary_page'// Funkcja renderująca 'statpress_yearly_summary_page'// Funkcja renderująca
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', // Slug rodzica 'statpress-dashboard', // Slug rodzica
'Infografika', // Tytuł strony 'Infografika', // Tytuł strony
'Infografika', // Tytuł w podmenu 'Infografika', // Tytuł w podmenu
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'mystat-infographic', // Slug podmenu 'statpress-infographic', // Slug podmenu
'mystat_infographic_page' // Funkcja renderująca 'statpress_infographic_page' // Funkcja renderująca
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', // Slug rodzica 'statpress-dashboard', // Slug rodzica
'Import CSV', // Tytuł strony 'Import CSV', // Tytuł strony
'Import CSV', // Tytuł w podmenu 'Import CSV', // Tytuł w podmenu
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'mystat-import-csv', // Slug podmenu 'statpress-import-csv', // Slug podmenu
'mystat_import_csv_page' // Funkcja renderująca 'statpress_import_csv_page' // Funkcja renderująca
); );
$mystat_plugin_hooks[] = add_submenu_page( $statpress_plugin_hooks[] = add_submenu_page(
'moje-statystyki', // Slug rodzica 'statpress-dashboard', // Slug rodzica
'Ustawienia', // Tytuł strony 'Ustawienia', // Tytuł strony
'Ustawienia', // Tytuł w podmenu 'Ustawienia', // Tytuł w podmenu
'manage_options', // Wymagane uprawnienia 'manage_options', // Wymagane uprawnienia
'mystat-settings', // Slug podmenu 'statpress-settings', // Slug podmenu
'mystat_settings_page' // Funkcja renderująca 'statpress_settings_page' // Funkcja renderująca
); );
} }
+303 -243
View File
@@ -1,244 +1,304 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_add_new_page() { function statpress_add_new_page() {
echo '<div class="wrap"><h1>Dodaj Nowy Trening</h1>'; echo '<div class="wrap"><h1>Dodaj Nowy Trening</h1>';
// Obsługa zapisu formularza (musi być przed renderowaniem, aby wyświetlić komunikat) // Obsługa zapisu formularza (musi być przed renderowaniem, aby wyświetlić komunikat)
statpress_handle_activity_form_submission(); statpress_handle_activity_form_submission();
// Formularz dodawania // Formularz dodawania
statpress_render_add_form(); statpress_render_add_form();
echo '</div>'; echo '</div>';
} }
function statpress_edit_activity_page() { function statpress_edit_activity_page() {
global $wpdb; global $wpdb;
$activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; $activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0;
if ( $activity_id === 0 ) { if ( $activity_id === 0 ) {
echo '<div class="wrap"><h1>Błąd</h1><p>Nie podano ID aktywności do edycji.</p></div>'; echo '<div class="wrap"><h1>Błąd</h1><p>Nie podano ID aktywności do edycji.</p></div>';
return; return;
} }
// Handle form submission for update // Handle form submission for update
statpress_handle_activity_form_submission(); statpress_handle_activity_form_submission();
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$activity = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_activities WHERE id = %d", $activity_id ) ); $activity = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_activities WHERE id = %d", $activity_id ) );
if ( ! $activity ) { if ( ! $activity ) {
echo '<div class="wrap"><h1>Błąd</h1><p>Nie znaleziono aktywności o podanym ID.</p></div>'; echo '<div class="wrap"><h1>Błąd</h1><p>Nie znaleziono aktywności o podanym ID.</p></div>';
return; return;
} }
echo '<div class="wrap"><h1>Edytuj Trening</h1>'; echo '<div class="wrap"><h1>Edytuj Trening</h1>';
statpress_render_add_form( $activity ); statpress_render_add_form( $activity );
echo '</div>'; echo '</div>';
} }
/** /**
* Obsługa zapisu nowego lub edytowanego wpisu do bazy danych * Obsługa zapisu nowego lub edytowanego wpisu do bazy danych
*/ */
function statpress_handle_activity_form_submission() { function statpress_handle_activity_form_submission() {
global $wpdb; global $wpdb;
// Sprawdź czy formularz został wysłany // Sprawdź czy formularz został wysłany
if ( ! isset( $_POST['statpress_submit_activity'] ) ) { if ( ! isset( $_POST['statpress_submit_activity'] ) ) {
return; return;
} }
$activity_id = isset( $_POST['activity_id'] ) ? intval( $_POST['activity_id'] ) : 0; $activity_id = isset( $_POST['activity_id'] ) ? intval( $_POST['activity_id'] ) : 0;
$nonce_action = $activity_id > 0 ? 'statpress_edit_entry_' . $activity_id : 'statpress_add_entry'; $nonce_action = $activity_id > 0 ? 'statpress_edit_entry_' . $activity_id : 'statpress_add_entry';
// Weryfikacja bezpieczeństwa (Nonce) // Weryfikacja bezpieczeństwa (Nonce)
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], $nonce_action ) ) { if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], $nonce_action ) ) {
echo '<div class="notice notice-error"><p>Błąd weryfikacji bezpieczeństwa formularza.</p></div>'; echo '<div class="notice notice-error"><p>Błąd weryfikacji bezpieczeństwa formularza.</p></div>';
return; return;
} }
// Use the refactored function to save data. // Use the refactored function to save data.
// We can pass $_POST directly as the function will sanitize it. // We can pass $_POST directly as the function will sanitize it.
$result = statpress_save_activity_data( $_POST, $activity_id ); $result = statpress_save_activity_data( $_POST, $activity_id );
if ( $activity_id > 0 ) { if ( $activity_id > 0 ) {
$message = 'Trening zaktualizowany pomyślnie!'; $message = 'Trening zaktualizowany pomyślnie!';
} else { } else {
$message = 'Trening dodany pomyślnie!'; $message = 'Trening dodany pomyślnie!';
} }
if ( $result ) { if ( $result ) {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $message ) . '</p></div>'; echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $message ) . '</p></div>';
} else { } else {
echo '<div class="notice notice-error is-dismissible"><p>Wystąpił błąd podczas zapisu do bazy.</p></div>'; echo '<div class="notice notice-error is-dismissible"><p>Wystąpił błąd podczas zapisu do bazy.</p></div>';
} }
} }
/** /**
* Renderowanie formularza HTML * Renderowanie formularza HTML
*/ */
function statpress_render_add_form( $activity = null ) { function statpress_render_add_form( $activity = null ) {
// Enqueue media scripts for the uploader // Enqueue media scripts for the uploader
wp_enqueue_media(); wp_enqueue_media();
global $wpdb; global $wpdb;
$table_categories = $wpdb->prefix . 'statpress_categories'; $table_categories = $wpdb->prefix . 'statpress_categories';
$table_event_types = $wpdb->prefix . 'statpress_event_types'; $table_event_types = $wpdb->prefix . 'statpress_event_types';
$table_equipment = $wpdb->prefix . 'statpress_equipment'; $table_equipment = $wpdb->prefix . 'statpress_equipment';
$categories = $wpdb->get_results( "SELECT * FROM $table_categories ORDER BY name ASC" ); $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" ); $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" ); $equipment_list = $wpdb->get_results( "SELECT * FROM $table_equipment ORDER BY name ASC" );
$is_edit_mode = ! is_null( $activity ); $is_edit_mode = ! is_null( $activity );
$nonce_action = $is_edit_mode ? 'statpress_edit_entry_' . $activity->id : 'statpress_add_entry'; $nonce_action = $is_edit_mode ? 'statpress_edit_entry_' . $activity->id : 'statpress_add_entry';
$form_title = $is_edit_mode ? 'Edytuj Aktywność' : 'Dodaj Nową Aktywność'; $form_title = $is_edit_mode ? 'Edytuj Aktywność' : 'Dodaj Nową Aktywność';
$button_text = $is_edit_mode ? 'Zaktualizuj Trening' : 'Zapisz Trening'; $button_text = $is_edit_mode ? 'Zaktualizuj Trening' : 'Zapisz Trening';
?> ?>
<div class="postbox"> <div class="postbox">
<div class="postbox-header"><h2 class="hndle"><?php echo esc_html( $form_title ); ?></h2></div> <div class="postbox-header"><h2 class="hndle"><?php echo esc_html( $form_title ); ?></h2></div>
<div class="inside"> <div class="inside">
<form method="post" action=""> <form method="post" action="">
<input type="hidden" name="activity_id" value="<?php echo $is_edit_mode ? esc_attr( $activity->id ) : '0'; ?>"> <input type="hidden" name="activity_id" value="<?php echo $is_edit_mode ? esc_attr( $activity->id ) : '0'; ?>">
<?php wp_nonce_field( $nonce_action, '_wpnonce' ); ?> <?php wp_nonce_field( $nonce_action, '_wpnonce' ); ?>
<table class="form-table"> <table class="form-table">
<tr> <tr>
<th scope="row"><label for="title">Tytuł</label></th> <th scope="row"><label for="title">Tytuł</label></th>
<td><input type="text" name="title" id="title" class="large-text" placeholder="np. Poranny trening w lesie" value="<?php echo $is_edit_mode ? esc_attr( $activity->title ) : ''; ?>" required></td> <td><input type="text" name="title" id="title" class="large-text" placeholder="np. Poranny trening w lesie" value="<?php echo $is_edit_mode ? esc_attr( $activity->title ) : ''; ?>" required></td>
</tr> </tr>
<tr> <tr>
<th scope="row"><label for="category_id">Kategoria</label></th> <th scope="row"><label for="gpx_url">Link do pliku GPX</label></th>
<td> <td>
<select name="category_id" id="category_id" required> <input type="text" name="gpx_url" id="gpx_url" class="large-text" placeholder="Wklej URL lub wgraj plik..." value="<?php echo $is_edit_mode ? esc_attr( $activity->gpx_url ) : ''; ?>" style="width: calc(100% - 150px); margin-right: 10px;">
<?php foreach ( $categories as $cat ) : ?> <span id="gpx_parse_status"></span>
<option value="<?php echo esc_attr( $cat->id ); ?>" <?php echo $is_edit_mode ? selected( $activity->category_id, $cat->id, false ) : selected( $cat->name, 'Rower', false ); ?>><?php echo esc_html( $cat->name ); ?></option> <button type="button" class="button" id="upload_gpx_button" style="margin-top: 5px;">Wgraj lub wybierz plik</button>
<?php endforeach; ?> <p class="description">Wklej link lub wgraj plik GPX, aby automatycznie uzupełnić pola poniżej.</p>
</select> </td>
</td> </tr>
</tr> <tr>
<tr> <td colspan="2" style="padding: 15px 0 5px;"><hr></td>
<th scope="row"><label for="date">Data</label></th> </tr>
<td><input type="date" name="date" id="date" value="<?php echo $is_edit_mode ? esc_attr( $activity->date ) : current_time( 'Y-m-d' ); ?>" class="regular-text" required></td> <tr>
</tr> <th scope="row"><label for="category_id">Kategoria</label></th>
<tr> <td>
<th scope="row"><label for="distance">Dystans (km)</label></th> <select name="category_id" id="category_id" required>
<td><input type="text" name="distance" id="distance" class="regular-text" placeholder="0,00" value="<?php echo $is_edit_mode ? esc_attr( number_format( (float) $activity->distance, 2, ',', '' ) ) : ''; ?>"></td> <?php foreach ( $categories as $cat ) : ?>
</tr> <option value="<?php echo esc_attr( $cat->id ); ?>" <?php echo $is_edit_mode ? selected( $activity->category_id, $cat->id, false ) : selected( $cat->name, 'Rower', false ); ?>><?php echo esc_html( $cat->name ); ?></option>
<tr> <?php endforeach; ?>
<th scope="row"><label for="duration">Czas (HH:MM:SS)</label></th> </select>
<td><input type="time" name="duration" id="duration" step="1" value="<?php echo $is_edit_mode ? esc_attr( $activity->duration ) : '00:00:00'; ?>" class="regular-text"></td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row"><label for="calories">Kalorie (kcal)</label></th> <th scope="row"><label for="date">Data</label></th>
<td><input type="number" name="calories" id="calories" class="regular-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->calories ) : ''; ?>"></td> <td><input type="date" name="date" id="date" value="<?php echo $is_edit_mode ? esc_attr( $activity->date ) : current_time( 'Y-m-d' ); ?>" class="regular-text" required></td>
</tr> </tr>
<tr> <tr>
<th scope="row"><label for="event_type_id">Typ wydarzenia</label></th> <th scope="row"><label for="distance">Dystans (km)</label></th>
<td> <td><input type="text" name="distance" id="distance" class="regular-text" placeholder="0,00" value="<?php echo $is_edit_mode ? esc_attr( number_format( (float) $activity->distance, 2, ',', '' ) ) : ''; ?>"></td>
<select name="event_type_id" id="event_type_id"> </tr>
<option value="">-- Wybierz --</option> <tr>
<?php foreach ( $event_types as $type ) : ?> <th scope="row"><label for="duration">Czas (HH:MM:SS)</label></th>
<option value="<?php echo esc_attr( $type->id ); ?>" <?php echo $is_edit_mode ? selected( $activity->event_type_id, $type->id, false ) : selected( $type->name, 'Trening', false ); ?>><?php echo esc_html( $type->name ); ?></option> <td><input type="time" name="duration" id="duration" step="1" value="<?php echo $is_edit_mode ? esc_attr( $activity->duration ) : '00:00:00'; ?>" class="regular-text"></td>
<?php endforeach; ?> </tr>
</select> <tr>
</td> <th scope="row"><label for="calories">Kalorie (kcal)</label></th>
</tr> <td><input type="number" name="calories" id="calories" class="regular-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->calories ) : ''; ?>"></td>
<tr> </tr>
<th scope="row"><label for="equipment_id">Sprzęt</label></th> <tr>
<td> <th scope="row"><label for="event_type_id">Typ wydarzenia</label></th>
<select name="equipment_id" id="equipment_id"> <td>
<option value="">-- Wybierz --</option> <select name="event_type_id" id="event_type_id">
<?php foreach ( $equipment_list as $item ) : ?> <option value="">-- Wybierz --</option>
<option value="<?php echo esc_attr( $item->id ); ?>" <?php if ( $is_edit_mode ) { <?php foreach ( $event_types as $type ) : ?>
selected( $activity->equipment_id, $item->id );} ?>><?php echo esc_html( $item->name ); ?></option> <option value="<?php echo esc_attr( $type->id ); ?>" <?php echo $is_edit_mode ? selected( $activity->event_type_id, $type->id, false ) : selected( $type->name, 'Trening', false ); ?>><?php echo esc_html( $type->name ); ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</td> </td>
</tr> </tr>
<tr class="form-field"> <tr>
<td colspan="2"><hr><h4>Dane szczegółowe (opcjonalne)</h4></td> <th scope="row"><label for="equipment_id">Sprzęt</label></th>
</tr> <td>
<tr> <select name="equipment_id" id="equipment_id">
<th scope="row"><label for="avg_speed">Śr. prędkość (km/h)</label></th> <option value="">-- Wybierz --</option>
<td><input type="text" name="avg_speed" id="avg_speed" class="small-text" placeholder="0,0" value="<?php echo $is_edit_mode ? esc_attr( number_format( (float) $activity->avg_speed, 2, ',', '' ) ) : ''; ?>"></td> <?php foreach ( $equipment_list as $item ) : ?>
</tr> <option value="<?php echo esc_attr( $item->id ); ?>" <?php if ( $is_edit_mode ) {
<tr> selected( $activity->equipment_id, $item->id );} ?>><?php echo esc_html( $item->name ); ?></option>
<th scope="row"><label for="max_speed">Maks. prędkość (km/h)</label></th> <?php endforeach; ?>
<td><input type="text" name="max_speed" id="max_speed" class="small-text" placeholder="0,0" value="<?php echo $is_edit_mode ? esc_attr( number_format( (float) $activity->max_speed, 2, ',', '' ) ) : ''; ?>"></td> </select>
</tr> </td>
<tr> </tr>
<th scope="row"><label for="avg_heart_rate">Śr. tętno</label></th> <tr>
<td><input type="number" name="avg_heart_rate" id="avg_heart_rate" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->avg_heart_rate ) : ''; ?>"></td> <td colspan="2" style="padding: 20px 0;">
</tr> <hr>
<tr> <h3 style="font-size: 1.2em; margin: 1em 0;">Dane szczegółowe (opcjonalne)</h3>
<th scope="row"><label for="max_heart_rate">Maks. tętno</label></th> <div class="statpress-form-grid">
<td><input type="number" name="max_heart_rate" id="max_heart_rate" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->max_heart_rate ) : ''; ?>"></td> <div class="statpress-form-group">
</tr> <h3><span class="dashicons dashicons-dashboard" style="vertical-align: middle;"></span> Prędkość</h3>
<tr> <p><label for="avg_speed">Średnia (km/h)</label><input type="text" name="avg_speed" id="avg_speed" placeholder="0,0" value="<?php echo $is_edit_mode ? esc_attr( number_format( (float) $activity->avg_speed, 2, ',', '' ) ) : ''; ?>"></p>
<th scope="row"><label for="avg_cadence">Śr. rytm pedałowania</label></th> <p><label for="max_speed">Maksymalna (km/h)</label><input type="text" name="max_speed" id="max_speed" placeholder="0,0" value="<?php echo $is_edit_mode ? esc_attr( number_format( (float) $activity->max_speed, 2, ',', '' ) ) : ''; ?>"></p>
<td><input type="number" name="avg_cadence" id="avg_cadence" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->avg_cadence ) : ''; ?>"></td> </div>
</tr> <div class="statpress-form-group">
<tr> <h3><span class="dashicons dashicons-heart" style="vertical-align: middle;"></span> Tętno</h3>
<th scope="row"><label for="max_cadence">Maks. rytm pedałowania</label></th> <p><label for="avg_heart_rate">Średnie (bpm)</label><input type="number" name="avg_heart_rate" id="avg_heart_rate" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->avg_heart_rate ) : ''; ?>"></p>
<td><input type="number" name="max_cadence" id="max_cadence" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->max_cadence ) : ''; ?>"></td> <p><label for="max_heart_rate">Maksymalne (bpm)</label><input type="number" name="max_heart_rate" id="max_heart_rate" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->max_heart_rate ) : ''; ?>"></p>
</tr> </div>
<tr> <div class="statpress-form-group">
<th scope="row"><label for="total_elevation_gain">Całkowity wznios (m)</label></th> <h3><span class="dashicons dashicons-update" style="vertical-align: middle;"></span> Rytm</h3>
<td><input type="number" name="total_elevation_gain" id="total_elevation_gain" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->total_elevation_gain ) : ''; ?>"></td> <p><label for="avg_cadence">Średni (rpm)</label><input type="number" name="avg_cadence" id="avg_cadence" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->avg_cadence ) : ''; ?>"></p>
</tr> <p><label for="max_cadence">Maksymalny (rpm)</label><input type="number" name="max_cadence" id="max_cadence" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->max_cadence ) : ''; ?>"></p>
<tr> </div>
<th scope="row"><label for="total_elevation_loss">Całkowity spadek (m)</label></th> <div class="statpress-form-group">
<td><input type="number" name="total_elevation_loss" id="total_elevation_loss" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->total_elevation_loss ) : ''; ?>"></td> <h3><span class="dashicons dashicons-chart-area" style="vertical-align: middle;"></span> Wysokość</h3>
</tr> <p><label for="total_elevation_gain">Suma wzniosów (m)</label><input type="number" name="total_elevation_gain" id="total_elevation_gain" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->total_elevation_gain ) : ''; ?>"></p>
<tr> <p><label for="total_elevation_loss">Suma spadków (m)</label><input type="number" name="total_elevation_loss" id="total_elevation_loss" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->total_elevation_loss ) : ''; ?>"></p>
<th scope="row"><label for="min_altitude">Min. wysokość (m)</labe></th> <p><label for="min_altitude">Min. wysokość (m n.p.m.)</label><input type="number" name="min_altitude" id="min_altitude" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->min_altitude ) : ''; ?>"></p>
<td><input type="number" name="min_altitude" id="min_altitude" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->min_altitude ) : ''; ?>"></td> <p><label for="max_altitude">Maks. wysokość (m n.p.m.)</label><input type="number" name="max_altitude" id="max_altitude" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->max_altitude ) : ''; ?>"></p>
</tr> </div>
<tr> </div>
<th scope="row"><label for="max_altitude">Maks. wysokość (m)</label></th> <hr style="margin-top: 2em;">
<td><input type="number" name="max_altitude" id="max_altitude" class="small-text" placeholder="0" value="<?php echo $is_edit_mode ? esc_attr( $activity->max_altitude ) : ''; ?>"></td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row"><label for="comment">Komentarz</label></th> <th scope="row"><label for="comment">Komentarz</label></th>
<td><textarea name="comment" id="comment" rows="3" class="large-text"><?php echo $is_edit_mode ? esc_textarea( $activity->comment ) : ''; ?></textarea></td> <td><textarea name="comment" id="comment" rows="3" class="large-text"><?php echo $is_edit_mode ? esc_textarea( $activity->comment ) : ''; ?></textarea></td>
</tr> </tr>
<tr class="form-field"> <tr>
<td colspan="2"><hr><h4>Linki zewnętrzne (opcjonalne)</h4></td> <th scope="row"><label for="strava_url">Link do Strava</label></th>
</tr> <td><input type="url" name="strava_url" id="strava_url" class="large-text" placeholder="https://www.strava.com/activities/..." value="<?php echo $is_edit_mode ? esc_attr( $activity->strava_url ) : ''; ?>"></td>
<tr> </tr>
<th scope="row"><label for="strava_url">Link do Strava</label></th> </table>
<td><input type="url" name="strava_url" id="strava_url" class="large-text" placeholder="https://www.strava.com/activities/..." value="<?php echo $is_edit_mode ? esc_attr( $activity->strava_url ) : ''; ?>"></td> <p class="submit">
</tr> <input type="submit" name="statpress_submit_activity" id="submit" class="button button-primary" value="<?php echo esc_attr( $button_text ); ?>">
<tr> </p>
<th scope="row"><label for="gpx_url">Link do pliku GPX</label></th> </form>
<td> <script>
<input type="text" name="gpx_url" id="gpx_url" class="large-text" placeholder="Wklej URL lub wgraj plik..." value="<?php echo $is_edit_mode ? esc_attr( $activity->gpx_url ) : ''; ?>"> jQuery(document).ready(function($) {
<button type="button" class="button" id="upload_gpx_button" style="margin-top: 5px;">Wgraj lub wybierz plik</button> $('#upload_gpx_button').click(function(e) {
</td> e.preventDefault();
</tr> var gpx_uploader = wp.media({
</table> title: 'Wybierz plik GPX',
<p class="submit"> button: { text: 'Użyj tego pliku' },
<input type="submit" name="statpress_submit_activity" id="submit" class="button button-primary" value="<?php echo esc_attr( $button_text ); ?>"> multiple: false,
</p> library: { type: ['application/gpx+xml', 'application/xml', 'text/plain'] }
</form> })
<script> .on('select', function() {
jQuery(document).ready(function($) { var attachment = gpx_uploader.state().get('selection').first().toJSON();
$('#upload_gpx_button').click(function(e) { $('#gpx_url').val(attachment.url).trigger('change');
e.preventDefault(); }).open();
var gpx_uploader = wp.media({ });
title: 'Wybierz plik GPX',
button: { text: 'Użyj tego pliku' }, // --- GPX Auto-fill Feature ---
multiple: false, const gpxUrlInput = $('#gpx_url');
library: { type: ['application/gpx+xml', 'application/xml', 'text/plain'] } const statusEl = $('#gpx_parse_status');
}) let debounceTimer;
.on('select', function() {
var attachment = gpx_uploader.state().get('selection').first().toJSON(); gpxUrlInput.on('input change', function() {
$('#gpx_url').val(attachment.url).trigger('change'); clearTimeout(debounceTimer);
}).open(); debounceTimer = setTimeout(function() {
}); const url = gpxUrlInput.val();
}); if (url && url.toLowerCase().endsWith('.gpx')) {
</script> parseGpx(url);
</div> }
</div> }, 500); // 500ms delay to avoid firing on every keystroke
<?php });
function parseGpx(url) {
statusEl.html('<span class="spinner is-active" style="float:none; vertical-align: middle;"></span> Analizuję plik...');
wp.apiFetch({
path: '/statpress/v1/gpx/parse-summary',
method: 'POST',
data: { gpx_url: url }
})
.then(function(data) {
statusEl.html('<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> Dane wczytane!');
const fieldsToCheck = [
'#distance', '#duration', '#calories', '#avg_speed', '#max_speed',
'#avg_heart_rate', '#max_heart_rate', '#avg_cadence', '#max_cadence',
'#total_elevation_gain', '#total_elevation_loss', '#min_altitude', '#max_altitude'
];
let hasExistingData = false;
fieldsToCheck.forEach(function(selector) {
const field = $(selector);
if (field.val() && field.val() !== '00:00:00' && field.val() !== '0' && field.val() !== '0,00') {
hasExistingData = true;
}
});
let proceed = true;
if (hasExistingData) {
proceed = confirm('Niektóre pola formularza zawierają już dane. Czy na pewno chcesz je nadpisać danymi z pliku GPX?');
}
if (proceed) {
const formatNum = (num) => String(num).replace('.', ',');
if (data.distance) $('#distance').val(formatNum(data.distance));
if (data.duration) $('#duration').val(data.duration);
if (data.avg_speed) $('#avg_speed').val(formatNum(data.avg_speed));
if (data.max_speed) $('#max_speed').val(formatNum(data.max_speed));
if (data.avg_heart_rate) $('#avg_heart_rate').val(data.avg_heart_rate);
if (data.max_heart_rate) $('#max_heart_rate').val(data.max_heart_rate);
if (data.avg_cadence) $('#avg_cadence').val(data.avg_cadence);
if (data.max_cadence) $('#max_cadence').val(data.max_cadence);
if (data.total_elevation_gain) $('#total_elevation_gain').val(data.total_elevation_gain);
if (data.total_elevation_loss) $('#total_elevation_loss').val(data.total_elevation_loss);
if (data.min_altitude) $('#min_altitude').val(data.min_altitude);
if (data.max_altitude) $('#max_altitude').val(data.max_altitude);
}
setTimeout(() => statusEl.html(''), 4000);
})
.catch(function(error) {
const errorMsg = error.message || 'Nieznany błąd.';
statusEl.html('<span class="dashicons dashicons-warning" style="color: #d63638;"></span> Błąd: ' + errorMsg);
});
}
});
</script>
</div>
</div>
<?php
} }
+262 -262
View File
@@ -1,263 +1,263 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_view_activity_page() { function statpress_view_activity_page() {
global $wpdb; global $wpdb;
$activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; $activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0;
if ( $activity_id === 0 ) { if ( $activity_id === 0 ) {
echo '<div class="wrap"><h1>Błąd</h1><p>Nie podano ID aktywności.</p></div>'; echo '<div class="wrap"><h1>Błąd</h1><p>Nie podano ID aktywności.</p></div>';
return; return;
} }
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$table_categories = $wpdb->prefix . 'statpress_categories'; $table_categories = $wpdb->prefix . 'statpress_categories';
$table_event_types = $wpdb->prefix . 'statpress_event_types'; $table_event_types = $wpdb->prefix . 'statpress_event_types';
$table_equipment = $wpdb->prefix . 'statpress_equipment'; $table_equipment = $wpdb->prefix . 'statpress_equipment';
$sql = $wpdb->prepare( $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 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 FROM $table_activities a
LEFT JOIN $table_categories c ON a.category_id = c.id 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_event_types et ON a.event_type_id = et.id
LEFT JOIN $table_equipment eq ON a.equipment_id = eq.id LEFT JOIN $table_equipment eq ON a.equipment_id = eq.id
WHERE a.id = %d WHERE a.id = %d
", ",
$activity_id $activity_id
); );
$activity = $wpdb->get_row( $sql ); $activity = $wpdb->get_row( $sql );
if ( ! $activity ) { if ( ! $activity ) {
echo '<div class="wrap"><h1>Błąd</h1><p>Nie znaleziono aktywności o podanym ID.</p></div>'; echo '<div class="wrap"><h1>Błąd</h1><p>Nie znaleziono aktywności o podanym ID.</p></div>';
return; return;
} }
// Funkcja pomocnicza do wyświetlania wiersza, jeśli wartość istnieje // Funkcja pomocnicza do wyświetlania wiersza, jeśli wartość istnieje
$render_row = function( $label, $value, $unit = '' ) { $render_row = function( $label, $value, $unit = '' ) {
if ( ! is_null( $value ) && '' !== $value ) { if ( ! is_null( $value ) && '' !== $value ) {
echo '<tr>'; echo '<tr>';
echo '<th scope="row">' . esc_html( $label ) . '</th>'; echo '<th scope="row">' . esc_html( $label ) . '</th>';
echo '<td>' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . '</td>'; echo '<td>' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . '</td>';
echo '</tr>'; echo '</tr>';
} }
}; };
// Prepare map and chart data if GPX exists // Prepare map and chart data if GPX exists
$gpx_data = array(); $gpx_data = array();
$has_gpx_data = false; $has_gpx_data = false;
if ( ! empty( $activity->gpx_url ) ) { if ( ! empty( $activity->gpx_url ) ) {
$gpx_data = statpress_parse_gpx_data( $activity->gpx_url ); $gpx_data = statpress_parse_gpx_data( $activity->gpx_url );
$has_gpx_data = ! empty( $gpx_data['points'] ); $has_gpx_data = ! empty( $gpx_data['points'] );
} }
if ( $has_gpx_data ) { if ( $has_gpx_data ) {
wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' ); 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( '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 ); wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
wp_register_script( 'statpress-details-loader', false ); wp_register_script( 'statpress-details-loader', false );
wp_enqueue_script( 'statpress-details-loader' ); wp_enqueue_script( 'statpress-details-loader' );
// Check which profiles have data // Check which profiles have data
$available_profiles = array(); $available_profiles = array();
if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
$available_profiles['elevation'] = 'Wysokość';} $available_profiles['elevation'] = 'Wysokość';}
if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
$available_profiles['speed'] = 'Prędkość';} $available_profiles['speed'] = 'Prędkość';}
if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
$available_profiles['hr'] = 'Tętno';} $available_profiles['hr'] = 'Tętno';}
if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
$available_profiles['cadence'] = 'Kadencja';} $available_profiles['cadence'] = 'Kadencja';}
$has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) ); $has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
$chart_js = ' $chart_js = '
const track_points = ' . json_encode( $gpx_data['points'] ) . '; const track_points = ' . json_encode( $gpx_data['points'] ) . ';
const profiles = ' . json_encode( $gpx_data['profiles'] ) . '; const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
let activeChart = null; let activeChart = null;
if (typeof L !== "undefined" && track_points.length > 0) { if (typeof L !== "undefined" && track_points.length > 0) {
const map = L.map("statpress-activity-map"); const map = L.map("statpress-activity-map");
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>\' }).addTo(map); 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(track_points, {color: "' . esc_js( $activity->color ) . '"}).addTo(map); const polyline = L.polyline(track_points, {color: "' . esc_js( $activity->color ) . '"}).addTo(map);
map.fitBounds(polyline.getBounds().pad(0.1)); map.fitBounds(polyline.getBounds().pad(0.1));
L.marker(track_points[0]).addTo(map).bindPopup("Start"); L.marker(track_points[0]).addTo(map).bindPopup("Start");
L.marker(track_points[track_points.length - 1]).addTo(map).bindPopup("Koniec"); L.marker(track_points[track_points.length - 1]).addTo(map).bindPopup("Koniec");
} }
const chartConfigs = { const chartConfigs = {
elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" }, elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" }, speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
hr: { label: "Tętno", unit: "bpm", color: "#c0392b" }, hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" } cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
}; };
const xAxisConfigs = { const xAxisConfigs = {
distance: { label: "Dystans (km)", data: profiles.distance }, distance: { label: "Dystans (km)", data: profiles.distance },
time: { time: {
label: "Czas", data: profiles.time, label: "Czas", data: profiles.time,
formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8) formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8)
} }
}; };
function renderChart() { function renderChart() {
if (activeChart) activeChart.destroy(); if (activeChart) activeChart.destroy();
const chartType = document.querySelector(".statpress-chart-tabs .nav-tab-active").getAttribute("href").substring(1); const chartType = document.querySelector(".statpress-chart-tabs .nav-tab-active").getAttribute("href").substring(1);
const xAxisType = document.querySelector(\'input[name="statpress_xaxis"]:checked\').value; const xAxisType = document.querySelector(\'input[name="statpress_xaxis"]:checked\').value;
const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data; const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
const filteredY = [], filteredX = []; const filteredY = [], filteredX = [];
if(yData) { if(yData) {
for(let i=0; i<yData.length; i++) { for(let i=0; i<yData.length; i++) {
if (yData[i] !== null) { if (yData[i] !== null) {
filteredY.push(yData[i]); filteredY.push(yData[i]);
filteredX.push(xData[i]); filteredX.push(xData[i]);
} }
} }
} }
const ctx = document.getElementById("statpress-details-chart").getContext("2d"); const ctx = document.getElementById("statpress-details-chart").getContext("2d");
activeChart = new Chart(ctx, { activeChart = new Chart(ctx, {
type: "line", type: "line",
data: { data: {
labels: filteredX, labels: filteredX,
datasets: [{ datasets: [{
label: chartConfigs[chartType].label, data: filteredY, label: chartConfigs[chartType].label, data: filteredY,
borderColor: chartConfigs[chartType].color, backgroundColor: chartConfigs[chartType].color + "20", borderColor: chartConfigs[chartType].color, backgroundColor: chartConfigs[chartType].color + "20",
fill: true, pointRadius: 0, tension: 0.1 fill: true, pointRadius: 0, tension: 0.1
}] }]
}, },
options: { options: {
responsive: true, maintainAspectRatio: false, responsive: true, maintainAspectRatio: false,
scales: { scales: {
x: { title: { display: true, text: xAxisConfigs[xAxisType].label }, ticks: { callback: xAxisType === "time" ? xAxisConfigs.time.formatter : (v) => v, maxRotation: 0, autoSkip: true, maxTicksLimit: 10 } }, x: { title: { display: true, text: xAxisConfigs[xAxisType].label }, ticks: { callback: xAxisType === "time" ? xAxisConfigs.time.formatter : (v) => v, maxRotation: 0, autoSkip: true, maxTicksLimit: 10 } },
y: { title: { display: true, text: chartConfigs[chartType].unit } } y: { title: { display: true, text: chartConfigs[chartType].unit } }
}, },
plugins: { legend: { display: false } }, plugins: { legend: { display: false } },
interaction: { intersect: false, mode: "index" }, interaction: { intersect: false, mode: "index" },
} }
}); });
} }
document.querySelectorAll(".statpress-chart-tabs .nav-tab").forEach(t => t.addEventListener("click", e => { document.querySelectorAll(".statpress-chart-tabs .nav-tab").forEach(t => t.addEventListener("click", e => {
e.preventDefault(); e.preventDefault();
document.querySelector(".statpress-chart-tabs .nav-tab-active").classList.remove("nav-tab-active"); document.querySelector(".statpress-chart-tabs .nav-tab-active").classList.remove("nav-tab-active");
e.target.classList.add("nav-tab-active"); e.target.classList.add("nav-tab-active");
renderChart(); renderChart();
})); }));
document.querySelectorAll(\'input[name="statpress_xaxis"]\').forEach(r => r.addEventListener("change", renderChart)); document.querySelectorAll(\'input[name="statpress_xaxis"]\').forEach(r => r.addEventListener("change", renderChart));
if (document.querySelector(".statpress-chart-tabs .nav-tab")) renderChart(); if (document.querySelector(".statpress-chart-tabs .nav-tab")) renderChart();
'; ';
wp_add_inline_script( 'statpress-details-loader', 'document.addEventListener("DOMContentLoaded", function() {' . $chart_js . '});' ); wp_add_inline_script( 'statpress-details-loader', 'document.addEventListener("DOMContentLoaded", function() {' . $chart_js . '});' );
} }
?> ?>
<div class="wrap"> <div class="wrap">
<h1> <h1>
Szczegóły treningu: <?php echo esc_html( $activity->title ); ?> Szczegóły treningu: <?php echo esc_html( $activity->title ); ?>
<a href="<?php echo esc_url( add_query_arg( array( 'page' => 'statpress-edit-activity', 'id' => $activity->id ), admin_url( 'admin.php' ) ) ); ?>" class="page-title-action"> <a href="<?php echo esc_url( add_query_arg( array( 'page' => 'statpress-edit-activity', 'id' => $activity->id ), admin_url( 'admin.php' ) ) ); ?>" class="page-title-action">
Edytuj Edytuj
</a> </a>
</h1> </h1>
<p><a href="<?php echo esc_url( admin_url( 'admin.php?page=statpress-dashboard' ) ); ?>">&larr; Powrót do listy aktywności</a></p> <p><a href="<?php echo esc_url( admin_url( 'admin.php?page=statpress-dashboard' ) ); ?>">&larr; Powrót do listy aktywności</a></p>
<div class="postbox" style="margin-top: 20px;"> <div class="postbox" style="margin-top: 20px;">
<div class="postbox-header"><h2 class="hndle">Podsumowanie</h2></div> <div class="postbox-header"><h2 class="hndle">Podsumowanie</h2></div>
<div class="inside"> <div class="inside">
<div id="statpress-details-container"> <div id="statpress-details-container">
<div class="statpress-details-col"> <div class="statpress-details-col">
<h3>Główne dane</h3> <h3>Główne dane</h3>
<table class="form-table"> <table class="form-table">
<?php $render_row( 'Kategoria', $activity->category_name ); ?> <?php $render_row( 'Kategoria', $activity->category_name ); ?>
<?php $render_row( 'Data', $activity->date ); ?> <?php $render_row( 'Data', $activity->date ); ?>
<?php $render_row( 'Dystans', number_format( $activity->distance, 2, ',', ' ' ), 'km' ); ?> <?php $render_row( 'Dystans', number_format( $activity->distance, 2, ',', ' ' ), 'km' ); ?>
<?php $render_row( 'Czas trwania', $activity->duration ); ?> <?php $render_row( 'Czas trwania', $activity->duration ); ?>
<?php $render_row( 'Spalone kalorie', $activity->calories, 'kcal' ); ?> <?php $render_row( 'Spalone kalorie', $activity->calories, 'kcal' ); ?>
<?php $render_row( 'Typ wydarzenia', $activity->event_type_name ); ?> <?php $render_row( 'Typ wydarzenia', $activity->event_type_name ); ?>
<?php $render_row( 'Sprzęt', $activity->equipment_name ); ?> <?php $render_row( 'Sprzęt', $activity->equipment_name ); ?>
</table> </table>
</div> </div>
<div class="statpress-details-col"> <div class="statpress-details-col">
<h3>Dane szczegółowe</h3> <h3>Dane szczegółowe</h3>
<table class="form-table"> <table class="form-table">
<?php $render_row( 'Średnia prędkość', number_format( $activity->avg_speed, 1, ',', ' ' ), 'km/h' ); ?> <?php $render_row( 'Średnia prędkość', number_format( $activity->avg_speed, 1, ',', ' ' ), 'km/h' ); ?>
<?php $render_row( 'Maksymalna prędkość', number_format( $activity->max_speed, 1, ',', ' ' ), 'km/h' ); ?> <?php $render_row( 'Maksymalna prędkość', number_format( $activity->max_speed, 1, ',', ' ' ), 'km/h' ); ?>
<?php $render_row( 'Średnie tętno', $activity->avg_heart_rate, 'bpm' ); ?> <?php $render_row( 'Średnie tętno', $activity->avg_heart_rate, 'bpm' ); ?>
<?php $render_row( 'Maksymalne tętno', $activity->max_heart_rate, 'bpm' ); ?> <?php $render_row( 'Maksymalne tętno', $activity->max_heart_rate, 'bpm' ); ?>
<?php $render_row( 'Średni rytm', $activity->avg_cadence, 'rpm' ); ?> <?php $render_row( 'Średni rytm', $activity->avg_cadence, 'rpm' ); ?>
<?php $render_row( 'Maksymalny rytm', $activity->max_cadence, 'rpm' ); ?> <?php $render_row( 'Maksymalny rytm', $activity->max_cadence, 'rpm' ); ?>
<?php $render_row( 'Suma wzniosów', $activity->total_elevation_gain, 'm' ); ?> <?php $render_row( 'Suma wzniosów', $activity->total_elevation_gain, 'm' ); ?>
<?php $render_row( 'Suma spadków', $activity->total_elevation_loss, 'm' ); ?> <?php $render_row( 'Suma spadków', $activity->total_elevation_loss, 'm' ); ?>
<?php $render_row( 'Minimalna wysokość', $activity->min_altitude, 'm n.p.m.' ); ?> <?php $render_row( 'Minimalna wysokość', $activity->min_altitude, 'm n.p.m.' ); ?>
<?php $render_row( 'Maksymalna wysokość', $activity->max_altitude, 'm n.p.m.' ); ?> <?php $render_row( 'Maksymalna wysokość', $activity->max_altitude, 'm n.p.m.' ); ?>
</table> </table>
</div> </div>
</div> </div>
<hr> <hr>
<h3>Notatki i linki</h3> <h3>Notatki i linki</h3>
<table class="form-table"> <table class="form-table">
<?php $render_row( 'Komentarz', nl2br( $activity->comment ) ); ?> <?php $render_row( 'Komentarz', nl2br( $activity->comment ) ); ?>
<?php if ( ! empty( $activity->strava_url ) ) : ?> <?php if ( ! empty( $activity->strava_url ) ) : ?>
<tr><th scope="row">Strava</th><td><a href="<?php echo esc_url( $activity->strava_url ); ?>" target="_blank" rel="noopener noreferrer">Zobacz aktywność na Strava</a></td></tr> <tr><th scope="row">Strava</th><td><a href="<?php echo esc_url( $activity->strava_url ); ?>" target="_blank" rel="noopener noreferrer">Zobacz aktywność na Strava</a></td></tr>
<?php endif; ?> <?php endif; ?>
<?php if ( ! empty( $activity->gpx_url ) ) : ?> <?php if ( ! empty( $activity->gpx_url ) ) : ?>
<tr><th scope="row">Plik GPX</th><td><a href="<?php echo esc_url( $activity->gpx_url ); ?>" target="_blank">Pobierz plik GPX</a></td></tr> <tr><th scope="row">Plik GPX</th><td><a href="<?php echo esc_url( $activity->gpx_url ); ?>" target="_blank">Pobierz plik GPX</a></td></tr>
<?php endif; ?> <?php endif; ?>
</table> </table>
<?php if ( $has_gpx_data ) : ?> <?php if ( $has_gpx_data ) : ?>
<hr> <hr>
<h3>Mapa Trasy</h3> <h3>Mapa Trasy</h3>
<div id="statpress-activity-map" style="height: 450px; width: 100%; border: 1px solid #ddd; margin-bottom: 20px;"></div> <div id="statpress-activity-map" style="height: 450px; width: 100%; border: 1px solid #ddd; margin-bottom: 20px;"></div>
<?php if ( ! empty( $available_profiles ) ) : ?> <?php if ( ! empty( $available_profiles ) ) : ?>
<h3>Wykresy</h3> <h3>Wykresy</h3>
<div class="statpress-charts-container"> <div class="statpress-charts-container">
<div class="statpress-chart-controls"> <div class="statpress-chart-controls">
<nav class="nav-tab-wrapper statpress-chart-tabs"> <nav class="nav-tab-wrapper statpress-chart-tabs">
<?php <?php
$is_first = true; $is_first = true;
foreach ( $available_profiles as $key => $label ) : foreach ( $available_profiles as $key => $label ) :
?> ?>
<a href="#<?php echo esc_attr( $key ); ?>" class="nav-tab <?php <a href="#<?php echo esc_attr( $key ); ?>" class="nav-tab <?php
if ( $is_first ) { if ( $is_first ) {
echo 'nav-tab-active'; echo 'nav-tab-active';
$is_first = false; } $is_first = false; }
?> ?>
"><?php echo esc_html( $label ); ?></a> "><?php echo esc_html( $label ); ?></a>
<?php endforeach; ?> <?php endforeach; ?>
</nav> </nav>
<?php if ( $has_time_data ) : ?> <?php if ( $has_time_data ) : ?>
<div class="statpress-xaxis-switcher"> <div class="statpress-xaxis-switcher">
<strong>Oś X:</strong>&nbsp; <strong>Oś X:</strong>&nbsp;
<label><input type="radio" name="statpress_xaxis" value="distance" checked> Dystans</label> <label><input type="radio" name="statpress_xaxis" value="distance" checked> Dystans</label>
&nbsp; &nbsp;
<label><input type="radio" name="statpress_xaxis" value="time"> Czas</label> <label><input type="radio" name="statpress_xaxis" value="time"> Czas</label>
</div> </div>
<?php else : ?> <?php else : ?>
<input type="hidden" name="statpress_xaxis" value="distance" checked> <input type="hidden" name="statpress_xaxis" value="distance" checked>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div style="position: relative; height:250px; width:100%;"> <div style="position: relative; height:250px; width:100%;">
<canvas id="statpress-details-chart"></canvas> <canvas id="statpress-details-chart"></canvas>
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php elseif ( ! empty( $activity->gpx_url ) ) : ?> <?php elseif ( ! empty( $activity->gpx_url ) ) : ?>
<hr> <hr>
<div class="notice notice-warning inline"> <div class="notice notice-warning inline">
<p>Nie udało się wczytać danych z pliku GPX lub plik jest uszkodzony/pusty. Brak danych do wyświetlenia mapy i wykresów.</p> <p>Nie udało się wczytać danych z pliku GPX lub plik jest uszkodzony/pusty. Brak danych do wyświetlenia mapy i wykresów.</p>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
</div> </div>
<?php <?php
} }
+232 -194
View File
@@ -1,195 +1,233 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_dashboard_page() { function statpress_dashboard_page() {
echo '<div class="wrap"><h1>StatPress Dashboard</h1>'; echo '<div class="wrap"><h1>StatPress Dashboard</h1>';
statpress_render_history_table();
echo '</div>'; // --- MIGRATION NOTICE ---
} // Show the migration button if it hasn't been completed yet.
if ( ! get_option( 'statpress_migration_complete' ) ) {
function statpress_render_history_table() { $migration_url = wp_nonce_url(
global $wpdb; admin_url( 'admin.php?page=statpress-dashboard&action=statpress_migrate_data' ),
'statpress_migration_nonce'
// Definicje nazw tabel (z uwzględnieniem prefixu WP, jeśli był użyty przy tworzeniu) );
// Zakładam, że tabele nazywają się dokładnie tak jak w dokumentacji, ale dobrą praktyką jest $wpdb->prefix echo '<div class="notice notice-warning is-dismissible" style="padding-bottom: 10px;">';
// Jeśli tabele są "sztywne" (bez prefixu wp_), usuń $wpdb->prefix. echo '<h4>Migracja danych StatPress</h4>';
$table_activities = $wpdb->prefix . 'statpress_activities'; echo '<p>Wygląda na to, że istnieją dane w starych tabelach (<code>mystat_*</code>), które można przenieść. Kliknij przycisk poniżej, aby rozpocząć proces.</p>';
$table_categories = $wpdb->prefix . 'statpress_categories'; echo '<p><strong>Ważne:</strong> Ta operacja jest jednorazowa i powinna być wykonana tylko raz, na pustej instalacji wtyczki StatPress.</p>';
echo '<a href="' . esc_url( $migration_url ) . '" class="button button-primary">Rozpocznij migrację danych</a>';
// --- 1. OBSŁUGA USUWANIA (DELETE) --- echo '</div>';
if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'statpress_delete' === $_GET['action'] ) { }
$activity_id = intval( $_GET['id'] );
// Show the results of the migration after it's done.
// Weryfikacja bezpieczeństwa (Nonce) $migration_results = get_transient( 'statpress_migration_results' );
if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_' . $activity_id ) ) { if ( $migration_results ) {
$result = $wpdb->delete( echo '<div class="notice notice-success is-dismissible">';
$table_activities, echo '<h4>Migracja zakończona!</h4>';
array( 'id' => $activity_id ), echo '<ul>';
array( '%d' ) foreach ( $migration_results as $table => $result ) {
); if ( 'success' === $result['status'] ) {
echo '<li>Tabela <strong>' . esc_html( $table ) . '</strong>: Przeniesiono <strong>' . esc_html( $result['count'] ) . '</strong> wierszy.</li>';
if ( $result ) { } elseif ( 'skipped' === $result['status'] ) {
echo '<div class="notice notice-success is-dismissible"><p>Aktywność została usunięta.</p></div>'; echo '<li>Tabela <strong>' . esc_html( $table ) . '</strong>: Pominięto, ponieważ nowa tabela zawiera już dane (' . esc_html( $result['count'] ) . ' wierszy).</li>';
} else { } elseif ( 'failure' === $result['status'] ) {
echo '<div class="notice notice-error is-dismissible"><p>Wystąpił błąd podczas usuwania.</p></div>'; echo '<li>Tabela <strong>' . esc_html( $table ) . '</strong>: <strong style="color:red;">Migracja nieudana.</strong> Błąd bazy danych: <pre style="display:inline;white-space:pre-wrap;">' . esc_html( $result['error'] ) . '</pre></li>';
} }
} else { }
echo '<div class="notice notice-error is-dismissible"><p>Błąd weryfikacji bezpieczeństwa (Nonce).</p></div>'; echo '</ul>';
} echo '<p>Twoje dane powinny być teraz widoczne. Stare tabele (<code>' . esc_html( $GLOBALS['wpdb']->prefix ) . 'mystat_*</code>) wciąż istnieją w bazie danych, ale nie są już używane. Możesz je usunąć ręcznie (np. przez phpMyAdmin), jeśli wszystko działa poprawnie.</p>';
} echo '</div>';
delete_transient( 'statpress_migration_results' );
// --- 2. USTAWIENIA PAGINACJI --- }
$items_per_page = 20; // Ile wpisów na stronę // --- END MIGRATION NOTICE ---
$current_page = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
$offset = ( $current_page - 1 ) * $items_per_page; statpress_render_history_table();
echo '</div>';
// --- 3. POBIERANIE DANYCH (SELECT) --- }
// Pobranie całkowitej liczby wpisów do paginacji
$total_items = $wpdb->get_var( "SELECT COUNT(id) FROM $table_activities" ); function statpress_render_history_table() {
$total_pages = ceil( $total_items / $items_per_page ); global $wpdb;
// Pobieramy wpisy dla bieżącej strony // Definicje nazw tabel (z uwzględnieniem prefixu WP, jeśli był użyty przy tworzeniu)
$sql = $wpdb->prepare( // Zakładam, że tabele nazywają się dokładnie tak jak w dokumentacji, ale dobrą praktyką jest $wpdb->prefix
" // Jeśli tabele są "sztywne" (bez prefixu wp_), usuń $wpdb->prefix.
SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name $table_activities = $wpdb->prefix . 'statpress_activities';
FROM $table_activities a $table_categories = $wpdb->prefix . 'statpress_categories';
LEFT JOIN $table_categories c ON a.category_id = c.id
LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id // --- 1. OBSŁUGA USUWANIA (DELETE) ---
LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'statpress_delete' === $_GET['action'] ) {
ORDER BY a.date DESC, a.id DESC $activity_id = intval( $_GET['id'] );
LIMIT %d OFFSET %d
", // Weryfikacja bezpieczeństwa (Nonce)
$items_per_page, if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_' . $activity_id ) ) {
$offset $result = $wpdb->delete(
); $table_activities,
array( 'id' => $activity_id ),
$activities = $wpdb->get_results( $sql ); array( '%d' )
);
// --- 4. WIDOK TABELI (HTML) ---
?> if ( $result ) {
<div class="wrap"> echo '<div class="notice notice-success is-dismissible"><p>Aktywność została usunięta.</p></div>';
<h2>Historia Aktywności</h2> } else {
echo '<div class="notice notice-error is-dismissible"><p>Wystąpił błąd podczas usuwania.</p></div>';
<?php if ( $total_pages > 1 ) : ?> }
<div class="tablenav top"> } else {
<div class="tablenav-pages"> echo '<div class="notice notice-error is-dismissible"><p>Błąd weryfikacji bezpieczeństwa (Nonce).</p></div>';
<span class="displaying-num"><?php echo esc_html( $total_items ); ?> aktywności</span> }
<?php }
echo paginate_links(
array( // --- 2. USTAWIENIA PAGINACJI ---
'base' => add_query_arg( 'paged', '%#%' ), $items_per_page = 20; // Ile wpisów na stronę
'format' => '', $current_page = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
'total' => $total_pages, $offset = ( $current_page - 1 ) * $items_per_page;
'current' => $current_page,
'prev_text' => '&laquo; Poprzednia', // --- 3. POBIERANIE DANYCH (SELECT) ---
'next_text' => 'Następna &raquo;', // 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 );
?>
</div> // Pobieramy wpisy dla bieżącej strony
</div> $sql = $wpdb->prepare(
<?php endif; ?> "
SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name
<table class="wp-list-table widefat fixed striped table-view-list"> FROM $table_activities a
<thead> LEFT JOIN $table_categories c ON a.category_id = c.id
<tr> LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id
<th scope="col" style="width: 50px;">Ikona</th> LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
<th scope="col">Data</th> ORDER BY a.date DESC, a.id DESC
<th scope="col">Tytuł</th> LIMIT %d OFFSET %d
<th scope="col" style="width: 120px;">Kategoria</th> ",
<th scope="col">Typ</th> $items_per_page,
<th scope="col">Sprzęt</th> $offset
<th scope="col">Dystans (km)</th> );
<th scope="col">Czas</th>
<th scope="col">Śr. prędkość</th> $activities = $wpdb->get_results( $sql );
<th scope="col" style="width: 180px;">Akcja</th>
</tr> // --- 4. WIDOK TABELI (HTML) ---
</thead> ?>
<tbody> <div class="wrap">
<?php if ( ! empty( $activities ) ) : ?> <h2>Historia Aktywności</h2>
<?php foreach ( $activities as $row ) : ?>
<?php <?php if ( $total_pages > 1 ) : ?>
// Generowanie URL-i akcji z zachowaniem paginacji <div class="tablenav top">
$delete_url = wp_nonce_url( <div class="tablenav-pages">
add_query_arg( <span class="displaying-num"><?php echo esc_html( $total_items ); ?> aktywności</span>
array( <?php
'action' => 'statpress_delete', echo paginate_links(
'id' => $row->id, array(
) 'base' => add_query_arg( 'paged', '%#%' ),
), 'format' => '',
'statpress_delete_' . $row->id 'total' => $total_pages,
); 'current' => $current_page,
'prev_text' => '&laquo; Poprzednia',
$edit_url = add_query_arg( 'next_text' => 'Następna &raquo;',
array( )
'page' => 'statpress-edit-activity', );
'id' => $row->id, ?>
), </div>
admin_url( 'admin.php' ) </div>
); <?php endif; ?>
$details_url = add_query_arg( <table class="wp-list-table widefat fixed striped table-view-list">
array( <thead>
'page' => 'statpress-view-activity', <tr>
'id' => $row->id, <th scope="col" style="width: 50px;">Ikona</th>
), <th scope="col">Data</th>
admin_url( 'admin.php' ) <th scope="col">Tytuł</th>
); <th scope="col" style="width: 120px;">Kategoria</th>
?> <th scope="col">Typ</th>
<tr> <th scope="col">Sprzęt</th>
<td> <th scope="col">Dystans (km)</th>
<?php if ( ! empty( $row->icon ) ) : ?> <th scope="col">Czas</th>
<span class="dashicons <?php echo esc_attr( $row->icon ); ?>" style="color: <?php echo esc_attr( $row->color ); ?>;"></span> <th scope="col">Śr. prędkość</th>
<?php endif; ?> <th scope="col" style="width: 180px;">Akcja</th>
</td> </tr>
<td><?php echo esc_html( $row->date ); ?></td> </thead>
<td><strong><a href="<?php echo esc_url( $details_url ); ?>"><?php echo esc_html( $row->title ? wp_trim_words( $row->title, 6 ) : '(bez tytułu)' ); ?></a></strong></td> <tbody>
<td><?php echo esc_html( $row->category_name ); ?></td> <?php if ( ! empty( $activities ) ) : ?>
<td><?php echo esc_html( $row->event_type_name ); ?></td> <?php foreach ( $activities as $row ) : ?>
<td><?php echo esc_html( $row->equipment_name ); ?></td> <?php
<td><?php echo number_format( $row->distance, 2, ',', ' ' ); ?></td> // Generowanie URL-i akcji z zachowaniem paginacji
<td><?php echo esc_html( $row->duration ); ?></td> $delete_url = wp_nonce_url(
<td><?php echo $row->avg_speed ? number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h' : '-'; ?></td> add_query_arg(
<td> array(
<a href="<?php echo esc_url( $edit_url ); ?>" class="button button-small">Edytuj</a> 'action' => 'statpress_delete',
<a href="<?php echo esc_url( $details_url ); ?>" class="button button-small">Szczegóły</a> 'id' => $row->id,
<a href="<?php echo esc_url( $delete_url ); ?>" )
class="button button-small button-link-delete" ),
onclick="return confirm('Czy na pewno chcesz usunąć ten wpis?');"> 'statpress_delete_' . $row->id
Usuń );
</a>
</td> $edit_url = add_query_arg(
</tr> array(
<?php endforeach; ?> 'page' => 'statpress-edit-activity',
<?php else : ?> 'id' => $row->id,
<tr> ),
<td colspan="10">Brak zarejestrowanych aktywności. Dodaj pierwszy trening powyżej!</td> admin_url( 'admin.php' )
</tr> );
<?php endif; ?>
</tbody> $details_url = add_query_arg(
</table> array(
'page' => 'statpress-view-activity',
<?php if ( $total_pages > 1 ) : ?> 'id' => $row->id,
<div class="tablenav bottom"> ),
<div class="tablenav-pages"> admin_url( 'admin.php' )
<span class="displaying-num"><?php echo esc_html( $total_items ); ?> aktywności</span> );
<?php ?>
echo paginate_links( <tr>
array( <td>
'base' => add_query_arg( 'paged', '%#%' ), <?php if ( ! empty( $row->icon ) ) : ?>
'format' => '', <span class="dashicons <?php echo esc_attr( $row->icon ); ?>" style="color: <?php echo esc_attr( $row->color ); ?>;"></span>
'total' => $total_pages, <?php endif; ?>
'current' => $current_page, </td>
'prev_text' => '&laquo; Poprzednia', <td><?php echo esc_html( $row->date ); ?></td>
'next_text' => 'Następna &raquo;', <td><strong><a href="<?php echo esc_url( $details_url ); ?>"><?php echo esc_html( $row->title ? wp_trim_words( $row->title, 6 ) : '(bez tytułu)' ); ?></a></strong></td>
) <td><?php echo esc_html( $row->category_name ); ?></td>
); <td><?php echo esc_html( $row->event_type_name ); ?></td>
?> <td><?php echo esc_html( $row->equipment_name ); ?></td>
</div> <td><?php echo number_format( $row->distance, 2, ',', ' ' ); ?></td>
</div> <td><?php echo esc_html( $row->duration ); ?></td>
<?php endif; ?> <td><?php echo $row->avg_speed ? number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h' : '-'; ?></td>
</div> <td>
<?php <a href="<?php echo esc_url( $edit_url ); ?>" class="button button-small">Edytuj</a>
<a href="<?php echo esc_url( $details_url ); ?>" class="button button-small">Szczegóły</a>
<a href="<?php echo esc_url( $delete_url ); ?>"
class="button button-small button-link-delete"
onclick="return confirm('Czy na pewno chcesz usunąć ten wpis?');">
Usuń
</a>
</td>
</tr>
<?php endforeach; ?>
<?php else : ?>
<tr>
<td colspan="10">Brak zarejestrowanych aktywności. Dodaj pierwszy trening powyżej!</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php if ( $total_pages > 1 ) : ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<span class="displaying-num"><?php echo esc_html( $total_items ); ?> aktywności</span>
<?php
echo paginate_links(
array(
'base' => add_query_arg( 'paged', '%#%' ),
'format' => '',
'total' => $total_pages,
'current' => $current_page,
'prev_text' => '&laquo; Poprzednia',
'next_text' => 'Następna &raquo;',
)
);
?>
</div>
</div>
<?php endif; ?>
</div>
<?php
} }
+296 -296
View File
@@ -1,297 +1,297 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_equipment_page() { function statpress_equipment_page() {
global $wpdb; global $wpdb;
$table_equipment = $wpdb->prefix . 'statpress_equipment'; $table_equipment = $wpdb->prefix . 'statpress_equipment';
$message = ''; $message = '';
$notice_class = ''; $notice_class = '';
// Handle POST requests (add/update) // Handle POST requests (add/update)
if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_equipment' ) ) { if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_equipment' ) ) {
$item_id = isset( $_POST['equipment_id'] ) ? intval( $_POST['equipment_id'] ) : 0; $item_id = isset( $_POST['equipment_id'] ) ? intval( $_POST['equipment_id'] ) : 0;
$data = array( $data = array(
'name' => sanitize_text_field( $_POST['equipment_name'] ), 'name' => sanitize_text_field( $_POST['equipment_name'] ),
'type' => sanitize_text_field( $_POST['equipment_type'] ), 'type' => sanitize_text_field( $_POST['equipment_type'] ),
'purchase_date' => empty( $_POST['purchase_date'] ) ? null : sanitize_text_field( $_POST['purchase_date'] ), 'purchase_date' => empty( $_POST['purchase_date'] ) ? null : sanitize_text_field( $_POST['purchase_date'] ),
'initial_cost' => empty( $_POST['initial_cost'] ) ? null : floatval( str_replace( ',', '.', $_POST['initial_cost'] ) ), 'initial_cost' => empty( $_POST['initial_cost'] ) ? null : floatval( str_replace( ',', '.', $_POST['initial_cost'] ) ),
'status' => sanitize_text_field( $_POST['status'] ), 'status' => sanitize_text_field( $_POST['status'] ),
'notes' => sanitize_textarea_field( $_POST['notes'] ), 'notes' => sanitize_textarea_field( $_POST['notes'] ),
); );
if ( ! empty( $data['name'] ) ) { if ( ! empty( $data['name'] ) ) {
if ( $item_id > 0 ) { // Update if ( $item_id > 0 ) { // Update
$result = $wpdb->update( $table_equipment, $data, array( 'id' => $item_id ) ); $result = $wpdb->update( $table_equipment, $data, array( 'id' => $item_id ) );
if ( false !== $result ) { if ( false !== $result ) {
$message = 'Sprzęt zaktualizowany.'; $message = 'Sprzęt zaktualizowany.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} else { } else {
$message = 'Błąd podczas aktualizacji sprzętu: ' . $wpdb->last_error; $message = 'Błąd podczas aktualizacji sprzętu: ' . $wpdb->last_error;
$notice_class = 'notice-error'; $notice_class = 'notice-error';
} }
} else { // Insert } else { // Insert
$result = $wpdb->insert( $table_equipment, $data ); $result = $wpdb->insert( $table_equipment, $data );
if ( false !== $result ) { if ( false !== $result ) {
$message = 'Sprzęt dodany.'; $message = 'Sprzęt dodany.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} else { } else {
$message = 'Błąd podczas dodawania sprzętu: ' . $wpdb->last_error; $message = 'Błąd podczas dodawania sprzętu: ' . $wpdb->last_error;
$notice_class = 'notice-error'; $notice_class = 'notice-error';
} }
} }
} else { } else {
$message = 'Nazwa sprzętu nie może być pusta.'; $message = 'Nazwa sprzętu nie może być pusta.';
$notice_class = 'notice-error'; $notice_class = 'notice-error';
} }
} }
// Handle GET requests (delete) // Handle GET requests (delete)
if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_equipment_' . $_GET['id'] ) ) { if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_equipment_' . $_GET['id'] ) ) {
// Sprawdź, czy sprzęt nie jest używany // Sprawdź, czy sprzęt nie jest używany
$usage_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}statpress_activities WHERE equipment_id = %d", intval( $_GET['id'] ) ) ); $usage_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}statpress_activities WHERE equipment_id = %d", intval( $_GET['id'] ) ) );
if ( 0 == $usage_count ) { if ( 0 == $usage_count ) {
$wpdb->delete( $table_equipment, array( 'id' => intval( $_GET['id'] ) ) ); $wpdb->delete( $table_equipment, array( 'id' => intval( $_GET['id'] ) ) );
// Usuń również powiązane wpisy w dzienniku // Usuń również powiązane wpisy w dzienniku
$wpdb->delete( $wpdb->prefix . 'statpress_equipment_log', array( 'equipment_id' => intval( $_GET['id'] ) ) ); $wpdb->delete( $wpdb->prefix . 'statpress_equipment_log', array( 'equipment_id' => intval( $_GET['id'] ) ) );
$message = 'Sprzęt usunięty.'; $message = 'Sprzęt usunięty.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} else { } else {
$message = 'Nie można usunąć sprzętu, ponieważ jest przypisany do ' . $usage_count . ' aktywności. Zmień jego status na "wycofany".'; $message = 'Nie można usunąć sprzętu, ponieważ jest przypisany do ' . $usage_count . ' aktywności. Zmień jego status na "wycofany".';
$notice_class = 'notice-error'; $notice_class = 'notice-error';
} }
} }
} }
// Prepare for form (for editing) // Prepare for form (for editing)
$item_to_edit = null; $item_to_edit = null;
$statuses = array( 'aktywny', 'sprzedany', 'wycofany' ); $statuses = array( 'aktywny', 'sprzedany', 'wycofany' );
if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) {
$item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment WHERE id = %d", intval( $_GET['id'] ) ) ); $item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment WHERE id = %d", intval( $_GET['id'] ) ) );
} }
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$equipment_list = $wpdb->get_results(" $equipment_list = $wpdb->get_results("
SELECT SELECT
eq.id, eq.id,
eq.name, eq.name,
eq.type, eq.type,
eq.status, eq.status,
SUM(a.distance) as total_distance, SUM(a.distance) as total_distance,
COUNT(a.id) as activity_count COUNT(a.id) as activity_count
FROM FROM
{$table_equipment} eq {$table_equipment} eq
LEFT JOIN LEFT JOIN
{$table_activities} a ON eq.id = a.equipment_id {$table_activities} a ON eq.id = a.equipment_id
GROUP BY GROUP BY
eq.id, eq.name, eq.type, eq.status eq.id, eq.name, eq.type, eq.status
ORDER BY ORDER BY
eq.status ASC, eq.name ASC" eq.status ASC, eq.name ASC"
); );
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Zarządzaj Sprzętem</h1> <h1>Zarządzaj Sprzętem</h1>
<?php if ( ! empty( $message ) ) : ?> <?php if ( ! empty( $message ) ) : ?>
<div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div> <div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div>
<?php endif; ?> <?php endif; ?>
<div id="col-container" class="wp-clearfix"> <div id="col-container" class="wp-clearfix">
<div id="col-left"> <div id="col-left">
<div class="col-wrap"> <div class="col-wrap">
<div class="form-wrap"> <div class="form-wrap">
<h2><?php echo $item_to_edit ? 'Edytuj pozycję' : 'Dodaj nowy sprzęt'; ?></h2> <h2><?php echo $item_to_edit ? 'Edytuj pozycję' : 'Dodaj nowy sprzęt'; ?></h2>
<form method="post"> <form method="post">
<input type="hidden" name="equipment_id" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->id ) : '0'; ?>"> <input type="hidden" name="equipment_id" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->id ) : '0'; ?>">
<?php wp_nonce_field( 'statpress_manage_equipment' ); ?> <?php wp_nonce_field( 'statpress_manage_equipment' ); ?>
<div class="form-field form-required"><label for="equipment_name">Nazwa</label><input type="text" name="equipment_name" id="equipment_name" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->name ) : ''; ?>" required></div> <div class="form-field form-required"><label for="equipment_name">Nazwa</label><input type="text" name="equipment_name" id="equipment_name" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->name ) : ''; ?>" required></div>
<div class="form-field"><label for="equipment_type">Typ</label><input type="text" name="equipment_type" id="equipment_type" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->type ) : 'Rower'; ?>"></div> <div class="form-field"><label for="equipment_type">Typ</label><input type="text" name="equipment_type" id="equipment_type" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->type ) : 'Rower'; ?>"></div>
<div class="form-field"><label for="purchase_date">Data zakupu</label><input type="date" name="purchase_date" id="purchase_date" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->purchase_date ) : ''; ?>"></div> <div class="form-field"><label for="purchase_date">Data zakupu</label><input type="date" name="purchase_date" id="purchase_date" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->purchase_date ) : ''; ?>"></div>
<div class="form-field"><label for="initial_cost">Koszt zakupu</label><input type="text" name="initial_cost" id="initial_cost" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->initial_cost ) : ''; ?>" placeholder="0.00"></div> <div class="form-field"><label for="initial_cost">Koszt zakupu</label><input type="text" name="initial_cost" id="initial_cost" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->initial_cost ) : ''; ?>" placeholder="0.00"></div>
<div class="form-field"><label for="status">Status</label><select name="status" id="status"><?php foreach ( $statuses as $s ) : ?><option value="<?php echo esc_attr( $s ); ?>" <?php if ( $item_to_edit ) { <div class="form-field"><label for="status">Status</label><select name="status" id="status"><?php foreach ( $statuses as $s ) : ?><option value="<?php echo esc_attr( $s ); ?>" <?php if ( $item_to_edit ) {
selected( $item_to_edit->status, $s );} ?>><?php echo esc_html( ucfirst( $s ) ); ?></option><?php endforeach; ?></select></div> selected( $item_to_edit->status, $s );} ?>><?php echo esc_html( ucfirst( $s ) ); ?></option><?php endforeach; ?></select></div>
<div class="form-field"><label for="notes">Notatki</label><textarea name="notes" id="notes" rows="3"><?php echo $item_to_edit ? esc_textarea( $item_to_edit->notes ) : ''; ?></textarea></div> <div class="form-field"><label for="notes">Notatki</label><textarea name="notes" id="notes" rows="3"><?php echo $item_to_edit ? esc_textarea( $item_to_edit->notes ) : ''; ?></textarea></div>
<?php submit_button( $item_to_edit ? 'Zaktualizuj' : 'Dodaj' ); ?> <?php submit_button( $item_to_edit ? 'Zaktualizuj' : 'Dodaj' ); ?>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div id="col-right"> <div id="col-right">
<div class="col-wrap"> <div class="col-wrap">
<table class="wp-list-table widefat striped"> <table class="wp-list-table widefat striped">
<thead><tr><th>Nazwa</th><th>Przebieg</th><th>Liczba aktywności</th><th>Status</th><th style="width: 220px;">Akcje</th></tr></thead> <thead><tr><th>Nazwa</th><th>Przebieg</th><th>Liczba aktywności</th><th>Status</th><th style="width: 220px;">Akcje</th></tr></thead>
<tbody> <tbody>
<?php foreach ( $equipment_list as $item ) : ?> <?php foreach ( $equipment_list as $item ) : ?>
<?php <?php
$details_url = admin_url( 'admin.php?page=statpress-equipment-details&id=' . $item->id ); $details_url = admin_url( 'admin.php?page=statpress-equipment-details&id=' . $item->id );
?> ?>
<tr> <tr>
<td><strong><a href="<?php echo esc_url( $details_url ); ?>"><?php echo esc_html( $item->name ); ?></a></strong><br><small><?php echo esc_html( $item->type ); ?></small></td> <td><strong><a href="<?php echo esc_url( $details_url ); ?>"><?php echo esc_html( $item->name ); ?></a></strong><br><small><?php echo esc_html( $item->type ); ?></small></td>
<td><?php echo $item->total_distance ? number_format( $item->total_distance, 2, ',', ' ' ) . ' km' : '0 km'; ?></td> <td><?php echo $item->total_distance ? number_format( $item->total_distance, 2, ',', ' ' ) . ' km' : '0 km'; ?></td>
<td><?php echo (int) $item->activity_count; ?></td> <td><?php echo (int) $item->activity_count; ?></td>
<td><?php echo esc_html( ucfirst( $item->status ) ); ?></td> <td><?php echo esc_html( ucfirst( $item->status ) ); ?></td>
<td> <td>
<a href="<?php echo esc_url( $details_url ); ?>" class="button button-small">Dziennik / Serwis</a> <a href="<?php echo esc_url( $details_url ); ?>" class="button button-small">Dziennik / Serwis</a>
<a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit', 'id' => $item->id ) ) ); ?>" class="button button-small">Edytuj</a> <a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit', 'id' => $item->id ) ) ); ?>" class="button button-small">Edytuj</a>
<a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'id' => $item->id ) ), 'statpress_delete_equipment_' . $item->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten sprzęt?')" class="button button-small button-link-delete">Usuń</a> <a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'id' => $item->id ) ), 'statpress_delete_equipment_' . $item->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten sprzęt?')" class="button button-small button-link-delete">Usuń</a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<?php <?php
} }
function statpress_equipment_details_page() { function statpress_equipment_details_page() {
global $wpdb; global $wpdb;
$equipment_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; $equipment_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0;
if ( 0 === $equipment_id ) { if ( 0 === $equipment_id ) {
echo '<div class="wrap"><h1>Błąd</h1><p>Nie podano ID sprzętu.</p></div>'; echo '<div class="wrap"><h1>Błąd</h1><p>Nie podano ID sprzętu.</p></div>';
return; return;
} }
$table_equipment = $wpdb->prefix . 'statpress_equipment'; $table_equipment = $wpdb->prefix . 'statpress_equipment';
$table_equipment_log = $wpdb->prefix . 'statpress_equipment_log'; $table_equipment_log = $wpdb->prefix . 'statpress_equipment_log';
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$message = ''; $message = '';
$notice_class = ''; $notice_class = '';
// --- Handle Service Log form submissions (add/update/delete) --- // --- Handle Service Log form submissions (add/update/delete) ---
if ( isset( $_POST['submit_log'] ) && check_admin_referer( 'statpress_manage_equipment_log' ) ) { if ( isset( $_POST['submit_log'] ) && check_admin_referer( 'statpress_manage_equipment_log' ) ) {
$log_id = isset( $_POST['log_id'] ) ? intval( $_POST['log_id'] ) : 0; $log_id = isset( $_POST['log_id'] ) ? intval( $_POST['log_id'] ) : 0;
$log_data = array( $log_data = array(
'equipment_id' => $equipment_id, 'equipment_id' => $equipment_id,
'log_date' => sanitize_text_field( $_POST['log_date'] ), 'log_date' => sanitize_text_field( $_POST['log_date'] ),
'log_type' => sanitize_text_field( $_POST['log_type'] ), 'log_type' => sanitize_text_field( $_POST['log_type'] ),
'description' => sanitize_textarea_field( $_POST['description'] ), 'description' => sanitize_textarea_field( $_POST['description'] ),
'cost' => empty( $_POST['cost'] ) ? null : floatval( str_replace( ',', '.', $_POST['cost'] ) ), 'cost' => empty( $_POST['cost'] ) ? null : floatval( str_replace( ',', '.', $_POST['cost'] ) ),
'mileage' => empty( $_POST['mileage'] ) ? null : intval( $_POST['mileage'] ), 'mileage' => empty( $_POST['mileage'] ) ? null : intval( $_POST['mileage'] ),
); );
if ( ! empty( $log_data['log_date'] ) && ! empty( $log_data['log_type'] ) && ! empty( $log_data['description'] ) ) { if ( ! empty( $log_data['log_date'] ) && ! empty( $log_data['log_type'] ) && ! empty( $log_data['description'] ) ) {
if ( $log_id > 0 ) { if ( $log_id > 0 ) {
$wpdb->update( $table_equipment_log, $log_data, array( 'id' => $log_id ) ); $wpdb->update( $table_equipment_log, $log_data, array( 'id' => $log_id ) );
$message = 'Wpis w dzienniku zaktualizowany.'; $message = 'Wpis w dzienniku zaktualizowany.';
} else { } else {
$wpdb->insert( $table_equipment_log, $log_data ); $wpdb->insert( $table_equipment_log, $log_data );
$message = 'Wpis dodany do dziennika.'; $message = 'Wpis dodany do dziennika.';
} }
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} else { } else {
$message = 'Wypełnij wymagane pola (Data, Typ, Opis).'; $message = 'Wypełnij wymagane pola (Data, Typ, Opis).';
$notice_class = 'notice-error'; $notice_class = 'notice-error';
} }
} }
if ( isset( $_GET['action'], $_GET['log_id'], $_GET['_wpnonce'] ) && 'delete_log' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['log_id'], $_GET['_wpnonce'] ) && 'delete_log' === $_GET['action'] ) {
if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_log_' . $_GET['log_id'] ) ) { if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_log_' . $_GET['log_id'] ) ) {
$wpdb->delete( $table_equipment_log, array( 'id' => intval( $_GET['log_id'] ) ) ); $wpdb->delete( $table_equipment_log, array( 'id' => intval( $_GET['log_id'] ) ) );
$message = 'Wpis z dziennika usunięty.'; $message = 'Wpis z dziennika usunięty.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} }
} }
// --- Get Data --- // --- Get Data ---
$equipment = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment WHERE id = %d", $equipment_id ) ); $equipment = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment WHERE id = %d", $equipment_id ) );
if ( ! $equipment ) { if ( ! $equipment ) {
echo '<div class="wrap"><h1>Błąd</h1><p>Nie znaleziono sprzętu o podanym ID.</p></div>'; echo '<div class="wrap"><h1>Błąd</h1><p>Nie znaleziono sprzętu o podanym ID.</p></div>';
return; return;
} }
$total_mileage = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(distance) FROM $table_activities WHERE equipment_id = %d", $equipment_id ) ); $total_mileage = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(distance) FROM $table_activities WHERE equipment_id = %d", $equipment_id ) );
$total_service_cost = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(cost) FROM $table_equipment_log WHERE equipment_id = %d", $equipment_id ) ); $total_service_cost = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(cost) FROM $table_equipment_log WHERE equipment_id = %d", $equipment_id ) );
$service_log = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_equipment_log WHERE equipment_id = %d ORDER BY log_date DESC, id DESC", $equipment_id ) ); $service_log = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_equipment_log WHERE equipment_id = %d ORDER BY log_date DESC, id DESC", $equipment_id ) );
$log_to_edit = null; $log_to_edit = null;
if ( isset( $_GET['action'], $_GET['log_id'] ) && 'edit_log' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['log_id'] ) && 'edit_log' === $_GET['action'] ) {
$log_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment_log WHERE id = %d", intval( $_GET['log_id'] ) ) ); $log_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment_log WHERE id = %d", intval( $_GET['log_id'] ) ) );
} }
$log_types = array( 'Naprawa', 'Zakup części', 'Przegląd', 'Modyfikacja', 'Inne' ); $log_types = array( 'Naprawa', 'Zakup części', 'Przegląd', 'Modyfikacja', 'Inne' );
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Dziennik serwisowy: <?php echo esc_html( $equipment->name ); ?></h1> <h1>Dziennik serwisowy: <?php echo esc_html( $equipment->name ); ?></h1>
<p><a href="<?php echo esc_url( admin_url( 'admin.php?page=statpress-equipment' ) ); ?>">&larr; Powrót do listy sprzętu</a></p> <p><a href="<?php echo esc_url( admin_url( 'admin.php?page=statpress-equipment' ) ); ?>">&larr; Powrót do listy sprzętu</a></p>
<?php if ( ! empty( $message ) ) : ?> <?php if ( ! empty( $message ) ) : ?>
<div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div> <div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div>
<?php endif; ?> <?php endif; ?>
<div class="postbox"> <div class="postbox">
<div class="postbox-header"><h2 class="hndle">Podsumowanie Sprzętu</h2></div> <div class="postbox-header"><h2 class="hndle">Podsumowanie Sprzętu</h2></div>
<div class="inside"> <div class="inside">
<div class="main"> <div class="main">
<p><strong>Całkowity przebieg:</strong> <?php echo number_format( (float) $total_mileage, 2, ',', ' ' ); ?> km</p> <p><strong>Całkowity przebieg:</strong> <?php echo number_format( (float) $total_mileage, 2, ',', ' ' ); ?> km</p>
<?php if ( $total_service_cost > 0 ) : ?> <?php if ( $total_service_cost > 0 ) : ?>
<p><strong>Całkowity koszt serwisu:</strong> <?php echo number_format( $total_service_cost, 2, ',', ' ' ); ?> zł</p> <p><strong>Całkowity koszt serwisu:</strong> <?php echo number_format( $total_service_cost, 2, ',', ' ' ); ?> zł</p>
<?php endif; ?> <?php endif; ?>
<p><strong>Status:</strong> <?php echo esc_html( ucfirst( $equipment->status ) ); ?></p> <p><strong>Status:</strong> <?php echo esc_html( ucfirst( $equipment->status ) ); ?></p>
<?php if ( $equipment->purchase_date ) : ?><p><strong>Data zakupu:</strong> <?php echo esc_html( $equipment->purchase_date ); ?></p><?php endif; ?> <?php if ( $equipment->purchase_date ) : ?><p><strong>Data zakupu:</strong> <?php echo esc_html( $equipment->purchase_date ); ?></p><?php endif; ?>
<?php if ( $equipment->initial_cost ) : ?><p><strong>Koszt zakupu:</strong> <?php echo number_format( $equipment->initial_cost, 2, ',', ' ' ); ?> zł</p><?php endif; ?> <?php if ( $equipment->initial_cost ) : ?><p><strong>Koszt zakupu:</strong> <?php echo number_format( $equipment->initial_cost, 2, ',', ' ' ); ?> zł</p><?php endif; ?>
<?php if ( $equipment->notes ) : ?><p><strong>Notatki:</strong><br><?php echo nl2br( esc_html( $equipment->notes ) ); ?></p><?php endif; ?> <?php if ( $equipment->notes ) : ?><p><strong>Notatki:</strong><br><?php echo nl2br( esc_html( $equipment->notes ) ); ?></p><?php endif; ?>
</div> </div>
</div> </div>
</div> </div>
<div id="col-container" class="wp-clearfix"> <div id="col-container" class="wp-clearfix">
<div id="col-left"> <div id="col-left">
<div class="col-wrap"> <div class="col-wrap">
<div class="form-wrap"> <div class="form-wrap">
<h2><?php echo $log_to_edit ? 'Edytuj wpis' : 'Dodaj wpis do dziennika'; ?></h2> <h2><?php echo $log_to_edit ? 'Edytuj wpis' : 'Dodaj wpis do dziennika'; ?></h2>
<form method="post"> <form method="post">
<input type="hidden" name="log_id" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->id ) : '0'; ?>"> <input type="hidden" name="log_id" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->id ) : '0'; ?>">
<?php wp_nonce_field( 'statpress_manage_equipment_log' ); ?> <?php wp_nonce_field( 'statpress_manage_equipment_log' ); ?>
<div class="form-field form-required"><label for="log_date">Data</label><input type="date" name="log_date" id="log_date" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->log_date ) : current_time( 'Y-m-d' ); ?>" required></div> <div class="form-field form-required"><label for="log_date">Data</label><input type="date" name="log_date" id="log_date" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->log_date ) : current_time( 'Y-m-d' ); ?>" required></div>
<div class="form-field form-required"><label for="log_type">Typ</label><select name="log_type" id="log_type" required><?php foreach ( $log_types as $type ) : ?><option value="<?php echo esc_attr( $type ); ?>" <?php if ( $log_to_edit ) { <div class="form-field form-required"><label for="log_type">Typ</label><select name="log_type" id="log_type" required><?php foreach ( $log_types as $type ) : ?><option value="<?php echo esc_attr( $type ); ?>" <?php if ( $log_to_edit ) {
selected( $log_to_edit->log_type, $type );} ?>><?php echo esc_html( $type ); ?></option><?php endforeach; ?></select></div> selected( $log_to_edit->log_type, $type );} ?>><?php echo esc_html( $type ); ?></option><?php endforeach; ?></select></div>
<div class="form-field form-required"><label for="description">Opis</label><textarea name="description" id="description" rows="4" required><?php echo $log_to_edit ? esc_textarea( $log_to_edit->description ) : ''; ?></textarea></div> <div class="form-field form-required"><label for="description">Opis</label><textarea name="description" id="description" rows="4" required><?php echo $log_to_edit ? esc_textarea( $log_to_edit->description ) : ''; ?></textarea></div>
<div class="form-field"><label for="cost">Koszt (zł)</label><input type="text" name="cost" id="cost" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->cost ) : ''; ?>" placeholder="0.00"></div> <div class="form-field"><label for="cost">Koszt (zł)</label><input type="text" name="cost" id="cost" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->cost ) : ''; ?>" placeholder="0.00"></div>
<div class="form-field"><label for="mileage">Przebieg (km)</label><input type="number" name="mileage" id="mileage" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->mileage ) : round( (float) $total_mileage ); ?>"><p class="description">Przebieg sprzętu w momencie serwisu. Domyślnie aktualny całkowity przebieg.</p></div> <div class="form-field"><label for="mileage">Przebieg (km)</label><input type="number" name="mileage" id="mileage" value="<?php echo $log_to_edit ? esc_attr( $log_to_edit->mileage ) : round( (float) $total_mileage ); ?>"><p class="description">Przebieg sprzętu w momencie serwisu. Domyślnie aktualny całkowity przebieg.</p></div>
<?php submit_button( $log_to_edit ? 'Zaktualizuj wpis' : 'Dodaj wpis', 'primary', 'submit_log' ); ?> <?php submit_button( $log_to_edit ? 'Zaktualizuj wpis' : 'Dodaj wpis', 'primary', 'submit_log' ); ?>
<?php if ( $log_to_edit ) : ?><a href="<?php echo esc_url( remove_query_arg( array( 'action', 'log_id' ) ) ); ?>" class="button">Anuluj edycję</a><?php endif; ?> <?php if ( $log_to_edit ) : ?><a href="<?php echo esc_url( remove_query_arg( array( 'action', 'log_id' ) ) ); ?>" class="button">Anuluj edycję</a><?php endif; ?>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div id="col-right"> <div id="col-right">
<div class="col-wrap"> <div class="col-wrap">
<table class="wp-list-table widefat fixed striped"> <table class="wp-list-table widefat fixed striped">
<tfoot> <tfoot>
<tr> <tr>
<th colspan="3" style="text-align:right;">Suma kosztów:</th> <th colspan="3" style="text-align:right;">Suma kosztów:</th>
<th colspan="3"><strong><?php echo number_format( (float) $total_service_cost, 2, ',', ' ' ); ?> zł</strong></th> <th colspan="3"><strong><?php echo number_format( (float) $total_service_cost, 2, ',', ' ' ); ?> zł</strong></th>
</tr> </tr>
</tfoot> </tfoot>
<thead><tr><th>Data</th><th>Typ</th><th>Opis</th><th>Koszt</th><th>Przebieg</th><th style="width: 100px;">Akcje</th></tr></thead> <thead><tr><th>Data</th><th>Typ</th><th>Opis</th><th>Koszt</th><th>Przebieg</th><th style="width: 100px;">Akcje</th></tr></thead>
<tbody> <tbody>
<?php if ( empty( $service_log ) ) : ?> <?php if ( empty( $service_log ) ) : ?>
<tr><td colspan="6">Brak wpisów w dzienniku.</td></tr> <tr><td colspan="6">Brak wpisów w dzienniku.</td></tr>
<?php else : ?> <?php else : ?>
<?php foreach ( $service_log as $log ) : ?> <?php foreach ( $service_log as $log ) : ?>
<tr> <tr>
<td><?php echo esc_html( $log->log_date ); ?></td> <td><?php echo esc_html( $log->log_date ); ?></td>
<td><?php echo esc_html( $log->log_type ); ?></td> <td><?php echo esc_html( $log->log_type ); ?></td>
<td><?php echo nl2br( esc_html( $log->description ) ); ?></td> <td><?php echo nl2br( esc_html( $log->description ) ); ?></td>
<td><?php echo $log->cost ? number_format( $log->cost, 2, ',', ' ' ) . ' zł' : '-'; ?></td> <td><?php echo $log->cost ? number_format( $log->cost, 2, ',', ' ' ) . ' zł' : '-'; ?></td>
<td><?php echo $log->mileage ? number_format( $log->mileage, 0, '', ' ' ) . ' km' : '-'; ?></td> <td><?php echo $log->mileage ? number_format( $log->mileage, 0, '', ' ' ) . ' km' : '-'; ?></td>
<td> <td>
<a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit_log', 'log_id' => $log->id ) ) ); ?>">Edytuj</a> | <a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit_log', 'log_id' => $log->id ) ) ); ?>">Edytuj</a> |
<a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete_log', 'log_id' => $log->id ) ), 'statpress_delete_log_' . $log->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten wpis?')" style="color: #a00;">Usuń</a> <a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete_log', 'log_id' => $log->id ) ), 'statpress_delete_log_' . $log->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten wpis?')" style="color: #a00;">Usuń</a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?php endif; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<?php <?php
} }
+87 -87
View File
@@ -1,88 +1,88 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_event_types_page() { function statpress_event_types_page() {
global $wpdb; global $wpdb;
$table_event_types = $wpdb->prefix . 'statpress_event_types'; $table_event_types = $wpdb->prefix . 'statpress_event_types';
$message = ''; $message = '';
$notice_class = ''; $notice_class = '';
// Handle POST requests (add/update) // Handle POST requests (add/update)
if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_event_type' ) ) { if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_event_type' ) ) {
$name = sanitize_text_field( $_POST['event_type_name'] ); $name = sanitize_text_field( $_POST['event_type_name'] );
$type_id = isset( $_POST['event_type_id'] ) ? intval( $_POST['event_type_id'] ) : 0; $type_id = isset( $_POST['event_type_id'] ) ? intval( $_POST['event_type_id'] ) : 0;
if ( ! empty( $name ) ) { if ( ! empty( $name ) ) {
if ( $type_id > 0 ) { // Update if ( $type_id > 0 ) { // Update
$wpdb->update( $table_event_types, array( 'name' => $name ), array( 'id' => $type_id ) ); $wpdb->update( $table_event_types, array( 'name' => $name ), array( 'id' => $type_id ) );
$message = 'Typ wydarzenia zaktualizowany.'; $message = 'Typ wydarzenia zaktualizowany.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} else { // Insert } else { // Insert
$wpdb->insert( $table_event_types, array( 'name' => $name ) ); $wpdb->insert( $table_event_types, array( 'name' => $name ) );
$message = 'Typ wydarzenia dodany.'; $message = 'Typ wydarzenia dodany.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} }
} else { } else {
$message = 'Nazwa typu wydarzenia nie może być pusta.'; $message = 'Nazwa typu wydarzenia nie może być pusta.';
$notice_class = 'notice-error'; $notice_class = 'notice-error';
} }
} }
// Handle GET requests (delete) // Handle GET requests (delete)
if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_event_type_' . $_GET['id'] ) ) { if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_event_type_' . $_GET['id'] ) ) {
$wpdb->delete( $table_event_types, array( 'id' => intval( $_GET['id'] ) ) ); $wpdb->delete( $table_event_types, array( 'id' => intval( $_GET['id'] ) ) );
$message = 'Typ wydarzenia usunięty.'; $message = 'Typ wydarzenia usunięty.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} }
} }
// Prepare for form (for editing) // Prepare for form (for editing)
$item_to_edit = null; $item_to_edit = null;
if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) {
$item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_event_types WHERE id = %d", intval( $_GET['id'] ) ) ); $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" ); $event_types = $wpdb->get_results( "SELECT * FROM $table_event_types ORDER BY name ASC" );
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Typy Wydarzeń</h1> <h1>Typy Wydarzeń</h1>
<?php if ( ! empty( $message ) ) : ?> <?php if ( ! empty( $message ) ) : ?>
<div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div> <div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div>
<?php endif; ?> <?php endif; ?>
<div id="col-container" class="wp-clearfix"> <div id="col-container" class="wp-clearfix">
<div id="col-left"> <div id="col-left">
<div class="col-wrap"> <div class="col-wrap">
<div class="form-wrap"> <div class="form-wrap">
<h2><?php echo $item_to_edit ? 'Edytuj typ wydarzenia' : 'Dodaj nowy typ wydarzenia'; ?></h2> <h2><?php echo $item_to_edit ? 'Edytuj typ wydarzenia' : 'Dodaj nowy typ wydarzenia'; ?></h2>
<form method="post"> <form method="post">
<input type="hidden" name="event_type_id" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->id ) : '0'; ?>"> <input type="hidden" name="event_type_id" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->id ) : '0'; ?>">
<?php wp_nonce_field( 'statpress_manage_event_type' ); ?> <?php wp_nonce_field( 'statpress_manage_event_type' ); ?>
<div class="form-field"> <div class="form-field">
<label for="event_type_name">Nazwa</label> <label for="event_type_name">Nazwa</label>
<input type="text" name="event_type_name" id="event_type_name" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->name ) : ''; ?>" required> <input type="text" name="event_type_name" id="event_type_name" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->name ) : ''; ?>" required>
</div> </div>
<?php submit_button( $item_to_edit ? 'Zaktualizuj' : 'Dodaj' ); ?> <?php submit_button( $item_to_edit ? 'Zaktualizuj' : 'Dodaj' ); ?>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div id="col-right"> <div id="col-right">
<div class="col-wrap"> <div class="col-wrap">
<table class="wp-list-table widefat fixed striped"> <table class="wp-list-table widefat fixed striped">
<thead><tr><th>Nazwa</th><th style="width: 100px;">Akcje</th></tr></thead> <thead><tr><th>Nazwa</th><th style="width: 100px;">Akcje</th></tr></thead>
<tbody> <tbody>
<?php foreach ( $event_types as $type ) : ?> <?php foreach ( $event_types as $type ) : ?>
<tr><td><?php echo esc_html( $type->name ); ?></td><td><a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit', 'id' => $type->id ) ) ); ?>">Edytuj</a> | <a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'id' => $type->id ) ), 'statpress_delete_event_type_' . $type->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten typ?')" style="color: #a00;">Usuń</a></td></tr> <tr><td><?php echo esc_html( $type->name ); ?></td><td><a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit', 'id' => $type->id ) ) ); ?>">Edytuj</a> | <a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'id' => $type->id ) ), 'statpress_delete_event_type_' . $type->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten typ?')" style="color: #a00;">Usuń</a></td></tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<?php <?php
} }
+198 -198
View File
@@ -1,199 +1,199 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/** /**
* Calculates the current progress for a given goal. * Calculates the current progress for a given goal.
* *
* @param object $goal The goal object from the database. * @param object $goal The goal object from the database.
* @return array An array containing 'current_value' and 'percentage'. * @return array An array containing 'current_value' and 'percentage'.
*/ */
function statpress_get_goal_progress( $goal ) { function statpress_get_goal_progress( $goal ) {
global $wpdb; global $wpdb;
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$sql_select = ''; $sql_select = '';
switch ( $goal->goal_type ) { switch ( $goal->goal_type ) {
case 'distance': case 'distance':
$sql_select = 'SUM(distance)'; $sql_select = 'SUM(distance)';
break; break;
case 'duration_sec': case 'duration_sec':
$sql_select = 'SUM(TIME_TO_SEC(duration))'; $sql_select = 'SUM(TIME_TO_SEC(duration))';
break; break;
case 'count': case 'count':
$sql_select = 'COUNT(id)'; $sql_select = 'COUNT(id)';
break; break;
default: default:
return array( return array(
'current_value' => 0, 'current_value' => 0,
'percentage' => 0, 'percentage' => 0,
); );
} }
$where_clauses = array(); $where_clauses = array();
$where_clauses[] = $wpdb->prepare( 'YEAR(date) = %d', $goal->year ); $where_clauses[] = $wpdb->prepare( 'YEAR(date) = %d', $goal->year );
if ( ! empty( $goal->month ) ) { if ( ! empty( $goal->month ) ) {
$where_clauses[] = $wpdb->prepare( 'MONTH(date) = %d', $goal->month ); $where_clauses[] = $wpdb->prepare( 'MONTH(date) = %d', $goal->month );
} }
if ( ! empty( $goal->category_id ) ) { if ( ! empty( $goal->category_id ) ) {
$where_clauses[] = $wpdb->prepare( 'category_id = %d', $goal->category_id ); $where_clauses[] = $wpdb->prepare( 'category_id = %d', $goal->category_id );
} }
$sql = "SELECT {$sql_select} FROM {$table_activities} WHERE " . implode( ' AND ', $where_clauses ); $sql = "SELECT {$sql_select} FROM {$table_activities} WHERE " . implode( ' AND ', $where_clauses );
$current_value = (float) $wpdb->get_var( $sql ); $current_value = (float) $wpdb->get_var( $sql );
$percentage = ( $goal->target_value > 0 ) ? ( $current_value / $goal->target_value ) * 100 : 0; $percentage = ( $goal->target_value > 0 ) ? ( $current_value / $goal->target_value ) * 100 : 0;
return array( return array(
'current_value' => $current_value, 'current_value' => $current_value,
'percentage' => $percentage, 'percentage' => $percentage,
); );
} }
function statpress_goals_page() { function statpress_goals_page() {
global $wpdb; global $wpdb;
$table_goals = $wpdb->prefix . 'statpress_goals'; $table_goals = $wpdb->prefix . 'statpress_goals';
$table_categories = $wpdb->prefix . 'statpress_categories'; $table_categories = $wpdb->prefix . 'statpress_categories';
$message = ''; $message = '';
$notice_class = ''; $notice_class = '';
// Handle POST requests (add/update) // Handle POST requests (add/update)
if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_goal' ) ) { if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_goal' ) ) {
$goal_id = isset( $_POST['goal_id'] ) ? intval( $_POST['goal_id'] ) : 0; $goal_id = isset( $_POST['goal_id'] ) ? intval( $_POST['goal_id'] ) : 0;
// Convert hours to seconds for duration goal type before saving // Convert hours to seconds for duration goal type before saving
$target_value = floatval( str_replace( ',', '.', $_POST['target_value'] ) ); $target_value = floatval( str_replace( ',', '.', $_POST['target_value'] ) );
if ( 'duration_sec' === $_POST['goal_type'] ) { if ( 'duration_sec' === $_POST['goal_type'] ) {
$target_value *= 3600; $target_value *= 3600;
} }
$data = array( $data = array(
'name' => sanitize_text_field( $_POST['goal_name'] ), 'name' => sanitize_text_field( $_POST['goal_name'] ),
'goal_type' => sanitize_text_field( $_POST['goal_type'] ), 'goal_type' => sanitize_text_field( $_POST['goal_type'] ),
'target_value' => $target_value, 'target_value' => $target_value,
'year' => intval( $_POST['year'] ), 'year' => intval( $_POST['year'] ),
'month' => 'all' === $_POST['month'] ? null : intval( $_POST['month'] ), 'month' => 'all' === $_POST['month'] ? null : intval( $_POST['month'] ),
'category_id' => 'all' === $_POST['category_id'] ? null : intval( $_POST['category_id'] ), 'category_id' => 'all' === $_POST['category_id'] ? null : intval( $_POST['category_id'] ),
); );
if ( ! empty( $data['name'] ) && ! empty( $data['goal_type'] ) && $data['target_value'] > 0 && $data['year'] > 2000 ) { if ( ! empty( $data['name'] ) && ! empty( $data['goal_type'] ) && $data['target_value'] > 0 && $data['year'] > 2000 ) {
if ( $goal_id > 0 ) { // Update if ( $goal_id > 0 ) { // Update
$wpdb->update( $table_goals, $data, array( 'id' => $goal_id ) ); $wpdb->update( $table_goals, $data, array( 'id' => $goal_id ) );
$message = 'Cel zaktualizowany.'; $message = 'Cel zaktualizowany.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} else { // Insert } else { // Insert
$wpdb->insert( $table_goals, $data ); $wpdb->insert( $table_goals, $data );
$message = 'Cel dodany.'; $message = 'Cel dodany.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} }
} else { } else {
$message = 'Wypełnij poprawnie wszystkie wymagane pola (Nazwa, Typ, Cel, Rok).'; $message = 'Wypełnij poprawnie wszystkie wymagane pola (Nazwa, Typ, Cel, Rok).';
$notice_class = 'notice-error'; $notice_class = 'notice-error';
} }
} }
// Handle GET requests (delete) // Handle GET requests (delete)
if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_goal_' . $_GET['id'] ) ) { if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_goal_' . $_GET['id'] ) ) {
$wpdb->delete( $table_goals, array( 'id' => intval( $_GET['id'] ) ) ); $wpdb->delete( $table_goals, array( 'id' => intval( $_GET['id'] ) ) );
$message = 'Cel usunięty.'; $message = 'Cel usunięty.';
$notice_class = 'notice-success'; $notice_class = 'notice-success';
} }
} }
// Prepare for form (for editing) // Prepare for form (for editing)
$item_to_edit = null; $item_to_edit = null;
if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) { if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) {
$item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_goals WHERE id = %d", intval( $_GET['id'] ) ) ); $item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_goals WHERE id = %d", intval( $_GET['id'] ) ) );
} }
$goals = $wpdb->get_results( "SELECT g.*, c.name as category_name FROM $table_goals g LEFT JOIN $table_categories c ON g.category_id = c.id ORDER BY g.year DESC, g.name ASC" ); $goals = $wpdb->get_results( "SELECT g.*, c.name as category_name FROM $table_goals g LEFT JOIN $table_categories c ON g.category_id = c.id ORDER BY g.year DESC, g.name ASC" );
$categories = $wpdb->get_results( "SELECT * FROM $table_categories ORDER BY name ASC" ); $categories = $wpdb->get_results( "SELECT * FROM $table_categories ORDER BY name ASC" );
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Zarządzaj Celami</h1> <h1>Zarządzaj Celami</h1>
<?php if ( ! empty( $message ) ) : ?> <?php if ( ! empty( $message ) ) : ?>
<div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div> <div class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible"><p><?php echo esc_html( $message ); ?></p></div>
<?php endif; ?> <?php endif; ?>
<div id="col-container" class="wp-clearfix"> <div id="col-container" class="wp-clearfix">
<div id="col-left"> <div id="col-left">
<div class="col-wrap"> <div class="col-wrap">
<div class="form-wrap"> <div class="form-wrap">
<h2><?php echo $item_to_edit ? 'Edytuj cel' : 'Dodaj nowy cel'; ?></h2> <h2><?php echo $item_to_edit ? 'Edytuj cel' : 'Dodaj nowy cel'; ?></h2>
<form method="post"> <form method="post">
<input type="hidden" name="goal_id" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->id ) : '0'; ?>"> <input type="hidden" name="goal_id" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->id ) : '0'; ?>">
<?php wp_nonce_field( 'statpress_manage_goal' ); ?> <?php wp_nonce_field( 'statpress_manage_goal' ); ?>
<div class="form-field form-required"><label for="goal_name">Nazwa celu</label><input type="text" name="goal_name" id="goal_name" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->name ) : ''; ?>" required></div> <div class="form-field form-required"><label for="goal_name">Nazwa celu</label><input type="text" name="goal_name" id="goal_name" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->name ) : ''; ?>" required></div>
<div class="form-field form-required"><label for="goal_type">Typ celu</label><select name="goal_type" id="goal_type" required><option value="distance" <?php if ( $item_to_edit ) { selected( $item_to_edit->goal_type, 'distance' ); } ?>>Dystans (km)</option><option value="duration_sec" <?php if ( $item_to_edit ) { selected( $item_to_edit->goal_type, 'duration_sec' ); } ?>>Czas (w godzinach)</option><option value="count" <?php if ( $item_to_edit ) { selected( $item_to_edit->goal_type, 'count' ); } ?>>Liczba aktywności</option></select></div> <div class="form-field form-required"><label for="goal_type">Typ celu</label><select name="goal_type" id="goal_type" required><option value="distance" <?php if ( $item_to_edit ) { selected( $item_to_edit->goal_type, 'distance' ); } ?>>Dystans (km)</option><option value="duration_sec" <?php if ( $item_to_edit ) { selected( $item_to_edit->goal_type, 'duration_sec' ); } ?>>Czas (w godzinach)</option><option value="count" <?php if ( $item_to_edit ) { selected( $item_to_edit->goal_type, 'count' ); } ?>>Liczba aktywności</option></select></div>
<div class="form-field form-required"><label for="target_value">Wartość docelowa</label><input type="text" name="target_value" id="target_value" value="<?php echo $item_to_edit ? esc_attr( 'duration_sec' === $item_to_edit->goal_type ? $item_to_edit->target_value / 3600 : $item_to_edit->target_value ) : ''; ?>" required><p>Dla czasu podaj wartość w godzinach (np. 100.5).</p></div> <div class="form-field form-required"><label for="target_value">Wartość docelowa</label><input type="text" name="target_value" id="target_value" value="<?php echo $item_to_edit ? esc_attr( 'duration_sec' === $item_to_edit->goal_type ? $item_to_edit->target_value / 3600 : $item_to_edit->target_value ) : ''; ?>" required><p>Dla czasu podaj wartość w godzinach (np. 100.5).</p></div>
<div class="form-field form-required"><label for="year">Rok</label><input type="number" name="year" id="year" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->year ) : current_time( 'Y' ); ?>" required></div> <div class="form-field form-required"><label for="year">Rok</label><input type="number" name="year" id="year" value="<?php echo $item_to_edit ? esc_attr( $item_to_edit->year ) : current_time( 'Y' ); ?>" required></div>
<div class="form-field"><label for="month">Miesiąc</label><select name="month" id="month"><option value="all">Cały rok</option><?php for ( $i = 1; $i <= 12; $i++ ) : ?><option value="<?php echo $i; ?>" <?php if ( $item_to_edit ) { selected( $item_to_edit->month, $i ); } ?>><?php echo date_i18n( 'F', mktime( 0, 0, 0, $i, 10 ) ); ?></option><?php endfor; ?></select></div> <div class="form-field"><label for="month">Miesiąc</label><select name="month" id="month"><option value="all">Cały rok</option><?php for ( $i = 1; $i <= 12; $i++ ) : ?><option value="<?php echo $i; ?>" <?php if ( $item_to_edit ) { selected( $item_to_edit->month, $i ); } ?>><?php echo date_i18n( 'F', mktime( 0, 0, 0, $i, 10 ) ); ?></option><?php endfor; ?></select></div>
<div class="form-field"><label for="category_id">Kategoria</label><select name="category_id" id="category_id"><option value="all">Wszystkie</option><?php foreach ( $categories as $cat ) : ?><option value="<?php echo esc_attr( $cat->id ); ?>" <?php if ( $item_to_edit ) { selected( $item_to_edit->category_id, $cat->id ); } ?>><?php echo esc_html( $cat->name ); ?></option><?php endforeach; ?></select></div> <div class="form-field"><label for="category_id">Kategoria</label><select name="category_id" id="category_id"><option value="all">Wszystkie</option><?php foreach ( $categories as $cat ) : ?><option value="<?php echo esc_attr( $cat->id ); ?>" <?php if ( $item_to_edit ) { selected( $item_to_edit->category_id, $cat->id ); } ?>><?php echo esc_html( $cat->name ); ?></option><?php endforeach; ?></select></div>
<?php submit_button( $item_to_edit ? 'Zaktualizuj' : 'Dodaj' ); ?> <?php submit_button( $item_to_edit ? 'Zaktualizuj' : 'Dodaj' ); ?>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div id="col-right"> <div id="col-right">
<div class="col-wrap"> <div class="col-wrap">
<table class="wp-list-table widefat fixed striped"> <table class="wp-list-table widefat fixed striped">
<thead><tr><th>Cel</th><th>Postęp</th><th style="width: 100px;">Akcje</th></tr></thead> <thead><tr><th>Cel</th><th>Postęp</th><th style="width: 100px;">Akcje</th></tr></thead>
<tbody> <tbody>
<?php if ( empty( $goals ) ) : ?> <?php if ( empty( $goals ) ) : ?>
<tr><td colspan="3">Brak zdefiniowanych celów.</td></tr> <tr><td colspan="3">Brak zdefiniowanych celów.</td></tr>
<?php else : ?> <?php else : ?>
<?php foreach ( $goals as $goal ) : ?> <?php foreach ( $goals as $goal ) : ?>
<?php <?php
$progress = statpress_get_goal_progress( $goal ); $progress = statpress_get_goal_progress( $goal );
$percentage = min( 100, $progress['percentage'] ); $percentage = min( 100, $progress['percentage'] );
$target_formatted = ''; $target_formatted = '';
$current_formatted = ''; $current_formatted = '';
if ( 'duration_sec' === $goal->goal_type ) { if ( 'duration_sec' === $goal->goal_type ) {
$target_formatted = round( $goal->target_value / 3600 ) . ' godz.'; $target_formatted = round( $goal->target_value / 3600 ) . ' godz.';
$current_formatted = sprintf( '%d godz. %d min.', floor( $progress['current_value'] / 3600 ), floor( ( $progress['current_value'] % 3600 ) / 60 ) ); $current_formatted = sprintf( '%d godz. %d min.', floor( $progress['current_value'] / 3600 ), floor( ( $progress['current_value'] % 3600 ) / 60 ) );
} elseif ( 'distance' === $goal->goal_type ) { } elseif ( 'distance' === $goal->goal_type ) {
$target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km'; $target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km';
$current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km'; $current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km';
} else { // count } else { // count
$target_formatted = (int) $goal->target_value; $target_formatted = (int) $goal->target_value;
$current_formatted = (int) $progress['current_value']; $current_formatted = (int) $progress['current_value'];
} }
?> ?>
<tr> <tr>
<td> <td>
<strong><?php echo esc_html( $goal->name ); ?></strong><br> <strong><?php echo esc_html( $goal->name ); ?></strong><br>
<small> <small>
<?php echo esc_html( $goal->year ); ?> <?php echo esc_html( $goal->year ); ?>
<?php if ( $goal->month ) { echo ' / ' . date_i18n( 'F', mktime( 0, 0, 0, $goal->month, 10 ) ); } ?> <?php if ( $goal->month ) { echo ' / ' . date_i18n( 'F', mktime( 0, 0, 0, $goal->month, 10 ) ); } ?>
<?php if ( $goal->category_name ) { echo ' / ' . esc_html( $goal->category_name ); } ?> <?php if ( $goal->category_name ) { echo ' / ' . esc_html( $goal->category_name ); } ?>
</small> </small>
</td> </td>
<td> <td>
<div class="statpress-progress-bar-container"> <div class="statpress-progress-bar-container">
<div class="statpress-progress-bar" style="width: <?php echo esc_attr( $percentage ); ?>%;"></div> <div class="statpress-progress-bar" style="width: <?php echo esc_attr( $percentage ); ?>%;"></div>
</div> </div>
<small><?php echo $current_formatted; ?> z <?php echo $target_formatted; ?> (<?php echo round( $percentage, 1 ); ?>%)</small> <small><?php echo $current_formatted; ?> z <?php echo $target_formatted; ?> (<?php echo round( $percentage, 1 ); ?>%)</small>
</td> </td>
<td> <td>
<a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit', 'id' => $goal->id ) ) ); ?>">Edytuj</a> | <a href="<?php echo esc_url( add_query_arg( array( 'action' => 'edit', 'id' => $goal->id ) ) ); ?>">Edytuj</a> |
<a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'id' => $goal->id ) ), 'statpress_delete_goal_' . $goal->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten cel?')" style="color: #a00;">Usuń</a> <a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'id' => $goal->id ) ), 'statpress_delete_goal_' . $goal->id ) ); ?>" onclick="return confirm('Czy na pewno chcesz usunąć ten cel?')" style="color: #a00;">Usuń</a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?php endif; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<style> <style>
.statpress-progress-bar-container { background: #eee; border-radius: 4px; height: 12px; width: 100%; overflow: hidden; margin-bottom: 4px; } .statpress-progress-bar-container { background: #eee; border-radius: 4px; height: 12px; width: 100%; overflow: hidden; margin-bottom: 4px; }
.statpress-progress-bar { background: #3498db; height: 100%; border-radius: 4px; transition: width 0.5s ease-in-out; } .statpress-progress-bar { background: #3498db; height: 100%; border-radius: 4px; transition: width 0.5s ease-in-out; }
</style> </style>
<?php <?php
} }
+308 -308
View File
@@ -1,309 +1,309 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_import_csv_page() { function statpress_import_csv_page() {
echo '<div class="wrap"><h1>Importuj aktywności z pliku CSV</h1>'; echo '<div class="wrap"><h1>Importuj aktywności z pliku CSV</h1>';
// Handle the form submission // Handle the form submission
if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['statpress_csv_import_nonce_field'] ) ) { if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['statpress_csv_import_nonce_field'] ) ) {
statpress_handle_csv_import(); statpress_handle_csv_import();
} }
// Display the form // Display the form
?> ?>
<div class="postbox"> <div class="postbox">
<div class="postbox-header"><h2 class="hndle">Instrukcje i formularz importu</h2></div> <div class="postbox-header"><h2 class="hndle">Instrukcje i formularz importu</h2></div>
<div class="inside"> <div class="inside">
<p>Aby zaimportować dane, możesz wgrać plik CSV <strong>LUB</strong> wkleić dane bezpośrednio w pole tekstowe poniżej. Ta druga opcja jest zalecana, jeśli napotykasz błędy z plikiem.</p> <p>Aby zaimportować dane, możesz wgrać plik CSV <strong>LUB</strong> wkleić dane bezpośrednio w pole tekstowe poniżej. Ta druga opcja jest zalecana, jeśli napotykasz błędy z plikiem.</p>
<p><strong>Wymagane kolumny:</strong> <code>Data</code>, <code>Tytuł</code>, <code>Dystans</code> oraz <code>Typ aktywności</code> (lub <code>Kategoria</code>).</p> <p><strong>Wymagane kolumny:</strong> <code>Data</code>, <code>Tytuł</code>, <code>Dystans</code> oraz <code>Typ aktywności</code> (lub <code>Kategoria</code>).</p>
<p><strong>Opcjonalne kolumny:</strong> <code>Czas</code> (format HH:MM:SS), <code>Kalorie</code>, <code>Średnie tętno</code>, <code>Maksymalne tętno</code>, <code>Średnia prędkość</code>, <code>Maksymalna prędkość</code>, <code>Średni rytm pedałowania</code>, <code>Maksymalny rytm pedałowania</code>, <code>Całkowity wznios</code>, <code>Całkowity spadek</code>, <code>Minimalna wysokość</code>, <code>Maksymalna wysokość</code>, <code>Sprzęt</code>, <code>Typ wydarzenia</code>.</p> <p><strong>Opcjonalne kolumny:</strong> <code>Czas</code> (format HH:MM:SS), <code>Kalorie</code>, <code>Średnie tętno</code>, <code>Maksymalne tętno</code>, <code>Średnia prędkość</code>, <code>Maksymalna prędkość</code>, <code>Średni rytm pedałowania</code>, <code>Maksymalny rytm pedałowania</code>, <code>Całkowity wznios</code>, <code>Całkowity spadek</code>, <code>Minimalna wysokość</code>, <code>Maksymalna wysokość</code>, <code>Sprzęt</code>, <code>Typ wydarzenia</code>.</p>
<p><strong>Ważne:</strong> <p><strong>Ważne:</strong>
<ul> <ul>
<li>Data musi być w formacie <code>YYYY-MM-DD</code>.</li> <li>Data musi być w formacie <code>YYYY-MM-DD</code>.</li>
<li>Dystans i prędkość: użyj kropki jako separatora dziesiętnego (np. <code>10.5</code>).</li> <li>Dystans i prędkość: użyj kropki jako separatora dziesiętnego (np. <code>10.5</code>).</li>
<li>Nazwy w kolumnach <code>Typ aktywności</code>, <code>Sprzęt</code>, <code>Typ wydarzenia</code> muszą dokładnie odpowiadać nazwom zdefiniowanym w ustawieniach wtyczki. Jeśli nazwa nie zostanie znaleziona, wiersz zostanie pominięty.</li> <li>Nazwy w kolumnach <code>Typ aktywności</code>, <code>Sprzęt</code>, <code>Typ wydarzenia</code> muszą dokładnie odpowiadać nazwom zdefiniowanym w ustawieniach wtyczki. Jeśli nazwa nie zostanie znaleziona, wiersz zostanie pominięty.</li>
</ul> </ul>
</p> </p>
<hr> <hr>
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<?php wp_nonce_field( 'statpress_csv_import_nonce', 'statpress_csv_import_nonce_field' ); ?> <?php wp_nonce_field( 'statpress_csv_import_nonce', 'statpress_csv_import_nonce_field' ); ?>
<table class="form-table"> <table class="form-table">
<tr valign="top"> <tr valign="top">
<th scope="row"><label for="statpress_csv_file">Opcja 1: Wgraj plik CSV</label></th> <th scope="row"><label for="statpress_csv_file">Opcja 1: Wgraj plik CSV</label></th>
<td><input type="file" id="statpress_csv_file" name="statpress_csv_file" accept=".csv,text/csv" /></td> <td><input type="file" id="statpress_csv_file" name="statpress_csv_file" accept=".csv,text/csv" /></td>
</tr> </tr>
<tr valign="top"> <tr valign="top">
<th scope="row"><label for="statpress_csv_data">Opcja 2: Wklej dane CSV</label></th> <th scope="row"><label for="statpress_csv_data">Opcja 2: Wklej dane CSV</label></th>
<td><textarea name="statpress_csv_data" id="statpress_csv_data" rows="15" class="large-text" placeholder="Wklej tutaj zawartość swojego pliku CSV...&#10;Typ aktywności,Data,Tytuł,Dystans,...&#10;Rower,2025-01-01,Nowy Rok,10.5,..."></textarea></td> <td><textarea name="statpress_csv_data" id="statpress_csv_data" rows="15" class="large-text" placeholder="Wklej tutaj zawartość swojego pliku CSV...&#10;Typ aktywności,Data,Tytuł,Dystans,...&#10;Rower,2025-01-01,Nowy Rok,10.5,..."></textarea></td>
</tr> </tr>
</table> </table>
<?php submit_button( 'Importuj' ); ?> <?php submit_button( 'Importuj' ); ?>
</form> </form>
</div> </div>
</div> </div>
<?php <?php
echo '</div>'; echo '</div>';
} }
function statpress_handle_csv_import() { function statpress_handle_csv_import() {
global $wpdb; global $wpdb;
if ( ! isset( $_POST['statpress_csv_import_nonce_field'] ) || ! wp_verify_nonce( $_POST['statpress_csv_import_nonce_field'], 'statpress_csv_import_nonce' ) ) { if ( ! isset( $_POST['statpress_csv_import_nonce_field'] ) || ! wp_verify_nonce( $_POST['statpress_csv_import_nonce_field'], 'statpress_csv_import_nonce' ) ) {
echo '<div class="notice notice-error"><p>Błąd weryfikacji bezpieczeństwa.</p></div>'; echo '<div class="notice notice-error"><p>Błąd weryfikacji bezpieczeństwa.</p></div>';
return; return;
} }
if ( ! current_user_can( 'manage_options' ) ) { if ( ! current_user_can( 'manage_options' ) ) {
echo '<div class="notice notice-error"><p>Nie masz wystarczających uprawnień.</p></div>'; echo '<div class="notice notice-error"><p>Nie masz wystarczających uprawnień.</p></div>';
return; return;
} }
// Unify input source: prefer textarea, fall back to file upload. // Unify input source: prefer textarea, fall back to file upload.
$csv_content = ''; $csv_content = '';
if ( ! empty( $_POST['statpress_csv_data'] ) ) { if ( ! empty( $_POST['statpress_csv_data'] ) ) {
$csv_content = stripslashes( $_POST['statpress_csv_data'] ); $csv_content = stripslashes( $_POST['statpress_csv_data'] );
} elseif ( ! empty( $_FILES['statpress_csv_file']['tmp_name'] ) && UPLOAD_ERR_OK === $_FILES['statpress_csv_file']['error'] ) { } elseif ( ! empty( $_FILES['statpress_csv_file']['tmp_name'] ) && UPLOAD_ERR_OK === $_FILES['statpress_csv_file']['error'] ) {
$csv_content = file_get_contents( $_FILES['statpress_csv_file']['tmp_name'] ); $csv_content = file_get_contents( $_FILES['statpress_csv_file']['tmp_name'] );
} }
if ( empty( trim( $csv_content ) ) ) { if ( empty( trim( $csv_content ) ) ) {
echo '<div class="notice notice-error"><p>Nie podano danych do importu. Wgraj plik lub wklej dane w pole tekstowe.</p></div>'; echo '<div class="notice notice-error"><p>Nie podano danych do importu. Wgraj plik lub wklej dane w pole tekstowe.</p></div>';
return; return;
} }
// Mapowanie polskich i angielskich nazw kolumn na wewnętrzne klucze // Mapowanie polskich i angielskich nazw kolumn na wewnętrzne klucze
$column_map = array( $column_map = array(
// Polish => English // Polish => English
'typ aktywności' => 'category_name', 'typ aktywności' => 'category_name',
'data' => 'date', 'data' => 'date',
'tytuł' => 'title', 'tytuł' => 'title',
'dystans' => 'distance', 'dystans' => 'distance',
'kalorie' => 'calories', 'kalorie' => 'calories',
'czas' => 'duration', 'czas' => 'duration',
'średnie tętno' => 'avg_heart_rate', 'średnie tętno' => 'avg_heart_rate',
'maksymalne tętno' => 'max_heart_rate', 'maksymalne tętno' => 'max_heart_rate',
'średnia prędkość' => 'avg_speed', 'średnia prędkość' => 'avg_speed',
'maksymalna prędkość' => 'max_speed', 'maksymalna prędkość' => 'max_speed',
'średni rytm pedałowania' => 'avg_cadence', 'średni rytm pedałowania' => 'avg_cadence',
'maksymalny rytm pedałowania' => 'max_cadence', 'maksymalny rytm pedałowania' => 'max_cadence',
'całkowity wznios' => 'total_elevation_gain', 'całkowity wznios' => 'total_elevation_gain',
'całkowity spadek' => 'total_elevation_loss', 'całkowity spadek' => 'total_elevation_loss',
'minimalna wysokość' => 'min_altitude', 'minimalna wysokość' => 'min_altitude',
'maksymalna wysokość' => 'max_altitude', 'maksymalna wysokość' => 'max_altitude',
'sprzęt' => 'equipment_name', 'sprzęt' => 'equipment_name',
'typ wydarzenia' => 'event_type_name', 'typ wydarzenia' => 'event_type_name',
'komentarz' => 'comment', 'komentarz' => 'comment',
'link do strava' => 'strava_url', 'link do strava' => 'strava_url',
// English keys for compatibility // English keys for compatibility
'category_name' => 'category_name', 'category_name' => 'category_name',
'date' => 'date', 'date' => 'date',
'title' => 'title', 'title' => 'title',
'distance' => 'distance', 'distance' => 'distance',
'calories' => 'calories', 'calories' => 'calories',
'duration' => 'duration', 'duration' => 'duration',
'avg_heart_rate' => 'avg_heart_rate', 'avg_heart_rate' => 'avg_heart_rate',
'max_heart_rate' => 'max_heart_rate', 'max_heart_rate' => 'max_heart_rate',
'avg_speed' => 'avg_speed', 'avg_speed' => 'avg_speed',
'max_speed' => 'max_speed', 'max_speed' => 'max_speed',
'avg_cadence' => 'avg_cadence', 'avg_cadence' => 'avg_cadence',
'max_cadence' => 'max_cadence', 'max_cadence' => 'max_cadence',
'total_elevation_gain' => 'total_elevation_gain', 'total_elevation_gain' => 'total_elevation_gain',
'total_elevation_loss' => 'total_elevation_loss', 'total_elevation_loss' => 'total_elevation_loss',
'min_altitude' => 'min_altitude', 'min_altitude' => 'min_altitude',
'max_altitude' => 'max_altitude', 'max_altitude' => 'max_altitude',
'equipment_name' => 'equipment_name', 'equipment_name' => 'equipment_name',
'event_type_name' => 'event_type_name', 'event_type_name' => 'event_type_name',
'comment' => 'comment', 'comment' => 'comment',
'strava_url' => 'strava_url', 'strava_url' => 'strava_url',
); );
// --- START: Robust, case-insensitive lookup --- // --- START: Robust, case-insensitive lookup ---
$table_categories = $wpdb->prefix . 'statpress_categories'; $table_categories = $wpdb->prefix . 'statpress_categories';
$table_event_types = $wpdb->prefix . 'statpress_event_types'; $table_event_types = $wpdb->prefix . 'statpress_event_types';
$table_equipment = $wpdb->prefix . 'statpress_equipment'; $table_equipment = $wpdb->prefix . 'statpress_equipment';
$create_lookup = function( $table_name ) use ( $wpdb ) { $create_lookup = function( $table_name ) use ( $wpdb ) {
$items = $wpdb->get_results( "SELECT id, name FROM {$table_name}" ); $items = $wpdb->get_results( "SELECT id, name FROM {$table_name}" );
$lookup = array(); $lookup = array();
if ( is_array( $items ) ) { if ( is_array( $items ) ) {
foreach ( $items as $item ) { foreach ( $items as $item ) {
// Use a robust trim to handle various whitespace characters and make it case-insensitive // 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 ); $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $item->name );
$lookup[ mb_strtolower( $clean_name, 'UTF-8' ) ] = $item->id; $lookup[ mb_strtolower( $clean_name, 'UTF-8' ) ] = $item->id;
} }
} }
return $lookup; return $lookup;
}; };
$categories_lookup = $create_lookup( $table_categories ); $categories_lookup = $create_lookup( $table_categories );
$event_types_lookup = $create_lookup( $table_event_types ); $event_types_lookup = $create_lookup( $table_event_types );
$equipment_lookup = $create_lookup( $table_equipment ); $equipment_lookup = $create_lookup( $table_equipment );
// --- END: Robust, case-insensitive lookup --- // --- END: Robust, case-insensitive lookup ---
// Process the CSV file // Process the CSV file
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$imported_count = 0; $imported_count = 0;
$skipped_details = array(); $skipped_details = array();
$row_number = 1; // Header is row 1 $row_number = 1; // Header is row 1
// Normalize line endings and split into lines // Normalize line endings and split into lines
$lines = str_replace( array( "\r\n", "\r" ), "\n", $csv_content ); $lines = str_replace( array( "\r\n", "\r" ), "\n", $csv_content );
$lines = explode( "\n", $lines ); $lines = explode( "\n", $lines );
if ( empty( $lines ) || empty( trim( $lines[0] ) ) ) { if ( empty( $lines ) || empty( trim( $lines[0] ) ) ) {
echo '<div class="notice notice-info"><p>Podane dane CSV są puste.</p></div>'; echo '<div class="notice notice-info"><p>Podane dane CSV są puste.</p></div>';
return; return;
} }
// --- Delimiter and BOM detection --- // --- Delimiter and BOM detection ---
$first_line = $lines[0]; $first_line = $lines[0];
$delimiter = ( substr_count( $first_line, ';' ) > substr_count( $first_line, ',' ) ) ? ';' : ','; $delimiter = ( substr_count( $first_line, ';' ) > substr_count( $first_line, ',' ) ) ? ';' : ',';
// --- BOM removal from first header element --- // --- BOM removal from first header element ---
$bom = "\xEF\xBB\xBF"; $bom = "\xEF\xBB\xBF";
if ( substr( $first_line, 0, 3 ) === $bom ) { if ( substr( $first_line, 0, 3 ) === $bom ) {
$lines[0] = substr( $first_line, 3 ); $lines[0] = substr( $first_line, 3 );
} }
$header_raw = str_getcsv( array_shift( $lines ), $delimiter ); $header_raw = str_getcsv( array_shift( $lines ), $delimiter );
$header_raw = array_map( 'trim', $header_raw ); $header_raw = array_map( 'trim', $header_raw );
// Translate header from Polish/English to internal keys // Translate header from Polish/English to internal keys
$header = array(); $header = array();
foreach ( $header_raw as $col ) { foreach ( $header_raw as $col ) {
$header[] = $column_map[ strtolower( $col ) ] ?? 'ignored_' . uniqid(); $header[] = $column_map[ strtolower( $col ) ] ?? 'ignored_' . uniqid();
} }
$required_internal_keys = array( 'date', 'title', 'category_name', 'distance' ); $required_internal_keys = array( 'date', 'title', 'category_name', 'distance' );
$missing_keys = array_diff( $required_internal_keys, $header ); $missing_keys = array_diff( $required_internal_keys, $header );
if ( ! empty( $missing_keys ) ) { if ( ! empty( $missing_keys ) ) {
$key_to_polish_map = array( $key_to_polish_map = array(
'date' => 'Data', 'date' => 'Data',
'title' => 'Tytuł', 'title' => 'Tytuł',
'category_name' => 'Kategoria / Typ aktywności', 'category_name' => 'Kategoria / Typ aktywności',
'distance' => 'Dystans', 'distance' => 'Dystans',
); );
$missing_polish_names = array_map( fn( $key ) => $key_to_polish_map[ $key ] ?? $key, $missing_keys ); $missing_polish_names = array_map( fn( $key ) => $key_to_polish_map[ $key ] ?? $key, $missing_keys );
echo '<div class="notice notice-error"><p>Brak wymaganych kolumn w danych CSV: ' . esc_html( implode( ', ', $missing_polish_names ) ) . '</p></div>'; echo '<div class="notice notice-error"><p>Brak wymaganych kolumn w danych CSV: ' . esc_html( implode( ', ', $missing_polish_names ) ) . '</p></div>';
return; return;
} }
$parse_and_round_int = fn( $val ) => round( floatval( str_replace( ',', '.', $val ) ) ); $parse_and_round_int = fn( $val ) => round( floatval( str_replace( ',', '.', $val ) ) );
$null_if_empty = fn( $value ) => '' !== $value ? $value : null; $null_if_empty = fn( $value ) => '' !== $value ? $value : null;
foreach ( $lines as $line ) { foreach ( $lines as $line ) {
$row_number++; $row_number++;
if ( empty( trim( $line ) ) ) { if ( empty( trim( $line ) ) ) {
continue; // Skip empty lines continue; // Skip empty lines
} }
$data = str_getcsv( $line, $delimiter ); $data = str_getcsv( $line, $delimiter );
if ( count( $data ) !== count( $header ) ) { if ( count( $data ) !== count( $header ) ) {
$skipped_details[] = array( $skipped_details[] = array(
'row' => $row_number, 'row' => $row_number,
'reason' => 'Nieprawidłowa liczba kolumn (oczekiwano ' . count( $header ) . ', otrzymano ' . count( $data ) . ').', 'reason' => 'Nieprawidłowa liczba kolumn (oczekiwano ' . count( $header ) . ', otrzymano ' . count( $data ) . ').',
'data' => $line, 'data' => $line,
); );
continue; continue;
} }
$row_data = array_combine( $header, $data ); $row_data = array_combine( $header, $data );
// Detailed validation // Detailed validation
$validation_errors = array(); $validation_errors = array();
if ( empty( $row_data['date'] ) ) { if ( empty( $row_data['date'] ) ) {
$validation_errors[] = 'brak daty'; $validation_errors[] = 'brak daty';
} }
if ( empty( $row_data['title'] ) ) { if ( empty( $row_data['title'] ) ) {
$validation_errors[] = 'brak tytułu'; $validation_errors[] = 'brak tytułu';
} }
if ( ! isset( $row_data['distance'] ) || '' === $row_data['distance'] ) { if ( ! isset( $row_data['distance'] ) || '' === $row_data['distance'] ) {
$validation_errors[] = 'brak dystansu'; $validation_errors[] = 'brak dystansu';
} }
$category_name = $row_data['category_name'] ?? ''; $category_name = $row_data['category_name'] ?? '';
$clean_category_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $category_name ); $clean_category_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $category_name );
$category_id = $categories_lookup[ mb_strtolower( $clean_category_name, 'UTF-8' ) ] ?? null; $category_id = $categories_lookup[ mb_strtolower( $clean_category_name, 'UTF-8' ) ] ?? null;
if ( empty( $clean_category_name ) ) { if ( empty( $clean_category_name ) ) {
$validation_errors[] = 'brak nazwy kategorii'; $validation_errors[] = 'brak nazwy kategorii';
} elseif ( is_null( $category_id ) ) { } elseif ( is_null( $category_id ) ) {
$available_categories_from_db = $wpdb->get_col( "SELECT name FROM $table_categories ORDER BY name" ); $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 ) ) . '".'; $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 ) ) { if ( ! empty( $validation_errors ) ) {
$skipped_details[] = array( $skipped_details[] = array(
'row' => $row_number, 'row' => $row_number,
'reason' => ucfirst( implode( ', ', $validation_errors ) ) . '.', 'reason' => ucfirst( implode( ', ', $validation_errors ) ) . '.',
'data' => $line, 'data' => $line,
); );
continue; continue;
} }
// Get IDs for optional fields using the same robust method // Get IDs for optional fields using the same robust method
$get_id = function( $name, $lookup_table ) { $get_id = function( $name, $lookup_table ) {
$clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $name ); $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $name );
return $lookup_table[ mb_strtolower( $clean_name, 'UTF-8' ) ] ?? null; return $lookup_table[ mb_strtolower( $clean_name, 'UTF-8' ) ] ?? null;
}; };
$equipment_id = $get_id( $row_data['equipment_name'] ?? '', $equipment_lookup ); $equipment_id = $get_id( $row_data['equipment_name'] ?? '', $equipment_lookup );
$event_type_id = $get_id( $row_data['event_type_name'] ?? '', $event_types_lookup ); $event_type_id = $get_id( $row_data['event_type_name'] ?? '', $event_types_lookup );
$insert_data = array( $insert_data = array(
'date' => sanitize_text_field( $row_data['date'] ), 'date' => sanitize_text_field( $row_data['date'] ),
'title' => sanitize_text_field( $row_data['title'] ), 'title' => sanitize_text_field( $row_data['title'] ),
'category_id' => $category_id, 'category_id' => $category_id,
'distance' => floatval( str_replace( ',', '.', $row_data['distance'] ) ), 'distance' => floatval( str_replace( ',', '.', $row_data['distance'] ) ),
'duration' => isset( $row_data['duration'] ) ? sanitize_text_field( $row_data['duration'] ) : '00:00:00', '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, 'calories' => isset( $row_data['calories'] ) ? intval( str_replace( ',', '.', $row_data['calories'] ) ) : 0,
'comment' => isset( $row_data['comment'] ) ? sanitize_textarea_field( $row_data['comment'] ) : null, '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, '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, '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, '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, '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, '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, '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, '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_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, '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, '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, 'max_altitude' => isset( $row_data['max_altitude'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_altitude'] ) ) : null,
'equipment_id' => $equipment_id, 'equipment_id' => $equipment_id,
'event_type_id' => $event_type_id, 'event_type_id' => $event_type_id,
); );
if ( $wpdb->insert( $table_activities, $insert_data ) ) { if ( $wpdb->insert( $table_activities, $insert_data ) ) {
$imported_count++; $imported_count++;
} else { } else {
$skipped_details[] = array( $skipped_details[] = array(
'row' => $row_number, 'row' => $row_number,
'reason' => 'Błąd zapisu do bazy danych. (' . esc_html( $wpdb->last_error ) . ')', 'reason' => 'Błąd zapisu do bazy danych. (' . esc_html( $wpdb->last_error ) . ')',
'data' => $line, 'data' => $line,
); );
} }
} }
if ( $imported_count > 0 ) { if ( $imported_count > 0 ) {
echo '<div class="notice notice-success is-dismissible"><p>Pomyślnie zaimportowano ' . esc_html( $imported_count ) . ' aktywności.</p></div>';} echo '<div class="notice notice-success is-dismissible"><p>Pomyślnie zaimportowano ' . esc_html( $imported_count ) . ' aktywności.</p></div>';}
if ( ! empty( $skipped_details ) ) { if ( ! empty( $skipped_details ) ) {
echo '<div class="notice notice-warning">'; echo '<div class="notice notice-warning">';
echo '<h4>Pominięto ' . count( $skipped_details ) . ' wierszy z powodu błędów</h4>'; echo '<h4>Pominięto ' . count( $skipped_details ) . ' wierszy z powodu błędów</h4>';
echo '<div style="max-height: 300px; overflow-y: auto; border: 1px solid #c3c4c7; padding: 10px; background: #fff; margin-top: 10px; font-size: 12px;">'; echo '<div style="max-height: 300px; overflow-y: auto; border: 1px solid #c3c4c7; padding: 10px; background: #fff; margin-top: 10px; font-size: 12px;">';
echo '<table class="wp-list-table widefat striped" style="margin:0;"><thead><tr><th style="width:80px">Wiersz</th><th>Powód pominięcia</th><th>Dane wiersza</th></tr></thead><tbody>'; echo '<table class="wp-list-table widefat striped" style="margin:0;"><thead><tr><th style="width:80px">Wiersz</th><th>Powód pominięcia</th><th>Dane wiersza</th></tr></thead><tbody>';
foreach ( $skipped_details as $error ) { foreach ( $skipped_details as $error ) {
echo '<tr>'; echo '<tr>';
echo '<td>' . esc_html( $error['row'] ) . '</td>'; echo '<td>' . esc_html( $error['row'] ) . '</td>';
echo '<td>' . esc_html( $error['reason'] ) . '</td>'; echo '<td>' . esc_html( $error['reason'] ) . '</td>';
echo '<td><small>' . esc_html( wp_trim_words( $error['data'], 25, '...' ) ) . '</small></td>'; echo '<td><small>' . esc_html( wp_trim_words( $error['data'], 25, '...' ) ) . '</small></td>';
echo '</tr>'; echo '</tr>';
} }
echo '</tbody></table>'; echo '</tbody></table>';
echo '</div></div>'; echo '</div></div>';
} }
if ( 0 === $imported_count && empty( $skipped_details ) && $row_number > 1 ) { if ( 0 === $imported_count && empty( $skipped_details ) && $row_number > 1 ) {
echo '<div class="notice notice-info"><p>Dane CSV nie zawierały żadnych poprawnych wierszy do importu.</p></div>'; echo '<div class="notice notice-info"><p>Dane CSV nie zawierały żadnych poprawnych wierszy do importu.</p></div>';
} elseif ( 1 === $row_number ) { } elseif ( 1 === $row_number ) {
echo '<div class="notice notice-info"><p>Dane CSV były puste lub zawierały tylko nagłówek.</p></div>';} echo '<div class="notice notice-info"><p>Dane CSV były puste lub zawierały tylko nagłówek.</p></div>';}
} }
+158 -158
View File
@@ -1,159 +1,159 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_infographic_page() { function statpress_infographic_page() {
global $wpdb; global $wpdb;
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$table_categories = $wpdb->prefix . 'statpress_categories'; $table_categories = $wpdb->prefix . 'statpress_categories';
$current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' ); $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' );
// Pobierz dostępne lata z bazy danych // Pobierz dostępne lata z bazy danych
$available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" ); $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" );
if ( empty( $available_years ) ) { if ( empty( $available_years ) ) {
$available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych $available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych
} }
// --- 1. Statystyki ogólne (wszystkie lata) --- // --- 1. Statystyki ogólne (wszystkie lata) ---
$overall_stats = $wpdb->get_row( $overall_stats = $wpdb->get_row(
" "
SELECT SELECT
SUM(distance) as total_distance, SUM(distance) as total_distance,
SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration, SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration,
SUM(total_elevation_gain) as total_elevation_gain, SUM(total_elevation_gain) as total_elevation_gain,
COUNT(id) as total_activities COUNT(id) as total_activities
FROM $table_activities FROM $table_activities
" "
); );
// --- 2. Statystyki dla wybranego roku --- // --- 2. Statystyki dla wybranego roku ---
$yearly_stats = $wpdb->get_row( $yearly_stats = $wpdb->get_row(
$wpdb->prepare( $wpdb->prepare(
" "
SELECT SELECT
SUM(distance) as total_distance, SUM(distance) as total_distance,
SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration, SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration,
SUM(total_elevation_gain) as total_elevation_gain, SUM(total_elevation_gain) as total_elevation_gain,
COUNT(id) as total_activities COUNT(id) as total_activities
FROM $table_activities FROM $table_activities
WHERE YEAR(date) = %d WHERE YEAR(date) = %d
", ",
$current_year $current_year
) )
); );
// --- 3. Dane dla wykresu kołowego (dystans per kategoria dla wybranego roku) --- // --- 3. Dane dla wykresu kołowego (dystans per kategoria dla wybranego roku) ---
$category_distance_data = $wpdb->get_results( $category_distance_data = $wpdb->get_results(
$wpdb->prepare( $wpdb->prepare(
" "
SELECT c.name as category_name, c.color, SUM(a.distance) as total_distance SELECT c.name as category_name, c.color, SUM(a.distance) as total_distance
FROM $table_activities a FROM $table_activities a
LEFT JOIN $table_categories c ON a.category_id = c.id LEFT JOIN $table_categories c ON a.category_id = c.id
WHERE YEAR(a.date) = %d WHERE YEAR(a.date) = %d
GROUP BY c.name, c.color GROUP BY c.name, c.color
HAVING SUM(a.distance) > 0 HAVING SUM(a.distance) > 0
ORDER BY total_distance DESC ORDER BY total_distance DESC
", ",
$current_year $current_year
) )
); );
$chart_labels = array(); $chart_labels = array();
$chart_data = array(); $chart_data = array();
$chart_colors = array(); $chart_colors = array();
foreach ( $category_distance_data as $data ) { foreach ( $category_distance_data as $data ) {
$chart_labels[] = $data->category_name; $chart_labels[] = $data->category_name;
$chart_data[] = round( (float) $data->total_distance, 2 ); $chart_data[] = round( (float) $data->total_distance, 2 );
$chart_colors[] = $data->color; $chart_colors[] = $data->color;
} }
// Włączenie skryptów dla Chart.js // Włączenie skryptów dla Chart.js
wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true ); wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
wp_register_script( 'statpress-infographic-chart-loader', false ); wp_register_script( 'statpress-infographic-chart-loader', false );
wp_enqueue_script( 'statpress-infographic-chart-loader' ); wp_enqueue_script( 'statpress-infographic-chart-loader' );
wp_add_inline_script( wp_add_inline_script(
'statpress-infographic-chart-loader', 'statpress-infographic-chart-loader',
' '
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const ctx = document.getElementById("statpressCategoryPieChart"); const ctx = document.getElementById("statpressCategoryPieChart");
if (!ctx) return; if (!ctx) return;
new Chart(ctx, { new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
labels: ' . json_encode( $chart_labels ) . ', labels: ' . json_encode( $chart_labels ) . ',
datasets: [{ datasets: [{
data: ' . json_encode( $chart_data ) . ', data: ' . json_encode( $chart_data ) . ',
backgroundColor: ' . json_encode( $chart_colors ) . ', backgroundColor: ' . json_encode( $chart_colors ) . ',
hoverOffset: 4 hoverOffset: 4
}] }]
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
plugins: { plugins: {
legend: { legend: {
position: "right", position: "right",
}, },
title: { title: {
display: true, display: true,
text: "Dystans wg kategorii w ' . esc_js( $current_year ) . '" text: "Dystans wg kategorii w ' . esc_js( $current_year ) . '"
} }
} }
} }
}); });
}); });
' '
); );
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Infografika Statystyk Sportowych</h1> <h1>Infografika Statystyk Sportowych</h1>
<div class="tablenav top"> <div class="tablenav top">
<div class="alignleft actions"> <div class="alignleft actions">
<form method="get" style="display: flex; gap: 5px; align-items: center;"> <form method="get" style="display: flex; gap: 5px; align-items: center;">
<input type="hidden" name="page" value="statpress-infographic"> <input type="hidden" name="page" value="statpress-infographic">
<label for="filter-by-year" class="screen-reader-text">Filtruj według roku</label> <label for="filter-by-year" class="screen-reader-text">Filtruj według roku</label>
<select name="year" id="filter-by-year"> <select name="year" id="filter-by-year">
<?php foreach ( $available_years as $year_option ) : ?> <?php foreach ( $available_years as $year_option ) : ?>
<option value="<?php echo esc_attr( $year_option ); ?>" <?php selected( $current_year, $year_option ); ?>><?php echo esc_html( $year_option ); ?></option> <option value="<?php echo esc_attr( $year_option ); ?>" <?php selected( $current_year, $year_option ); ?>><?php echo esc_html( $year_option ); ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<?php submit_button( 'Filtruj', 'secondary', 'filter_action', false ); ?> <?php submit_button( 'Filtruj', 'secondary', 'filter_action', false ); ?>
</form> </form>
</div> </div>
</div> </div>
<div class="postbox" style="margin-bottom: 20px;"> <div class="postbox" style="margin-bottom: 20px;">
<div class="postbox-header"><h2 class="hndle">Statystyki Ogólne (wszystkie lata)</h2></div> <div class="postbox-header"><h2 class="hndle">Statystyki Ogólne (wszystkie lata)</h2></div>
<div class="inside statpress-infographic-grid"> <div class="inside statpress-infographic-grid">
<div class="statpress-infographic-card"><h3>Dystans</h3><p><?php echo number_format( $overall_stats->total_distance, 2, ',', ' ' ); ?> km</p></div> <div class="statpress-infographic-card"><h3>Dystans</h3><p><?php echo number_format( $overall_stats->total_distance, 2, ',', ' ' ); ?> km</p></div>
<div class="statpress-infographic-card"><h3>Czas</h3><p><?php echo esc_html( $overall_stats->total_duration ); ?></p></div> <div class="statpress-infographic-card"><h3>Czas</h3><p><?php echo esc_html( $overall_stats->total_duration ); ?></p></div>
<div class="statpress-infographic-card"><h3>Wznios</h3><p><?php echo number_format( $overall_stats->total_elevation_gain, 0, ',', ' ' ); ?> m</p></div> <div class="statpress-infographic-card"><h3>Wznios</h3><p><?php echo number_format( $overall_stats->total_elevation_gain, 0, ',', ' ' ); ?> m</p></div>
<div class="statpress-infographic-card"><h3>Aktywności</h3><p><?php echo number_format( $overall_stats->total_activities, 0, ',', ' ' ); ?></p></div> <div class="statpress-infographic-card"><h3>Aktywności</h3><p><?php echo number_format( $overall_stats->total_activities, 0, ',', ' ' ); ?></p></div>
</div> </div>
</div> </div>
<div class="postbox" style="margin-bottom: 20px;"> <div class="postbox" style="margin-bottom: 20px;">
<div class="postbox-header"><h2 class="hndle">Statystyki dla <?php echo esc_html( $current_year ); ?></h2></div> <div class="postbox-header"><h2 class="hndle">Statystyki dla <?php echo esc_html( $current_year ); ?></h2></div>
<div class="inside statpress-infographic-grid"> <div class="inside statpress-infographic-grid">
<div class="statpress-infographic-card"><h3>Dystans</h3><p><?php echo number_format( $yearly_stats->total_distance, 2, ',', ' ' ); ?> km</p></div> <div class="statpress-infographic-card"><h3>Dystans</h3><p><?php echo number_format( $yearly_stats->total_distance, 2, ',', ' ' ); ?> km</p></div>
<div class="statpress-infographic-card"><h3>Czas</h3><p><?php echo esc_html( $yearly_stats->total_duration ); ?></p></div> <div class="statpress-infographic-card"><h3>Czas</h3><p><?php echo esc_html( $yearly_stats->total_duration ); ?></p></div>
<div class="statpress-infographic-card"><h3>Wznios</h3><p><?php echo number_format( $yearly_stats->total_elevation_gain, 0, ',', ' ' ); ?> m</p></div> <div class="statpress-infographic-card"><h3>Wznios</h3><p><?php echo number_format( $yearly_stats->total_elevation_gain, 0, ',', ' ' ); ?> m</p></div>
<div class="statpress-infographic-card"><h3>Aktywności</h3><p><?php echo number_format( $yearly_stats->total_activities, 0, ',', ' ' ); ?></p></div> <div class="statpress-infographic-card"><h3>Aktywności</h3><p><?php echo number_format( $yearly_stats->total_activities, 0, ',', ' ' ); ?></p></div>
</div> </div>
</div> </div>
<div class="postbox" style="margin-bottom: 20px;"> <div class="postbox" style="margin-bottom: 20px;">
<div class="postbox-header"><h2 class="hndle">Rozkład Dystansu wg Kategorii w <?php echo esc_html( $current_year ); ?></h2></div> <div class="postbox-header"><h2 class="hndle">Rozkład Dystansu wg Kategorii w <?php echo esc_html( $current_year ); ?></h2></div>
<div class="inside"> <div class="inside">
<div style="position: relative; height:40vh; width:100%; max-width: 600px; margin: 0 auto;"> <div style="position: relative; height:40vh; width:100%; max-width: 600px; margin: 0 auto;">
<canvas id="statpressCategoryPieChart"></canvas> <canvas id="statpressCategoryPieChart"></canvas>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<?php <?php
} }
+160 -137
View File
@@ -1,138 +1,161 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_settings_page() { function statpress_settings_page() {
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Ustawienia Wtyczki Statystyk</h1> <?php
<form method="post" action="options.php"> if ( get_transient( 'statpress_migration_reset_notice' ) ) {
<?php echo '<div class="notice notice-success is-dismissible"><p>Status migracji został zresetowany. Wróć do <a href="' . esc_url( admin_url( 'admin.php?page=statpress-dashboard' ) ) . '">głównego panelu</a>, aby ponownie uruchomić migrację.</p></div>';
settings_fields( 'statpress_privacy_settings' ); delete_transient( 'statpress_migration_reset_notice' );
do_settings_sections( 'statpress-privacy-section' ); }
settings_fields( 'statpress_api_settings' ); ?>
do_settings_sections( 'statpress-api-section' ); <h1>Ustawienia Wtyczki Statystyk</h1>
submit_button(); <form method="post" action="options.php">
?> <?php
</form> settings_fields( 'statpress_settings_group' );
</div> do_settings_sections( 'statpress_settings_page' );
<?php submit_button();
} ?>
</form>
function statpress_register_settings() {
register_setting( <hr>
'statpress_privacy_settings', <h2>Narzędzia deweloperskie</h2>
'statpress_privacy_options', <div class="postbox">
'statpress_sanitize_privacy_options' <div class="postbox-header"><h3 class="hndle">Resetowanie migracji</h3></div>
); <div class="inside">
<p>Jeśli migracja danych nie powiodła się, a przycisk do jej ponownego uruchomienia zniknął, możesz użyć tego narzędzia, aby zresetować status migracji. Po kliknięciu, przycisk w głównym panelu wtyczki pojawi się ponownie.</p>
add_settings_section( <form method="post">
'statpress_privacy_zone_section', <input type="hidden" name="statpress_action" value="reset_migration"><?php wp_nonce_field( 'statpress_reset_migration_nonce', '_wpnonce' ); ?><?php submit_button( 'Zresetuj status migracji', 'delete' ); ?></form>
'Strefa Prywatności GPX', </div>
'statpress_privacy_section_callback', </div>
'statpress-privacy-section'
); </div>
<?php
add_settings_field( }
'statpress_privacy_latitude',
'Szerokość geograficzna (Latitude)', function statpress_register_settings() {
'statpress_render_lat_field', // Define a single group and page for all settings on this form.
'statpress-privacy-section', $option_group = 'statpress_settings_group';
'statpress_privacy_zone_section' $page_slug = 'statpress_settings_page';
);
// Register Privacy settings under the main group.
add_settings_field( register_setting(
'statpress_privacy_longitude', $option_group,
'Długość geograficzna (Longitude)', 'statpress_privacy_options',
'statpress_render_lon_field', 'statpress_sanitize_privacy_options'
'statpress-privacy-section', );
'statpress_privacy_zone_section'
); // Register API settings under the same main group.
register_setting(
add_settings_field( $option_group,
'statpress_privacy_radius', 'statpress_api_options',
'Promień strefy (w metrach)', 'statpress_sanitize_api_options'
'statpress_render_radius_field', );
'statpress-privacy-section',
'statpress_privacy_zone_section' // Add the Privacy section to the main page.
); add_settings_section(
'statpress_privacy_zone_section',
// API Settings 'Strefa Prywatności GPX',
register_setting( 'statpress_privacy_section_callback',
'statpress_api_settings', $page_slug
'statpress_api_options', );
'statpress_sanitize_api_options'
); add_settings_field(
'statpress_privacy_latitude',
add_settings_section( 'Szerokość geograficzna (Latitude)',
'statpress_api_section', 'statpress_render_lat_field',
'Ustawienia API', $page_slug,
'statpress_api_section_callback', 'statpress_privacy_zone_section'
'statpress-api-section' );
);
add_settings_field(
add_settings_field( 'statpress_privacy_longitude',
'statpress_enable_api', 'Długość geograficzna (Longitude)',
'REST API', 'statpress_render_lon_field',
'statpress_render_enable_api_field', $page_slug,
'statpress-privacy-section', 'statpress_privacy_zone_section'
'statpress_privacy_zone_section' );
);
} add_settings_field(
'statpress_privacy_radius',
function statpress_privacy_section_callback() { 'Promień strefy (w metrach)',
echo '<p>Zdefiniuj strefę prywatności, aby ukryć początek i koniec swoich tras GPX. Punkty wewnątrz tego okręgu nie będą wyświetlane na mapie.</p>'; 'statpress_render_radius_field',
echo '<p>Aby znaleźć swoje współrzędne, kliknij prawym przyciskiem myszy na mapie Google w wybranym miejscu - współrzędne pojawią się jako pierwsza pozycja w menu.</p>'; $page_slug,
} 'statpress_privacy_zone_section'
);
function statpress_render_lat_field() {
$options = get_option( 'statpress_privacy_options' ); // Add the API section to the same main page.
$latitude = isset( $options['latitude'] ) ? esc_attr( $options['latitude'] ) : ''; add_settings_section(
echo "<input type='text' name='statpress_privacy_options[latitude]' value='{$latitude}' placeholder='np. 52.2297' class='regular-text' />"; 'statpress_api_section',
} 'Ustawienia API',
'statpress_api_section_callback',
function statpress_render_lon_field() { $page_slug
$options = get_option( 'statpress_privacy_options' ); );
$longitude = isset( $options['longitude'] ) ? esc_attr( $options['longitude'] ) : '';
echo "<input type='text' name='statpress_privacy_options[longitude]' value='{$longitude}' placeholder='np. 21.0122' class='regular-text' />"; add_settings_field(
} 'statpress_enable_api',
'REST API',
function statpress_render_radius_field() { 'statpress_render_enable_api_field',
$options = get_option( 'statpress_privacy_options' ); $page_slug,
$radius = isset( $options['radius'] ) ? esc_attr( $options['radius'] ) : '500'; 'statpress_api_section'
echo "<input type='number' name='statpress_privacy_options[radius]' value='{$radius}' class='small-text' /> metrów"; );
} }
function statpress_sanitize_privacy_options( $input ) { function statpress_privacy_section_callback() {
$sanitized_input = array(); echo '<p>Zdefiniuj strefę prywatności, aby ukryć początek i koniec swoich tras GPX. Punkty wewnątrz tego okręgu nie będą wyświetlane na mapie.</p>';
if ( isset( $input['latitude'] ) ) { echo '<p>Aby znaleźć swoje współrzędne, kliknij prawym przyciskiem myszy na mapie Google w wybranym miejscu - współrzędne pojawią się jako pierwsza pozycja w menu.</p>';
$sanitized_input['latitude'] = floatval( str_replace( ',', '.', $input['latitude'] ) ); }
}
if ( isset( $input['longitude'] ) ) { function statpress_render_lat_field() {
$sanitized_input['longitude'] = floatval( str_replace( ',', '.', $input['longitude'] ) ); $options = get_option( 'statpress_privacy_options' );
} $latitude = isset( $options['latitude'] ) ? esc_attr( $options['latitude'] ) : '';
if ( isset( $input['radius'] ) ) { echo "<input type='text' name='statpress_privacy_options[latitude]' value='{$latitude}' placeholder='np. 52.2297' class='regular-text' />";
$sanitized_input['radius'] = abs( intval( $input['radius'] ) ); }
}
return $sanitized_input; function statpress_render_lon_field() {
} $options = get_option( 'statpress_privacy_options' );
$longitude = isset( $options['longitude'] ) ? esc_attr( $options['longitude'] ) : '';
function statpress_api_section_callback() { echo "<input type='text' name='statpress_privacy_options[longitude]' value='{$longitude}' placeholder='np. 21.0122' class='regular-text' />";
echo '<p>Ustawienia związane z integracją wtyczki z zewnętrznymi aplikacjami, np. mobilnymi.</p>'; }
}
function statpress_render_radius_field() {
function statpress_render_enable_api_field() { $options = get_option( 'statpress_privacy_options' );
$options = get_option( 'statpress_api_options' ); $radius = isset( $options['radius'] ) ? esc_attr( $options['radius'] ) : '500';
$checked = isset( $options['enable_api'] ) && $options['enable_api'] ? 'checked' : ''; echo "<input type='number' name='statpress_privacy_options[radius]' value='{$radius}' class='small-text' /> metrów";
echo "<label><input type='checkbox' name='statpress_api_options[enable_api]' value='1' {$checked} /> Włącz REST API</label>"; }
echo '<p class="description">Umożliwia zewnętrznym aplikacjom (np. na Androida) komunikację z wtyczką w celu dodawania i odczytywania aktywności. Jeśli nie korzystasz z takich integracji, możesz to wyłączyć dla większego bezpieczeństwa.</p>';
} function statpress_sanitize_privacy_options( $input ) {
$sanitized_input = array();
function statpress_sanitize_api_options( $input ) { if ( isset( $input['latitude'] ) ) {
$sanitized_input = array(); $sanitized_input['latitude'] = floatval( str_replace( ',', '.', $input['latitude'] ) );
// If the checkbox is not checked, it won't be in the $input array. }
// So we check for its existence to determine if it's on or off. if ( isset( $input['longitude'] ) ) {
$sanitized_input['enable_api'] = isset( $input['enable_api'] ) ? 1 : 0; $sanitized_input['longitude'] = floatval( str_replace( ',', '.', $input['longitude'] ) );
return $sanitized_input; }
if ( isset( $input['radius'] ) ) {
$sanitized_input['radius'] = abs( intval( $input['radius'] ) );
}
return $sanitized_input;
}
function statpress_api_section_callback() {
echo '<p>Ustawienia związane z integracją wtyczki z zewnętrznymi aplikacjami, np. mobilnymi.</p>';
}
function statpress_render_enable_api_field() {
$options = get_option( 'statpress_api_options' );
$checked = isset( $options['enable_api'] ) && $options['enable_api'] ? 'checked' : '';
echo "<label><input type='checkbox' name='statpress_api_options[enable_api]' value='1' {$checked} /> Włącz REST API</label>";
echo '<p class="description">Umożliwia zewnętrznym aplikacjom (np. na Androida) komunikację z wtyczką w celu dodawania i odczytywania aktywności. Jeśli nie korzystasz z takich integracji, możesz to wyłączyć dla większego bezpieczeństwa.</p>';
}
function statpress_sanitize_api_options( $input ) {
$sanitized_input = array();
// If the checkbox is not checked, it won't be in the $input array.
// So we check for its existence to determine if it's on or off.
$sanitized_input['enable_api'] = isset( $input['enable_api'] ) ? 1 : 0;
return $sanitized_input;
} }
+291 -291
View File
@@ -1,292 +1,292 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
function statpress_yearly_summary_page() { function statpress_yearly_summary_page() {
global $wpdb; global $wpdb;
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' ); $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' );
// Pobierz dostępne lata z bazy danych // Pobierz dostępne lata z bazy danych
$available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" ); $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" );
if ( empty( $available_years ) ) { if ( empty( $available_years ) ) {
$available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych $available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych
} }
// --- GOALS SECTION --- // --- GOALS SECTION ---
$table_goals = $wpdb->prefix . 'statpress_goals'; $table_goals = $wpdb->prefix . 'statpress_goals';
$goals_for_year = $wpdb->get_results( $goals_for_year = $wpdb->get_results(
$wpdb->prepare( $wpdb->prepare(
"SELECT * FROM {$table_goals} WHERE year = %d ORDER BY name ASC", "SELECT * FROM {$table_goals} WHERE year = %d ORDER BY name ASC",
$current_year $current_year
) )
); );
// Zapytanie SQL do grupowania danych miesięcznie // Zapytanie SQL do grupowania danych miesięcznie
$sql = $wpdb->prepare( $sql = $wpdb->prepare(
" "
SELECT SELECT
MONTH(date) as month_num, MONTH(date) as month_num,
SUM(distance) as total_distance, SUM(distance) as total_distance,
SUM(calories) as total_calories, SUM(calories) as total_calories,
SUM(TIME_TO_SEC(duration)) as total_seconds, SUM(TIME_TO_SEC(duration)) as total_seconds,
COUNT(id) as activity_count COUNT(id) as activity_count
FROM $table_activities FROM $table_activities
WHERE YEAR(date) = %d WHERE YEAR(date) = %d
GROUP BY month_num GROUP BY month_num
ORDER BY month_num ASC ORDER BY month_num ASC
", ",
$current_year $current_year
); );
$monthly_summary = $wpdb->get_results( $sql, OBJECT_K ); // OBJECT_K zwróci tablicę z kluczami będącymi numerami miesięcy $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 // Przygotowanie danych dla wszystkich 12 miesięcy
$full_year_summary = array(); $full_year_summary = array();
$total_year_distance = 0; $total_year_distance = 0;
$total_year_calories = 0; $total_year_calories = 0;
$total_year_seconds = 0; $total_year_seconds = 0;
// Określ, ile miesięcy pokazać, aby uniknąć zer dla przyszłych miesięcy // Określ, ile miesięcy pokazać, aby uniknąć zer dla przyszłych miesięcy
$this_year = (int) current_time( 'Y' ); $this_year = (int) current_time( 'Y' );
$this_month = (int) current_time( 'n' ); $this_month = (int) current_time( 'n' );
$loop_until_month = 12; // Domyślnie dla lat ubiegłych $loop_until_month = 12; // Domyślnie dla lat ubiegłych
if ( $current_year === $this_year ) { if ( $current_year === $this_year ) {
// Dla bieżącego roku, pokaż miesiące do bieżącego miesiąca // Dla bieżącego roku, pokaż miesiące do bieżącego miesiąca
$loop_until_month = $this_month; $loop_until_month = $this_month;
} elseif ( $current_year > $this_year ) { } elseif ( $current_year > $this_year ) {
// Dla przyszłych lat, pokaż miesiące tylko do ostatniego, w którym są dane // 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 ) ); $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; $loop_until_month = $last_month_with_data ? (int) $last_month_with_data : 0;
} }
for ( $i = 1; $i <= $loop_until_month; $i++ ) { for ( $i = 1; $i <= $loop_until_month; $i++ ) {
$month_name = date_i18n( 'F', mktime( 0, 0, 0, $i, 10 ) ); // Nazwa miesiąca $month_name = date_i18n( 'F', mktime( 0, 0, 0, $i, 10 ) ); // Nazwa miesiąca
$data = isset( $monthly_summary[ $i ] ) ? $monthly_summary[ $i ] : null; $data = isset( $monthly_summary[ $i ] ) ? $monthly_summary[ $i ] : null;
$full_year_summary[ $i ] = (object) array( $full_year_summary[ $i ] = (object) array(
'month_name' => $month_name, 'month_name' => $month_name,
'total_distance' => $data ? $data->total_distance : 0, 'total_distance' => $data ? $data->total_distance : 0,
'total_calories' => $data ? (int) $data->total_calories : 0, 'total_calories' => $data ? (int) $data->total_calories : 0,
'total_seconds' => $data ? (int) $data->total_seconds : 0, 'total_seconds' => $data ? (int) $data->total_seconds : 0,
'activity_count' => $data ? (int) $data->activity_count : 0, 'activity_count' => $data ? (int) $data->activity_count : 0,
); );
$total_year_distance += $full_year_summary[ $i ]->total_distance; $total_year_distance += $full_year_summary[ $i ]->total_distance;
$total_year_seconds += $full_year_summary[ $i ]->total_seconds; $total_year_seconds += $full_year_summary[ $i ]->total_seconds;
$total_year_calories += $full_year_summary[ $i ]->total_calories; $total_year_calories += $full_year_summary[ $i ]->total_calories;
} }
$total_year_hours = floor( $total_year_seconds / 3600 ); $total_year_hours = floor( $total_year_seconds / 3600 );
$total_year_minutes = floor( ( $total_year_seconds % 3600 ) / 60 ); $total_year_minutes = floor( ( $total_year_seconds % 3600 ) / 60 );
$total_year_duration_formatted = sprintf( '%d godz. %d min.', $total_year_hours, $total_year_minutes ); $total_year_duration_formatted = sprintf( '%d godz. %d min.', $total_year_hours, $total_year_minutes );
// Przygotowanie danych dla wykresu // Przygotowanie danych dla wykresu
$chart_labels_js = array(); $chart_labels_js = array();
$chart_datasets = array( $chart_datasets = array(
'distance' => array(), 'distance' => array(),
'duration' => array(), 'duration' => array(),
'calories' => array(), 'calories' => array(),
'activities' => array(), 'activities' => array(),
); );
foreach ( $full_year_summary as $month_data ) { foreach ( $full_year_summary as $month_data ) {
$chart_labels_js[] = $month_data->month_name; $chart_labels_js[] = $month_data->month_name;
$chart_datasets['distance'][] = round( (float) $month_data->total_distance, 2 ); $chart_datasets['distance'][] = round( (float) $month_data->total_distance, 2 );
$chart_datasets['duration'][] = round( $month_data->total_seconds / 3600, 2 ); // w godzinach $chart_datasets['duration'][] = round( $month_data->total_seconds / 3600, 2 ); // w godzinach
$chart_datasets['calories'][] = $month_data->total_calories; $chart_datasets['calories'][] = $month_data->total_calories;
$chart_datasets['activities'][] = $month_data->activity_count; $chart_datasets['activities'][] = $month_data->activity_count;
} }
// Włączenie skryptów dla Chart.js // Włączenie skryptów dla Chart.js
wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true ); wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
wp_register_script( 'statpress-chart-loader', false ); wp_register_script( 'statpress-chart-loader', false );
wp_enqueue_script( 'statpress-chart-loader' ); wp_enqueue_script( 'statpress-chart-loader' );
$chart_configs = array( $chart_configs = array(
'distance' => array( 'distance' => array(
'label' => 'Dystans (km)', 'label' => 'Dystans (km)',
'data' => $chart_datasets['distance'], 'data' => $chart_datasets['distance'],
'backgroundColor' => 'rgba(52, 152, 219, 0.5)', 'backgroundColor' => 'rgba(52, 152, 219, 0.5)',
'borderColor' => 'rgba(52, 152, 219, 1)', 'borderColor' => 'rgba(52, 152, 219, 1)',
'yAxisLabel' => 'Kilometry', 'yAxisLabel' => 'Kilometry',
), ),
'duration' => array( 'duration' => array(
'label' => 'Czas trwania (godz.)', 'label' => 'Czas trwania (godz.)',
'data' => $chart_datasets['duration'], 'data' => $chart_datasets['duration'],
'backgroundColor' => 'rgba(26, 188, 156, 0.5)', 'backgroundColor' => 'rgba(26, 188, 156, 0.5)',
'borderColor' => 'rgba(26, 188, 156, 1)', 'borderColor' => 'rgba(26, 188, 156, 1)',
'yAxisLabel' => 'Godziny', 'yAxisLabel' => 'Godziny',
), ),
'calories' => array( 'calories' => array(
'label' => 'Kalorie (kcal)', 'label' => 'Kalorie (kcal)',
'data' => $chart_datasets['calories'], 'data' => $chart_datasets['calories'],
'backgroundColor' => 'rgba(231, 76, 60, 0.5)', 'backgroundColor' => 'rgba(231, 76, 60, 0.5)',
'borderColor' => 'rgba(231, 76, 60, 1)', 'borderColor' => 'rgba(231, 76, 60, 1)',
'yAxisLabel' => 'kcal', 'yAxisLabel' => 'kcal',
), ),
'activities' => array( 'activities' => array(
'label' => 'Liczba aktywności', 'label' => 'Liczba aktywności',
'data' => $chart_datasets['activities'], 'data' => $chart_datasets['activities'],
'backgroundColor' => 'rgba(241, 196, 15, 0.5)', 'backgroundColor' => 'rgba(241, 196, 15, 0.5)',
'borderColor' => 'rgba(241, 196, 15, 1)', 'borderColor' => 'rgba(241, 196, 15, 1)',
'yAxisLabel' => 'Ilość', 'yAxisLabel' => 'Ilość',
), ),
); );
wp_add_inline_script( wp_add_inline_script(
'statpress-chart-loader', 'statpress-chart-loader',
' '
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const chartLabels = ' . json_encode( $chart_labels_js ) . '; const chartLabels = ' . json_encode( $chart_labels_js ) . ';
const chartConfigs = ' . json_encode( $chart_configs ) . '; const chartConfigs = ' . json_encode( $chart_configs ) . ';
let activeChart = null; let activeChart = null;
const tabs = document.querySelectorAll(".nav-tab"); const tabs = document.querySelectorAll(".nav-tab");
tabs.forEach(tab => { tabs.forEach(tab => {
tab.addEventListener("click", function(e) { tab.addEventListener("click", function(e) {
e.preventDefault(); e.preventDefault();
tabs.forEach(t => t.classList.remove("nav-tab-active")); tabs.forEach(t => t.classList.remove("nav-tab-active"));
this.classList.add("nav-tab-active"); this.classList.add("nav-tab-active");
const chartType = this.getAttribute("href").substring(1); const chartType = this.getAttribute("href").substring(1);
renderChart(chartType); renderChart(chartType);
}); });
}); });
function renderChart(type) { function renderChart(type) {
if (activeChart) { if (activeChart) {
activeChart.destroy(); activeChart.destroy();
} }
const config = chartConfigs[type]; const config = chartConfigs[type];
const ctx = document.getElementById("statpressYearlyChart").getContext("2d"); const ctx = document.getElementById("statpressYearlyChart").getContext("2d");
activeChart = new Chart(ctx, { activeChart = new Chart(ctx, {
type: "bar", type: "bar",
data: { data: {
labels: chartLabels, labels: chartLabels,
datasets: [{ datasets: [{
label: config.label, label: config.label,
data: config.data, data: config.data,
backgroundColor: config.backgroundColor, backgroundColor: config.backgroundColor,
borderColor: config.borderColor, borderColor: config.borderColor,
borderWidth: 1 borderWidth: 1
}] }]
}, },
options: { options: {
responsive: true, maintainAspectRatio: false, responsive: true, maintainAspectRatio: false,
scales: { y: { beginAtZero: true, title: { display: true, text: config.yAxisLabel } } } scales: { y: { beginAtZero: true, title: { display: true, text: config.yAxisLabel } } }
} }
}); });
} }
// Render initial chart // Render initial chart
if (tabs.length > 0) { if (tabs.length > 0) {
renderChart(tabs[0].getAttribute("href").substring(1)); renderChart(tabs[0].getAttribute("href").substring(1));
} }
}); });
' '
); );
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Podsumowanie Roczne</h1> <h1>Podsumowanie Roczne</h1>
<div class="tablenav top"> <div class="tablenav top">
<div class="alignleft actions"> <div class="alignleft actions">
<label for="filter-by-year" class="screen-reader-text">Filtruj według roku</label> <label for="filter-by-year" class="screen-reader-text">Filtruj według roku</label>
<form method="get" style="display: flex; gap: 5px; align-items: center;"> <form method="get" style="display: flex; gap: 5px; align-items: center;">
<input type="hidden" name="page" value="statpress-yearly-summary"> <input type="hidden" name="page" value="statpress-yearly-summary">
<select name="year" id="filter-by-year"> <select name="year" id="filter-by-year">
<?php foreach ( $available_years as $year_option ) : ?> <?php foreach ( $available_years as $year_option ) : ?>
<option value="<?php echo esc_attr( $year_option ); ?>" <?php selected( $current_year, $year_option ); ?>><?php echo esc_html( $year_option ); ?></option> <option value="<?php echo esc_attr( $year_option ); ?>" <?php selected( $current_year, $year_option ); ?>><?php echo esc_html( $year_option ); ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<?php submit_button( 'Filtruj', 'secondary', 'filter_action', false ); ?> <?php submit_button( 'Filtruj', 'secondary', 'filter_action', false ); ?>
</form> </form>
</div> </div>
</div> </div>
<?php if ( ! empty( $goals_for_year ) ) : ?> <?php if ( ! empty( $goals_for_year ) ) : ?>
<div class="postbox" style="margin-bottom: 20px;"> <div class="postbox" style="margin-bottom: 20px;">
<div class="postbox-header"><h2 class="hndle">Cele na <?php echo esc_html( $current_year ); ?></h2></div> <div class="postbox-header"><h2 class="hndle">Cele na <?php echo esc_html( $current_year ); ?></h2></div>
<div class="inside"> <div class="inside">
<ul class="statpress-goals-list"> <ul class="statpress-goals-list">
<?php <?php
foreach ( $goals_for_year as $goal ) : foreach ( $goals_for_year as $goal ) :
$progress = statpress_get_goal_progress( $goal ); $progress = statpress_get_goal_progress( $goal );
$percentage = min( 100, $progress['percentage'] ); $percentage = min( 100, $progress['percentage'] );
$target_formatted = ''; $target_formatted = '';
$current_formatted = ''; $current_formatted = '';
if ( 'duration_sec' === $goal->goal_type ) { if ( 'duration_sec' === $goal->goal_type ) {
$target_formatted = round( $goal->target_value / 3600 ) . ' godz.'; $target_formatted = round( $goal->target_value / 3600 ) . ' godz.';
$current_formatted = sprintf( '%d godz. %d min.', floor( $progress['current_value'] / 3600 ), floor( ( $progress['current_value'] % 3600 ) / 60 ) ); $current_formatted = sprintf( '%d godz. %d min.', floor( $progress['current_value'] / 3600 ), floor( ( $progress['current_value'] % 3600 ) / 60 ) );
} elseif ( 'distance' === $goal->goal_type ) { } elseif ( 'distance' === $goal->goal_type ) {
$target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km'; $target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km';
$current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km'; $current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km';
} else { // count } else { // count
$target_formatted = (int) $goal->target_value; $target_formatted = (int) $goal->target_value;
$current_formatted = (int) $progress['current_value']; $current_formatted = (int) $progress['current_value'];
} }
?> ?>
<li class="statpress-goal-item"> <li class="statpress-goal-item">
<div class="statpress-goal-info"> <div class="statpress-goal-info">
<strong><?php echo esc_html( $goal->name ); ?></strong> <strong><?php echo esc_html( $goal->name ); ?></strong>
<small><?php echo $current_formatted; ?> / <?php echo $target_formatted; ?> (<?php echo round( $percentage, 1 ); ?>%)</small> <small><?php echo $current_formatted; ?> / <?php echo $target_formatted; ?> (<?php echo round( $percentage, 1 ); ?>%)</small>
</div> </div>
<div class="statpress-progress-bar-container"> <div class="statpress-progress-bar-container">
<div class="statpress-progress-bar" style="width: <?php echo esc_attr( $percentage ); ?>%;"></div> <div class="statpress-progress-bar" style="width: <?php echo esc_attr( $percentage ); ?>%;"></div>
</div> </div>
</li> </li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
</div> </div>
</div> </div>
<style> <style>
.statpress-goals-list { list-style: none; margin: 0; padding: 0; } .statpress-goal-item { margin-bottom: 15px; } .statpress-goal-info { display: flex; justify-content: space-between; margin-bottom: 5px; } .statpress-goals-list { list-style: none; margin: 0; padding: 0; } .statpress-goal-item { margin-bottom: 15px; } .statpress-goal-info { display: flex; justify-content: space-between; margin-bottom: 5px; }
.statpress-progress-bar-container { background: #eee; border-radius: 4px; height: 18px; width: 100%; overflow: hidden; } .statpress-progress-bar { background: #3498db; height: 100%; border-radius: 4px; transition: width 0.5s ease-in-out; text-align: center; color: white; font-size: 11px; line-height: 18px; } .statpress-progress-bar-container { background: #eee; border-radius: 4px; height: 18px; width: 100%; overflow: hidden; } .statpress-progress-bar { background: #3498db; height: 100%; border-radius: 4px; transition: width 0.5s ease-in-out; text-align: center; color: white; font-size: 11px; line-height: 18px; }
</style> </style>
<?php endif; ?> <?php endif; ?>
<div class="postbox" style="margin-bottom: 20px;"> <div class="postbox" style="margin-bottom: 20px;">
<div class="postbox-header"><h2 class="hndle">Wykresy dla <?php echo esc_html( $current_year ); ?></h2></div> <div class="postbox-header"><h2 class="hndle">Wykresy dla <?php echo esc_html( $current_year ); ?></h2></div>
<div class="inside"> <div class="inside">
<nav class="nav-tab-wrapper" style="margin-bottom: 20px;"> <nav class="nav-tab-wrapper" style="margin-bottom: 20px;">
<a href="#distance" class="nav-tab nav-tab-active">Dystans</a> <a href="#distance" class="nav-tab nav-tab-active">Dystans</a>
<a href="#duration" class="nav-tab">Czas</a> <a href="#duration" class="nav-tab">Czas</a>
<a href="#calories" class="nav-tab">Kalorie</a> <a href="#calories" class="nav-tab">Kalorie</a>
<a href="#activities" class="nav-tab">Aktywności</a> <a href="#activities" class="nav-tab">Aktywności</a>
</nav> </nav>
<div style="position: relative; height:40vh; width:100%;"> <div style="position: relative; height:40vh; width:100%;">
<canvas id="statpressYearlyChart"></canvas> <canvas id="statpressYearlyChart"></canvas>
</div> </div>
</div> </div>
</div> </div>
<table class="wp-list-table widefat fixed striped"> <table class="wp-list-table widefat fixed striped">
<thead> <thead>
<tr><th>Miesiąc</th><th>Dystans (km)</th><th>Kalorie (kcal)</th><th>Czas</th></tr> <tr><th>Miesiąc</th><th>Dystans (km)</th><th>Kalorie (kcal)</th><th>Czas</th></tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ( $full_year_summary as $month_data ) : ?> <?php foreach ( $full_year_summary as $month_data ) : ?>
<tr> <tr>
<td><?php echo esc_html( $month_data->month_name ); ?></td> <td><?php echo esc_html( $month_data->month_name ); ?></td>
<td><?php echo number_format( $month_data->total_distance, 2, ',', ' ' ); ?></td> <td><?php echo number_format( $month_data->total_distance, 2, ',', ' ' ); ?></td>
<td><?php echo number_format( $month_data->total_calories, 0, ',', ' ' ); ?></td> <td><?php echo number_format( $month_data->total_calories, 0, ',', ' ' ); ?></td>
<td><?php echo esc_html( gmdate( 'H:i:s', $month_data->total_seconds ) ); ?></td> <td><?php echo esc_html( gmdate( 'H:i:s', $month_data->total_seconds ) ); ?></td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<tr class="alternate"> <tr class="alternate">
<th>SUMA ROCZNA</th> <th>SUMA ROCZNA</th>
<th><?php echo number_format( $total_year_distance, 2, ',', ' ' ); ?></th> <th><?php echo number_format( $total_year_distance, 2, ',', ' ' ); ?></th>
<th><?php echo number_format( $total_year_calories, 0, ',', ' ' ); ?></th> <th><?php echo number_format( $total_year_calories, 0, ',', ' ' ); ?></th>
<th><?php echo esc_html( $total_year_duration_formatted ); ?></th> <th><?php echo esc_html( $total_year_duration_formatted ); ?></th>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<?php <?php
} }
+242 -181
View File
@@ -1,182 +1,243 @@
<?php <?php
/** /**
* REST API routes for the plugin. * REST API routes for the plugin.
* *
* @package WordPress Activity Stats * @package WordPress Activity Stats
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly. exit; // Exit if accessed directly.
} }
/** /**
* Register REST API routes. * Register REST API routes.
*/ */
function statpress_register_rest_routes() { function statpress_register_rest_routes() {
$namespace = 'statpress/v1'; $namespace = 'statpress/v1';
// Route for getting a collection of activities // Route for getting a collection of activities
register_rest_route( register_rest_route(
$namespace, $namespace,
'/activities', '/activities',
array( array(
array( array(
'methods' => WP_REST_Server::READABLE, 'methods' => WP_REST_Server::READABLE,
'callback' => 'statpress_get_activities_api', 'callback' => 'statpress_get_activities_api',
'permission_callback' => 'statpress_api_permissions_check', 'permission_callback' => 'statpress_api_permissions_check',
'args' => array( 'args' => array(
'page' => array( 'page' => array(
'validate_callback' => 'is_numeric', 'validate_callback' => 'is_numeric',
), ),
'per_page' => array( 'per_page' => array(
'validate_callback' => 'is_numeric', 'validate_callback' => 'is_numeric',
), ),
), ),
), ),
array( array(
'methods' => WP_REST_Server::CREATABLE, 'methods' => WP_REST_Server::CREATABLE,
'callback' => 'statpress_create_activity_api', 'callback' => 'statpress_create_activity_api',
'permission_callback' => 'statpress_api_permissions_check', 'permission_callback' => 'statpress_api_permissions_check',
), ),
) )
); );
// Route for a single activity // Route for a single activity
register_rest_route( register_rest_route(
$namespace, $namespace,
'/activities/(?P<id>[\d]+)', '/activities/(?P<id>[\d]+)',
array( array(
array( array(
'methods' => WP_REST_Server::READABLE, 'methods' => WP_REST_Server::READABLE,
'callback' => 'statpress_get_activity_api', 'callback' => 'statpress_get_activity_api',
'permission_callback' => 'statpress_api_permissions_check', 'permission_callback' => 'statpress_api_permissions_check',
), ),
array( array(
'methods' => WP_REST_Server::EDITABLE, 'methods' => WP_REST_Server::EDITABLE,
'callback' => 'statpress_update_activity_api', 'callback' => 'statpress_update_activity_api',
'permission_callback' => 'statpress_api_permissions_check', 'permission_callback' => 'statpress_api_permissions_check',
), ),
array( array(
'methods' => WP_REST_Server::DELETABLE, 'methods' => WP_REST_Server::DELETABLE,
'callback' => 'statpress_delete_activity_api', 'callback' => 'statpress_delete_activity_api',
'permission_callback' => 'statpress_api_permissions_check', 'permission_callback' => 'statpress_api_permissions_check',
), ),
) )
); );
}
// Route for parsing GPX file summary
/** register_rest_route(
* Permission check for API endpoints. $namespace,
* '/gpx/parse-summary',
* @return bool array(
*/ 'methods' => WP_REST_Server::CREATABLE, // Use POST to send URL in the body
function statpress_api_permissions_check() { 'callback' => 'statpress_parse_gpx_summary_api',
return current_user_can( 'manage_options' ); 'permission_callback' => 'statpress_api_permissions_check',
} 'args' => array(
'gpx_url' => array(
/** 'required' => true,
* Get a collection of activities. 'validate_callback' => 'esc_url_raw',
* ),
* @param WP_REST_Request $request Full details about the request. ),
* @return WP_REST_Response|WP_Error )
*/ );
function statpress_get_activities_api( WP_REST_Request $request ) { }
global $wpdb;
$table_activities = $wpdb->prefix . 'statpress_activities'; /**
* Permission check for API endpoints.
$per_page = $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : 20; *
$page = $request->get_param( 'page' ) ? (int) $request->get_param( 'page' ) : 1; * @return bool
$offset = ( $page - 1 ) * $per_page; */
function statpress_api_permissions_check() {
$sql = $wpdb->prepare( return current_user_can( 'manage_options' );
"SELECT a.*, c.name as category_name, 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 * Get a collection of activities.
LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id *
ORDER BY a.date DESC, a.id DESC * @param WP_REST_Request $request Full details about the request.
LIMIT %d OFFSET %d", * @return WP_REST_Response|WP_Error
$per_page, */
$offset function statpress_get_activities_api( WP_REST_Request $request ) {
); global $wpdb;
$table_activities = $wpdb->prefix . 'statpress_activities';
$results = $wpdb->get_results( $sql );
$per_page = $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : 20;
return new WP_REST_Response( $results, 200 ); $page = $request->get_param( 'page' ) ? (int) $request->get_param( 'page' ) : 1;
} $offset = ( $page - 1 ) * $per_page;
/** // Get total items for pagination headers
* Get a single activity. $total_items = (int) $wpdb->get_var( "SELECT COUNT(id) FROM $table_activities" );
* $total_pages = ceil( $total_items / $per_page );
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error $sql = $wpdb->prepare(
*/ "SELECT a.*, c.name as category_name, et.name as event_type_name, eq.name as equipment_name
function statpress_get_activity_api( WP_REST_Request $request ) { FROM $table_activities a
global $wpdb; LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
$id = (int) $request['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
$sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}statpress_activities WHERE id = %d", $id ); ORDER BY a.date DESC, a.id DESC
$activity = $wpdb->get_row( $sql ); LIMIT %d OFFSET %d",
$per_page,
if ( ! $activity ) { $offset
return new WP_Error( 'not_found', 'Activity not found', array( 'status' => 404 ) ); );
}
$results = $wpdb->get_results( $sql );
return new WP_REST_Response( $activity, 200 );
} $response = new WP_REST_Response( $results, 200 );
$response->header( 'X-WP-Total', $total_items );
/** $response->header( 'X-WP-TotalPages', $total_pages );
* Create a new activity.
* return $response;
* @param WP_REST_Request $request Full details about the request. }
* @return WP_REST_Response|WP_Error
*/ /**
function statpress_create_activity_api( WP_REST_Request $request ) { * Get a single activity.
$params = $request->get_json_params(); *
$activity_id = statpress_save_activity_data( $params ); * @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
if ( ! $activity_id ) { */
return new WP_Error( 'cant-create', 'Error creating activity', array( 'status' => 500 ) ); function statpress_get_activity_api( WP_REST_Request $request ) {
} global $wpdb;
$id = (int) $request['id'];
$response = statpress_get_activity_api( new WP_REST_Request( 'GET', "/statpress/v1/activities/{$activity_id}" ) );
$response->set_status( 201 ); // 201 Created // Use the same rich query as the collection endpoint for consistency.
return $response; $sql = $wpdb->prepare(
} "SELECT a.*, c.name as category_name, et.name as event_type_name, eq.name as equipment_name
FROM {$wpdb->prefix}statpress_activities a
/** LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
* Update an existing activity. 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
* @param WP_REST_Request $request Full details about the request. WHERE a.id = %d",
* @return WP_REST_Response|WP_Error $id
*/ );
function statpress_update_activity_api( WP_REST_Request $request ) { $activity = $wpdb->get_row( $sql );
$id = (int) $request['id'];
$params = $request->get_json_params(); if ( ! $activity ) {
$activity_id = statpress_save_activity_data( $params, $id ); return new WP_Error( 'not_found', 'Activity not found', array( 'status' => 404 ) );
}
if ( ! $activity_id ) {
return new WP_Error( 'cant-update', 'Error updating activity', array( 'status' => 500 ) ); return new WP_REST_Response( $activity, 200 );
} }
return statpress_get_activity_api( $request ); /**
} * Create a new activity.
*
/** * @param WP_REST_Request $request Full details about the request.
* Delete an activity. * @return WP_REST_Response|WP_Error
* */
* @param WP_REST_Request $request Full details about the request. function statpress_create_activity_api( WP_REST_Request $request ) {
* @return WP_REST_Response|WP_Error $params = $request->get_json_params();
*/ $activity_id = statpress_save_activity_data( $params );
function statpress_delete_activity_api( WP_REST_Request $request ) {
global $wpdb; if ( ! $activity_id ) {
$id = (int) $request['id']; return new WP_Error( 'cant-create', 'Error creating activity', array( 'status' => 500 ) );
}
$result = $wpdb->delete( $wpdb->prefix . 'statpress_activities', array( 'id' => $id ), array( '%d' ) );
// Create a new request object to fetch the newly created activity.
if ( ! $result ) { $new_request = new WP_REST_Request();
return new WP_Error( 'cant-delete', 'Error deleting activity', array( 'status' => 500 ) ); $new_request->set_param( 'id', $activity_id );
} $response = statpress_get_activity_api( $new_request );
$response->set_status( 201 ); // 201 Created
return new WP_REST_Response( array( 'message' => 'Activity deleted successfully.' ), 200 ); return $response;
}
/**
* Update an existing activity.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
function statpress_update_activity_api( WP_REST_Request $request ) {
$id = (int) $request['id'];
$params = $request->get_json_params();
$activity_id = statpress_save_activity_data( $params, $id );
if ( ! $activity_id ) {
return new WP_Error( 'cant-update', 'Error updating activity', array( 'status' => 500 ) );
}
return statpress_get_activity_api( $request );
}
/**
* Delete an activity.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
function statpress_delete_activity_api( WP_REST_Request $request ) {
global $wpdb;
$id = (int) $request['id'];
$result = $wpdb->delete( $wpdb->prefix . 'statpress_activities', array( 'id' => $id ), array( '%d' ) );
if ( ! $result ) {
return new WP_Error( 'cant-delete', 'Error deleting activity', array( 'status' => 500 ) );
}
return new WP_REST_Response( array( 'message' => 'Activity deleted successfully.' ), 200 );
}
/**
* API callback to parse a GPX file and return its summary data.
*
* @param WP_REST_Request $request The request object.
* @return WP_REST_Response|WP_Error A response object with summary data or an error.
*/
function statpress_parse_gpx_summary_api( WP_REST_Request $request ) {
$params = $request->get_json_params();
$gpx_url = $params['gpx_url'] ?? '';
if ( empty( $gpx_url ) ) {
return new WP_Error( 'no_url_provided', 'Nie podano adresu URL do pliku GPX.', array( 'status' => 400 ) );
}
// This new function will do the heavy lifting of calculation.
$summary = statpress_calculate_gpx_summary( $gpx_url );
if ( empty( $summary ) || ! $summary['distance'] > 0 ) {
return new WP_Error( 'gpx_parse_error', 'Nie udało się przetworzyć pliku GPX. Sprawdź, czy plik jest poprawny i zawiera dane trasy.', array( 'status' => 400 ) );
}
return new WP_REST_Response( $summary, 200 );
} }
+94 -94
View File
@@ -1,95 +1,95 @@
<?php <?php
/** /**
* CRUD functions for Activities. * CRUD functions for Activities.
* *
* @package WordPress Activity Stats * @package WordPress Activity Stats
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly. exit; // Exit if accessed directly.
} }
/** /**
* Saves or updates an activity in the database. * Saves or updates an activity in the database.
* *
* @param array $data Associative array of data to save. * @param array $data Associative array of data to save.
* @param int $activity_id The ID of the activity to update. 0 for new activity. * @param int $activity_id The ID of the activity to update. 0 for new activity.
* @return int|false The ID of the saved/updated activity, or false on failure. * @return int|false The ID of the saved/updated activity, or false on failure.
*/ */
function statpress_save_activity_data( array $data, int $activity_id = 0 ) { function statpress_save_activity_data( array $data, int $activity_id = 0 ) {
global $wpdb; global $wpdb;
$table_activities = $wpdb->prefix . 'statpress_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
// Helper to convert empty strings to NULL for the database. // Helper to convert empty strings to NULL for the database.
$null_if_empty = function( $value ) { $null_if_empty = function( $value ) {
// Also check for 0 as we don't want to nullify it for numeric fields. // Also check for 0 as we don't want to nullify it for numeric fields.
return ( '' !== $value && ! is_null( $value ) ) ? $value : null; return ( '' !== $value && ! is_null( $value ) ) ? $value : null;
}; };
// Sanitize and prepare data. // Sanitize and prepare data.
$prepared_data = array( $prepared_data = array(
'category_id' => isset( $data['category_id'] ) ? intval( $data['category_id'] ) : null, 'category_id' => isset( $data['category_id'] ) ? intval( $data['category_id'] ) : null,
'date' => isset( $data['date'] ) ? sanitize_text_field( $data['date'] ) : current_time( 'Y-m-d' ), 'date' => isset( $data['date'] ) ? sanitize_text_field( $data['date'] ) : current_time( 'Y-m-d' ),
'title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '', 'title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '',
'distance' => isset( $data['distance'] ) ? floatval( str_replace( ',', '.', $data['distance'] ) ) : 0, 'distance' => isset( $data['distance'] ) ? floatval( str_replace( ',', '.', $data['distance'] ) ) : 0,
'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : '00:00:00', 'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : '00:00:00',
'calories' => isset( $data['calories'] ) ? intval( $data['calories'] ) : null, 'calories' => isset( $data['calories'] ) ? intval( $data['calories'] ) : null,
'comment' => isset( $data['comment'] ) ? sanitize_textarea_field( $data['comment'] ) : null, 'comment' => isset( $data['comment'] ) ? sanitize_textarea_field( $data['comment'] ) : null,
'strava_url' => isset( $data['strava_url'] ) ? $null_if_empty( esc_url_raw( $data['strava_url'] ) ) : null, 'strava_url' => isset( $data['strava_url'] ) ? $null_if_empty( esc_url_raw( $data['strava_url'] ) ) : null,
'avg_heart_rate' => isset( $data['avg_heart_rate'] ) ? $null_if_empty( intval( $data['avg_heart_rate'] ) ) : null, 'avg_heart_rate' => isset( $data['avg_heart_rate'] ) ? $null_if_empty( intval( $data['avg_heart_rate'] ) ) : null,
'max_heart_rate' => isset( $data['max_heart_rate'] ) ? $null_if_empty( intval( $data['max_heart_rate'] ) ) : null, 'max_heart_rate' => isset( $data['max_heart_rate'] ) ? $null_if_empty( intval( $data['max_heart_rate'] ) ) : null,
'avg_speed' => isset( $data['avg_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['avg_speed'] ) ) ) : null, 'avg_speed' => isset( $data['avg_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['avg_speed'] ) ) ) : null,
'max_speed' => isset( $data['max_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['max_speed'] ) ) ) : null, 'max_speed' => isset( $data['max_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['max_speed'] ) ) ) : null,
'avg_cadence' => isset( $data['avg_cadence'] ) ? $null_if_empty( intval( $data['avg_cadence'] ) ) : null, 'avg_cadence' => isset( $data['avg_cadence'] ) ? $null_if_empty( intval( $data['avg_cadence'] ) ) : null,
'max_cadence' => isset( $data['max_cadence'] ) ? $null_if_empty( intval( $data['max_cadence'] ) ) : null, 'max_cadence' => isset( $data['max_cadence'] ) ? $null_if_empty( intval( $data['max_cadence'] ) ) : null,
'total_elevation_gain' => isset( $data['total_elevation_gain'] ) ? $null_if_empty( intval( $data['total_elevation_gain'] ) ) : null, 'total_elevation_gain' => isset( $data['total_elevation_gain'] ) ? $null_if_empty( intval( $data['total_elevation_gain'] ) ) : null,
'total_elevation_loss' => isset( $data['total_elevation_loss'] ) ? $null_if_empty( intval( $data['total_elevation_loss'] ) ) : null, 'total_elevation_loss' => isset( $data['total_elevation_loss'] ) ? $null_if_empty( intval( $data['total_elevation_loss'] ) ) : null,
'min_altitude' => isset( $data['min_altitude'] ) ? $null_if_empty( intval( $data['min_altitude'] ) ) : null, 'min_altitude' => isset( $data['min_altitude'] ) ? $null_if_empty( intval( $data['min_altitude'] ) ) : null,
'max_altitude' => isset( $data['max_altitude'] ) ? $null_if_empty( intval( $data['max_altitude'] ) ) : null, 'max_altitude' => isset( $data['max_altitude'] ) ? $null_if_empty( intval( $data['max_altitude'] ) ) : null,
'equipment_id' => isset( $data['equipment_id'] ) ? $null_if_empty( intval( $data['equipment_id'] ) ) : null, 'equipment_id' => isset( $data['equipment_id'] ) ? $null_if_empty( intval( $data['equipment_id'] ) ) : null,
'gpx_url' => isset( $data['gpx_url'] ) ? $null_if_empty( esc_url_raw( $data['gpx_url'] ) ) : null, 'gpx_url' => isset( $data['gpx_url'] ) ? $null_if_empty( esc_url_raw( $data['gpx_url'] ) ) : null,
'event_type_id' => isset( $data['event_type_id'] ) ? $null_if_empty( intval( $data['event_type_id'] ) ) : null, 'event_type_id' => isset( $data['event_type_id'] ) ? $null_if_empty( intval( $data['event_type_id'] ) ) : null,
); );
// Data formats for $wpdb. // Data formats for $wpdb.
$format = array( $format = array(
'%d', // category_id '%d', // category_id
'%s', // date '%s', // date
'%s', // title '%s', // title
'%f', // distance '%f', // distance
'%s', // duration '%s', // duration
'%d', // calories '%d', // calories
'%s', // comment '%s', // comment
'%s', // strava_url '%s', // strava_url
'%d', // avg_heart_rate '%d', // avg_heart_rate
'%d', // max_heart_rate '%d', // max_heart_rate
'%f', // avg_speed '%f', // avg_speed
'%f', // max_speed '%f', // max_speed
'%d', // avg_cadence '%d', // avg_cadence
'%d', // max_cadence '%d', // max_cadence
'%d', // total_elevation_gain '%d', // total_elevation_gain
'%d', // total_elevation_loss '%d', // total_elevation_loss
'%d', // min_altitude '%d', // min_altitude
'%d', // max_altitude '%d', // max_altitude
'%d', // equipment_id '%d', // equipment_id
'%s', // gpx_url '%s', // gpx_url
'%d', // event_type_id '%d', // event_type_id
); );
if ( $activity_id > 0 ) { if ( $activity_id > 0 ) {
// UPDATE // UPDATE
$result = $wpdb->update( $table_activities, $prepared_data, array( 'id' => $activity_id ), $format, array( '%d' ) ); $result = $wpdb->update( $table_activities, $prepared_data, array( 'id' => $activity_id ), $format, array( '%d' ) );
if ( false !== $result ) { if ( false !== $result ) {
return $activity_id; return $activity_id;
} }
} else { } else {
// INSERT // INSERT
$result = $wpdb->insert( $table_activities, $prepared_data, $format ); $result = $wpdb->insert( $table_activities, $prepared_data, $format );
if ( $result ) { if ( $result ) {
return $wpdb->insert_id; return $wpdb->insert_id;
} }
} }
return false; return false;
} }
+296 -138
View File
@@ -1,139 +1,297 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/** /**
* Fetches and parses a GPX file from a URL to extract track points and elevation profile. * Fetches and parses a GPX file from a URL to extract track points and elevation profile.
* *
* @param string $gpx_url The URL of the GPX file. * @param string $gpx_url The URL of the GPX file.
* @return array An array containing 'points' for the map and 'elevation_profile'. * @return array An array containing 'points' for the map and 'elevation_profile'.
*/ */
function statpress_parse_gpx_data( $gpx_url ) { function statpress_parse_gpx_data( $gpx_url ) {
if ( empty( $gpx_url ) ) { if ( empty( $gpx_url ) ) {
return array(); return array();
} }
$response = wp_remote_get( $gpx_url, array( 'timeout' => 20 ) ); $response = wp_remote_get( $gpx_url, array( 'timeout' => 20 ) );
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
return array(); return array();
} }
$gpx_content = wp_remote_retrieve_body( $response ); $gpx_content = wp_remote_retrieve_body( $response );
if ( empty( $gpx_content ) ) { if ( empty( $gpx_content ) ) {
return array(); return array();
} }
// --- Privacy Zone --- // --- Privacy Zone ---
$privacy_options = get_option( 'statpress_privacy_options' ); $privacy_options = get_option( 'statpress_privacy_options' );
$privacy_enabled = ! empty( $privacy_options['latitude'] ) && ! empty( $privacy_options['longitude'] ) && ! empty( $privacy_options['radius'] ); $privacy_enabled = ! empty( $privacy_options['latitude'] ) && ! empty( $privacy_options['longitude'] ) && ! empty( $privacy_options['radius'] );
$privacy_center_lat = $privacy_enabled ? (float) $privacy_options['latitude'] : 0; $privacy_center_lat = $privacy_enabled ? (float) $privacy_options['latitude'] : 0;
$privacy_center_lon = $privacy_enabled ? (float) $privacy_options['longitude'] : 0; $privacy_center_lon = $privacy_enabled ? (float) $privacy_options['longitude'] : 0;
$privacy_radius_km = $privacy_enabled ? ( (int) $privacy_options['radius'] ) / 1000 : 0; // Convert meters to km $privacy_radius_km = $privacy_enabled ? ( (int) $privacy_options['radius'] ) / 1000 : 0; // Convert meters to km
libxml_use_internal_errors( true ); libxml_use_internal_errors( true );
$gpx = simplexml_load_string( $gpx_content ); $gpx = simplexml_load_string( $gpx_content );
libxml_clear_errors(); libxml_clear_errors();
if ( false === $gpx ) { if ( false === $gpx ) {
return array(); return array();
} }
// Use XPath to be more robust against different GPX structures/namespaces // Use XPath to be more robust against different GPX structures/namespaces
$gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' ); $gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' );
$trackpoints = $gpx->xpath( '//gpx:trkpt' ); $trackpoints = $gpx->xpath( '//gpx:trkpt' );
if ( empty( $trackpoints ) ) { if ( empty( $trackpoints ) ) {
$trackpoints = $gpx->xpath( '//trkpt' ); // Fallback for files without namespace $trackpoints = $gpx->xpath( '//trkpt' ); // Fallback for files without namespace
} }
$raw_points = array(); $raw_points = array();
$start_time = null; $start_time = null;
foreach ( $trackpoints as $trkpt ) { foreach ( $trackpoints as $trkpt ) {
if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) { if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) {
$extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null; $extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null;
$time = isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null; $time = isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null;
if ( $time && is_null( $start_time ) ) { if ( $time && is_null( $start_time ) ) {
$start_time = $time; $start_time = $time;
} }
$hr_val = ( $extensions && isset( $extensions->TrackPointExtension->hr ) ) ? (int) $extensions->TrackPointExtension->hr : null; $hr_val = ( $extensions && isset( $extensions->TrackPointExtension->hr ) ) ? (int) $extensions->TrackPointExtension->hr : null;
$cad_val = ( $extensions && isset( $extensions->TrackPointExtension->cad ) ) ? (int) $extensions->TrackPointExtension->cad : null; $cad_val = ( $extensions && isset( $extensions->TrackPointExtension->cad ) ) ? (int) $extensions->TrackPointExtension->cad : null;
$raw_points[] = array( $raw_points[] = array(
'lat' => (float) $trkpt['lat'], 'lat' => (float) $trkpt['lat'],
'lon' => (float) $trkpt['lon'], 'lon' => (float) $trkpt['lon'],
'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null, 'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null,
'time_offset' => $start_time && $time ? $time - $start_time : null, 'time_offset' => $start_time && $time ? $time - $start_time : null,
'hr' => $hr_val, 'hr' => $hr_val,
'cad' => $cad_val, 'cad' => $cad_val,
); );
} }
} }
if ( empty( $raw_points ) ) { if ( empty( $raw_points ) ) {
return array(); return array();
} }
// Process raw points to calculate profiles // Process raw points to calculate profiles
$map_points = array(); $map_points = array();
$profiles = array( $profiles = array(
'distance' => array(), 'distance' => array(),
'time' => array(), 'time' => array(),
'elevation' => array(), 'elevation' => array(),
'speed' => array(), 'speed' => array(),
'hr' => array(), 'hr' => array(),
'cadence' => array(), 'cadence' => array(),
); );
$cumulative_distance = 0; $cumulative_distance = 0;
$haversine = function( $lat1, $lon1, $lat2, $lon2 ) { $haversine = function( $lat1, $lon1, $lat2, $lon2 ) {
$earth_radius = 6371; // in km $earth_radius = 6371; // in km
$dLat = deg2rad( $lat2 - $lat1 ); $dLat = deg2rad( $lat2 - $lat1 );
$dLon = deg2rad( $lon2 - $lon1 ); $dLon = deg2rad( $lon2 - $lon1 );
$a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 ); $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 ) ); $c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) );
return $earth_radius * $c; return $earth_radius * $c;
}; };
foreach ( $raw_points as $i => $point ) { foreach ( $raw_points as $i => $point ) {
$is_in_privacy_zone = false; $is_in_privacy_zone = false;
if ( $privacy_enabled ) { if ( $privacy_enabled ) {
$distance_from_center = $haversine( $privacy_center_lat, $privacy_center_lon, $point['lat'], $point['lon'] ); $distance_from_center = $haversine( $privacy_center_lat, $privacy_center_lon, $point['lat'], $point['lon'] );
if ( $distance_from_center <= $privacy_radius_km ) { if ( $distance_from_center <= $privacy_radius_km ) {
$is_in_privacy_zone = true; $is_in_privacy_zone = true;
} }
} }
if ( ! $is_in_privacy_zone ) { if ( ! $is_in_privacy_zone ) {
$map_points[] = array( $point['lat'], $point['lon'] ); $map_points[] = array( $point['lat'], $point['lon'] );
$speed = null; $speed = null;
if ( $i > 0 ) { if ( $i > 0 ) {
$prev_point = $raw_points[ $i - 1 ]; $prev_point = $raw_points[ $i - 1 ];
$distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); // km $distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); // km
$cumulative_distance += $distance_delta; $cumulative_distance += $distance_delta;
if ( ! is_null( $point['time_offset'] ) && ! is_null( $prev_point['time_offset'] ) ) { if ( ! is_null( $point['time_offset'] ) && ! is_null( $prev_point['time_offset'] ) ) {
$time_delta = $point['time_offset'] - $prev_point['time_offset']; // seconds $time_delta = $point['time_offset'] - $prev_point['time_offset']; // seconds
if ( $time_delta > 0 ) { if ( $time_delta > 0 ) {
$speed = ( $distance_delta * 3600 ) / $time_delta; // km/h $speed = ( $distance_delta * 3600 ) / $time_delta; // km/h
} }
} }
} }
$profiles['distance'][] = round( $cumulative_distance, 3 ); $profiles['distance'][] = round( $cumulative_distance, 3 );
$profiles['time'][] = $point['time_offset']; $profiles['time'][] = $point['time_offset'];
$profiles['elevation'][] = ! is_null( $point['ele'] ) ? round( $point['ele'], 2 ) : null; $profiles['elevation'][] = ! is_null( $point['ele'] ) ? round( $point['ele'], 2 ) : null;
$profiles['speed'][] = ! is_null( $speed ) ? round( $speed, 1 ) : null; $profiles['speed'][] = ! is_null( $speed ) ? round( $speed, 1 ) : null;
$profiles['hr'][] = $point['hr']; $profiles['hr'][] = $point['hr'];
$profiles['cadence'][] = $point['cad']; $profiles['cadence'][] = $point['cad'];
} }
} }
return array( return array(
'points' => $map_points, 'points' => $map_points,
'profiles' => $profiles, 'profiles' => $profiles,
); );
}
/**
* Fetches and calculates a full summary from a GPX file.
* This function parses the entire file to get accurate summary stats, ignoring privacy zones.
*
* @param string $gpx_url The URL of the GPX file.
* @return array An associative array with summary statistics.
*/
function statpress_calculate_gpx_summary( $gpx_url ) {
if ( empty( $gpx_url ) ) {
return array();
}
$response = wp_remote_get( $gpx_url, array( 'timeout' => 20 ) );
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
return array();
}
$gpx_content = wp_remote_retrieve_body( $response );
if ( empty( $gpx_content ) ) {
return array();
}
libxml_use_internal_errors( true );
$gpx = simplexml_load_string( $gpx_content );
libxml_clear_errors();
if ( false === $gpx ) {
return array();
}
$gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' );
$trackpoints = $gpx->xpath( '//gpx:trkpt' );
if ( empty( $trackpoints ) ) {
$trackpoints = $gpx->xpath( '//trkpt' );
}
if ( empty( $trackpoints ) ) {
return array();
}
$haversine = function( $lat1, $lon1, $lat2, $lon2 ) {
$earth_radius = 6371; // 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;
};
$stats = array(
'distance' => 0,
'total_elevation_gain' => 0,
'total_elevation_loss' => 0,
'min_altitude' => null,
'max_altitude' => null,
'max_speed' => 0,
'max_heart_rate' => 0,
'max_cadence' => 0,
'hr_sum' => 0,
'hr_count' => 0,
'cad_sum' => 0,
'cad_count' => 0,
'speed_sum' => 0,
'speed_count' => 0,
'start_time' => null,
'end_time' => null,
);
$prev_point = null;
foreach ( $trackpoints as $trkpt ) {
$point = array(
'lat' => (float) $trkpt['lat'],
'lon' => (float) $trkpt['lon'],
'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null,
'time' => isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null,
);
if ( is_null( $stats['start_time'] ) && ! is_null( $point['time'] ) ) {
$stats['start_time'] = $point['time'];
}
$stats['end_time'] = $point['time'];
if ( ! is_null( $point['ele'] ) ) {
if ( is_null( $stats['min_altitude'] ) || $point['ele'] < $stats['min_altitude'] ) {
$stats['min_altitude'] = $point['ele'];
}
if ( is_null( $stats['max_altitude'] ) || $point['ele'] > $stats['max_altitude'] ) {
$stats['max_altitude'] = $point['ele'];
}
}
$extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null;
if ( $extensions ) {
if ( isset( $extensions->TrackPointExtension->hr ) ) {
$hr = (int) $extensions->TrackPointExtension->hr;
$stats['hr_sum'] += $hr;
$stats['hr_count']++;
if ( $hr > $stats['max_heart_rate'] ) {
$stats['max_heart_rate'] = $hr;
}
}
if ( isset( $extensions->TrackPointExtension->cad ) ) {
$cad = (int) $extensions->TrackPointExtension->cad;
$stats['cad_sum'] += $cad;
$stats['cad_count']++;
if ( $cad > $stats['max_cadence'] ) {
$stats['max_cadence'] = $cad;
}
}
}
if ( $prev_point ) {
$distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] );
$stats['distance'] += $distance_delta;
if ( ! is_null( $point['ele'] ) && ! is_null( $prev_point['ele'] ) ) {
$ele_delta = $point['ele'] - $prev_point['ele'];
if ( $ele_delta > 0 ) {
$stats['total_elevation_gain'] += $ele_delta;
} else {
$stats['total_elevation_loss'] += abs( $ele_delta );
}
}
if ( ! is_null( $point['time'] ) && ! is_null( $prev_point['time'] ) ) {
$time_delta = $point['time'] - $prev_point['time'];
if ( $time_delta > 0 ) {
$speed = ( $distance_delta * 3600 ) / $time_delta; // km/h
$stats['speed_sum'] += $speed;
$stats['speed_count']++;
if ( $speed > $stats['max_speed'] ) {
$stats['max_speed'] = $speed;
}
}
}
}
$prev_point = $point;
}
$duration_sec = ( ! is_null( $stats['end_time'] ) && ! is_null( $stats['start_time'] ) ) ? ( $stats['end_time'] - $stats['start_time'] ) : 0;
return array(
'distance' => round( $stats['distance'], 2 ),
'duration' => gmdate( 'H:i:s', $duration_sec ),
'avg_speed' => ( $stats['distance'] > 0 && $duration_sec > 0 ) ? round( ( $stats['distance'] / ( $duration_sec / 3600 ) ), 1 ) : 0,
'max_speed' => round( $stats['max_speed'], 1 ),
'total_elevation_gain' => round( $stats['total_elevation_gain'] ),
'total_elevation_loss' => round( $stats['total_elevation_loss'] ),
'min_altitude' => ! is_null( $stats['min_altitude'] ) ? round( $stats['min_altitude'] ) : null,
'max_altitude' => ! is_null( $stats['max_altitude'] ) ? round( $stats['max_altitude'] ) : null,
'avg_heart_rate' => $stats['hr_count'] > 0 ? round( $stats['hr_sum'] / $stats['hr_count'] ) : 0,
'max_heart_rate' => $stats['max_heart_rate'],
'avg_cadence' => $stats['cad_count'] > 0 ? round( $stats['cad_sum'] / $stats['cad_count'] ) : 0,
'max_cadence' => $stats['max_cadence'],
);
} }
+32 -32
View File
@@ -1,33 +1,33 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/** /**
* Add GPX support to WordPress Media Library. * Add GPX support to WordPress Media Library.
* *
* @param array $mimes Allowed mime types. * @param array $mimes Allowed mime types.
* @return array Modified mime types. * @return array Modified mime types.
*/ */
function statpress_add_gpx_mime_type( $mimes ) { function statpress_add_gpx_mime_type( $mimes ) {
$mimes['gpx'] = 'application/gpx+xml'; $mimes['gpx'] = 'application/gpx+xml';
return $mimes; return $mimes;
} }
/** /**
* Bypasses WordPress's strict file type check for GPX files. * Bypasses WordPress's strict file type check for GPX files.
* This is needed because WordPress can be overly cautious with XML-based files. * This is needed because WordPress can be overly cautious with XML-based files.
* *
* @param array $data File data. * @param array $data File data.
* @param string $file Full path to the file. * @param string $file Full path to the file.
* @param string $filename The filename. * @param string $filename The filename.
* @param array $mimes Mime types. * @param array $mimes Mime types.
* @return array Modified file data. * @return array Modified file data.
*/ */
function statpress_fix_gpx_upload_permission( $data, $file, $filename, $mimes ) { function statpress_fix_gpx_upload_permission( $data, $file, $filename, $mimes ) {
if ( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ) === 'gpx' ) { if ( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ) === 'gpx' ) {
$data['ext'] = 'gpx'; $data['ext'] = 'gpx';
$data['type'] = 'application/gpx+xml'; $data['type'] = 'application/gpx+xml';
} }
return $data; return $data;
} }
+17 -17
View File
@@ -1,18 +1,18 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/** /**
* Registers styles for frontend and enqueues them when shortcodes are used. * Registers styles for frontend and enqueues them when shortcodes are used.
*/ */
function mystat_enqueue_frontend_assets() { function statpress_enqueue_frontend_assets() {
// Register the stylesheet. It will be enqueued by the shortcodes when needed. // Register the stylesheet. It will be enqueued by the shortcodes when needed.
$plugin_version = '1.0'; $plugin_version = '1.0';
wp_register_style( wp_register_style(
'mystat-frontend-styles', 'statpress-frontend-styles',
MYSTAT_PLUGIN_URL . 'assets/css/frontend.css', STATPRESS_PLUGIN_URL . 'assets/css/frontend.css',
array(), array(),
$plugin_version $plugin_version
); );
} }
+392 -392
View File
@@ -1,393 +1,393 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/** /**
* Rejestruje shortcode [moje_statystyki]. * Rejestruje shortcode [statpress_summary] and [statpress_activity].
*/ */
function mystat_register_shortcode() { function statpress_register_shortcode() {
add_shortcode( 'moje_statystyki', 'mystat_shortcode_handler' ); add_shortcode( 'statpress_summary', 'statpress_shortcode_handler' );
add_shortcode( 'moje_statystyki_wpis', 'mystat_single_activity_shortcode_handler' ); add_shortcode( 'statpress_activity', 'statpress_single_activity_shortcode_handler' );
} }
/** /**
* Funkcja obsługująca shortcode. * Funkcja obsługująca shortcode.
* @param array $atts Atrybuty shortcode'u (np. year, month). * @param array $atts Atrybuty shortcode'u (np. year, month).
* @return string HTML do wyświetlenia. * @return string HTML do wyświetlenia.
*/ */
function mystat_shortcode_handler( $atts ) { function statpress_shortcode_handler( $atts ) {
wp_enqueue_style( 'mystat-frontend-styles' ); wp_enqueue_style( 'statpress-frontend-styles' );
global $wpdb; global $wpdb;
// Ustawienie domyślnych atrybutów (bieżący rok i miesiąc) // Ustawienie domyślnych atrybutów (bieżący rok i miesiąc)
$atts = shortcode_atts( $atts = shortcode_atts(
array( array(
'year' => current_time( 'Y' ), 'year' => current_time( 'Y' ),
'month' => current_time( 'n' ), 'month' => current_time( 'n' ),
), ),
$atts, $atts,
'moje_statystyki' 'statpress_summary'
); );
$year = intval( $atts['year'] ); $year = intval( $atts['year'] );
$month = intval( $atts['month'] ); $month = intval( $atts['month'] );
// Pobieranie danych z bazy // Pobieranie danych z bazy
$table_activities = $wpdb->prefix . 'mystat_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$sql = $wpdb->prepare( $sql = $wpdb->prepare(
" "
SELECT a.*, c.name as category_name, eq.name as equipment_name SELECT a.*, c.name as category_name, eq.name as equipment_name
FROM $table_activities a FROM $table_activities a
LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
WHERE YEAR(a.date) = %d AND MONTH(a.date) = %d WHERE YEAR(a.date) = %d AND MONTH(a.date) = %d
ORDER BY a.date ASC ORDER BY a.date ASC
", ",
$year, $year,
$month $month
); );
$activities = $wpdb->get_results( $sql ); $activities = $wpdb->get_results( $sql );
// Obliczanie podsumowań // Obliczanie podsumowań
$total_distance = 0; $total_distance = 0;
$total_seconds = 0; $total_seconds = 0;
foreach ( $activities as $activity ) { foreach ( $activities as $activity ) {
$total_distance += $activity->distance; $total_distance += $activity->distance;
if ( ! empty( $activity->duration ) ) { if ( ! empty( $activity->duration ) ) {
list($h, $m, $s) = explode( ':', $activity->duration ); list($h, $m, $s) = explode( ':', $activity->duration );
$total_seconds += $h * 3600 + $m * 60 + $s; $total_seconds += $h * 3600 + $m * 60 + $s;
} }
} }
$hours = floor( $total_seconds / 3600 ); $hours = floor( $total_seconds / 3600 );
$minutes = floor( ( $total_seconds % 3600 ) / 60 ); $minutes = floor( ( $total_seconds % 3600 ) / 60 );
$total_duration_formatted = sprintf( '%d godz. %d min.', $hours, $minutes ); $total_duration_formatted = sprintf( '%d godz. %d min.', $hours, $minutes );
// Rozpoczęcie buforowania wyjścia // Rozpoczęcie buforowania wyjścia
ob_start(); ob_start();
?> ?>
<div class="mystats-shortcode-container"> <div class="statpress-shortcode-container">
<h3>Podsumowanie miesiąca</h3> <h3>Podsumowanie miesiąca</h3>
<table class="mystats-summary-table"> <table class="statpress-summary-table">
<thead> <thead>
<tr> <tr>
<th>Całkowity dystans</th> <th>Całkowity dystans</th>
<th>Całkowity czas</th> <th>Całkowity czas</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><?php echo number_format( $total_distance, 2, ',', ' ' ); ?> km</td> <td><?php echo number_format( $total_distance, 2, ',', ' ' ); ?> km</td>
<td><?php echo esc_html( $total_duration_formatted ); ?></td> <td><?php echo esc_html( $total_duration_formatted ); ?></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<h3>Lista aktywności</h3> <h3>Lista aktywności</h3>
<table class="mystats-activity-table"> <table class="statpress-activity-table">
<thead> <thead>
<tr> <tr>
<th>Data</th> <th>Data</th>
<th>Tytuł</th> <th>Tytuł</th>
<th>Kategoria</th> <th>Kategoria</th>
<th>Dystans</th> <th>Dystans</th>
<th>Czas</th> <th>Czas</th>
<th>Sprzęt</th> <th>Sprzęt</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php if ( ! empty( $activities ) ) : ?> <?php if ( ! empty( $activities ) ) : ?>
<?php foreach ( $activities as $row ) : ?> <?php foreach ( $activities as $row ) : ?>
<tr> <tr>
<td><?php echo esc_html( date_i18n( 'd.m.Y', strtotime( $row->date ) ) ); ?></td> <td><?php echo esc_html( date_i18n( 'd.m.Y', strtotime( $row->date ) ) ); ?></td>
<td><?php echo esc_html( $row->title ); ?></td> <td><?php echo esc_html( $row->title ); ?></td>
<td><?php echo esc_html( $row->category_name ); ?></td> <td><?php echo esc_html( $row->category_name ); ?></td>
<td><?php echo number_format( $row->distance, 2, ',', ' ' ); ?> km</td> <td><?php echo number_format( $row->distance, 2, ',', ' ' ); ?> km</td>
<td><?php echo esc_html( $row->duration ); ?></td> <td><?php echo esc_html( $row->duration ); ?></td>
<td><?php echo esc_html( $row->equipment_name ); ?></td> <td><?php echo esc_html( $row->equipment_name ); ?></td>
</tr> </tr>
<?php if ( $row->avg_speed || $row->avg_heart_rate || $row->avg_cadence ) : ?> <?php if ( $row->avg_speed || $row->avg_heart_rate || $row->avg_cadence ) : ?>
<tr class="mystats-activity-details-row"> <tr class="statpress-activity-details-row">
<td colspan="6" class="mystats-activity-details-cell"> <td colspan="6" class="statpress-activity-details-cell">
<table class="mystats-nested-details-table"> <table class="statpress-nested-details-table">
<thead> <thead>
<tr> <tr>
<?php if ( $row->avg_speed ) { echo '<th>Śr. prędkość</th>'; } ?> <?php if ( $row->avg_speed ) { echo '<th>Śr. prędkość</th>'; } ?>
<?php if ( $row->avg_heart_rate ) { echo '<th>Śr. tętno</th>'; } ?> <?php if ( $row->avg_heart_rate ) { echo '<th>Śr. tętno</th>'; } ?>
<?php if ( $row->avg_cadence ) { echo '<th>Śr. kadencja</th>'; } ?> <?php if ( $row->avg_cadence ) { echo '<th>Śr. kadencja</th>'; } ?>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<?php if ( $row->avg_speed ) { echo '<td>' . number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h</td>'; } ?> <?php if ( $row->avg_speed ) { echo '<td>' . number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h</td>'; } ?>
<?php if ( $row->avg_heart_rate ) { echo '<td>' . $row->avg_heart_rate . ' bpm</td>'; } ?> <?php if ( $row->avg_heart_rate ) { echo '<td>' . $row->avg_heart_rate . ' bpm</td>'; } ?>
<?php if ( $row->avg_cadence ) { echo '<td>' . $row->avg_cadence . ' rpm</td>'; } ?> <?php if ( $row->avg_cadence ) { echo '<td>' . $row->avg_cadence . ' rpm</td>'; } ?>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</td> </td>
</tr> </tr>
<?php endif; ?> <?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
<?php else : ?> <?php else : ?>
<tr> <tr>
<td colspan="6">Brak aktywności w tym miesiącu.</td> <td colspan="6">Brak aktywności w tym miesiącu.</td>
</tr> </tr>
<?php endif; ?> <?php endif; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
<?php <?php
// Zwrócenie zawartości bufora // Zwrócenie zawartości bufora
return ob_get_clean(); return ob_get_clean();
} }
function mystat_single_activity_shortcode_handler( $atts ) { function statpress_single_activity_shortcode_handler( $atts ) {
wp_enqueue_style( 'mystat-frontend-styles' ); wp_enqueue_style( 'statpress-frontend-styles' );
global $wpdb; global $wpdb;
$atts = shortcode_atts( $atts = shortcode_atts(
array( array(
'id' => 0, 'id' => 0,
), ),
$atts, $atts,
'moje_statystyki_wpis' 'statpress_activity'
); );
$activity_id = intval( $atts['id'] ); $activity_id = intval( $atts['id'] );
if ( 0 === $activity_id ) { if ( 0 === $activity_id ) {
return '<p><strong>Błąd:</strong> Nie podano ID wpisu w shortcode.</p>'; return '<p><strong>Błąd:</strong> Nie podano ID wpisu w shortcode.</p>';
} }
// Pobieranie danych z bazy // Pobieranie danych z bazy
$table_activities = $wpdb->prefix . 'mystat_activities'; $table_activities = $wpdb->prefix . 'statpress_activities';
$sql = $wpdb->prepare( $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 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 FROM $table_activities a
LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id LEFT JOIN {$wpdb->prefix}statpress_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}statpress_event_types et ON a.event_type_id = et.id
LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
WHERE a.id = %d WHERE a.id = %d
", ",
$activity_id $activity_id
); );
$activity = $wpdb->get_row( $sql ); $activity = $wpdb->get_row( $sql );
if ( ! $activity ) { if ( ! $activity ) {
return '<p><strong>Błąd:</strong> Nie znaleziono wpisu o ID ' . esc_html( $activity_id ) . '.</p>'; return '<p><strong>Błąd:</strong> Nie znaleziono wpisu o ID ' . esc_html( $activity_id ) . '.</p>';
} }
// Funkcja pomocnicza do wyświetlania wiersza // Funkcja pomocnicza do wyświetlania wiersza
$render_row = function( $label, $value, $unit = '' ) { $render_row = function( $label, $value, $unit = '' ) {
if ( ! is_null( $value ) && '' !== $value && 0 != $value ) { if ( ! is_null( $value ) && '' !== $value && 0 != $value ) {
echo '<tr>'; echo '<tr>';
echo '<th>' . esc_html( $label ) . '</th>'; echo '<th>' . esc_html( $label ) . '</th>';
echo '<td>' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . '</td>'; echo '<td>' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . '</td>';
echo '</tr>'; echo '</tr>';
} }
}; };
ob_start(); ob_start();
// Prepare map and chart data if GPX exists // Prepare map and chart data if GPX exists
$gpx_data = array(); $gpx_data = array();
$has_gpx_data = false; $has_gpx_data = false;
if ( ! empty( $activity->gpx_url ) ) { if ( ! empty( $activity->gpx_url ) ) {
$gpx_data = mystat_parse_gpx_data( $activity->gpx_url ); $gpx_data = statpress_parse_gpx_data( $activity->gpx_url );
$has_gpx_data = ! empty( $gpx_data['points'] ); $has_gpx_data = ! empty( $gpx_data['points'] );
} }
$unique_id = 'mystat-activity-' . esc_attr( $activity->id ); $unique_id = 'statpress-activity-' . esc_attr( $activity->id );
if ( $has_gpx_data ) { if ( $has_gpx_data ) {
wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' ); 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( '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 ); wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
$map_id = 'map-' . $unique_id; $map_id = 'map-' . $unique_id;
$chart_id = 'chart-' . $unique_id; $chart_id = 'chart-' . $unique_id;
$available_profiles = array(); $available_profiles = array();
if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
$available_profiles['elevation'] = 'Wysokość';} $available_profiles['elevation'] = 'Wysokość';}
if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
$available_profiles['speed'] = 'Prędkość';} $available_profiles['speed'] = 'Prędkość';}
if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
$available_profiles['hr'] = 'Tętno';} $available_profiles['hr'] = 'Tętno';}
if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) { if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
$available_profiles['cadence'] = 'Kadencja';} $available_profiles['cadence'] = 'Kadencja';}
$has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) ); $has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
wp_register_script( 'mystat-shortcode-loader-' . $activity->id, false ); wp_register_script( 'statpress-shortcode-loader-' . $activity->id, false );
wp_enqueue_script( 'mystat-shortcode-loader-' . $activity->id ); wp_enqueue_script( 'statpress-shortcode-loader-' . $activity->id );
$js_script = ' $js_script = '
(function() { (function() {
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const uniqueId = "' . esc_js( $unique_id ) . '"; const uniqueId = "' . esc_js( $unique_id ) . '";
const containerEl = document.getElementById(uniqueId); const containerEl = document.getElementById(uniqueId);
if (!containerEl || typeof L === "undefined" || typeof Chart === "undefined") return; if (!containerEl || typeof L === "undefined" || typeof Chart === "undefined") return;
const trackPoints = ' . json_encode( $gpx_data['points'] ) . '; const trackPoints = ' . json_encode( $gpx_data['points'] ) . ';
const profiles = ' . json_encode( $gpx_data['profiles'] ) . '; const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
let activeChart = null; let activeChart = null;
const mapId = "' . esc_js( $map_id ) . '"; const mapId = "' . esc_js( $map_id ) . '";
const mapEl = document.getElementById(mapId); const mapEl = document.getElementById(mapId);
if (mapEl && trackPoints.length > 0) { if (mapEl && trackPoints.length > 0) {
if (mapEl._leaflet_id) mapEl._leaflet_id = null; if (mapEl._leaflet_id) mapEl._leaflet_id = null;
const map = L.map(mapId); 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); 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); const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map);
map.fitBounds(polyline.getBounds().pad(0.1)); map.fitBounds(polyline.getBounds().pad(0.1));
} }
const chartId = "' . esc_js( $chart_id ) . '"; const chartId = "' . esc_js( $chart_id ) . '";
const chartEl = document.getElementById(chartId); const chartEl = document.getElementById(chartId);
if (!chartEl) return; if (!chartEl) return;
const chartConfigs = { const chartConfigs = {
elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" }, elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" }, speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
hr: { label: "Tętno", unit: "bpm", color: "#c0392b" }, hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" } cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
}; };
const xAxisConfigs = { const xAxisConfigs = {
distance: { label: "Dystans (km)", data: profiles.distance }, 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) } time: { label: "Czas", data: profiles.time, formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8) }
}; };
function renderChart() { function renderChart() {
if (activeChart) activeChart.destroy(); if (activeChart) activeChart.destroy();
const activeTab = containerEl.querySelector(".mystat-chart-tab.active"); const activeTab = containerEl.querySelector(".statpress-chart-tab.active");
if (!activeTab) return; if (!activeTab) return;
const chartType = activeTab.dataset.type; const chartType = activeTab.dataset.type;
const xAxisRadio = containerEl.querySelector(\'input[name="xaxis-\' + uniqueId + \'"]:checked\'); const xAxisRadio = containerEl.querySelector(\'input[name="xaxis-\' + uniqueId + \'"]:checked\');
if (!xAxisRadio) return; if (!xAxisRadio) return;
const xAxisType = xAxisRadio.value; const xAxisType = xAxisRadio.value;
const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data; const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
const filteredY = [], filteredX = []; const filteredY = [], filteredX = [];
if(yData) { if(yData) {
for(let i=0; i<yData.length; i++) { for(let i=0; i<yData.length; i++) {
if (yData[i] !== null) { if (yData[i] !== null) {
filteredY.push(yData[i]); filteredY.push(yData[i]);
filteredX.push(xData[i]); filteredX.push(xData[i]);
} }
} }
} }
const ctx = chartEl.getContext("2d"); const ctx = chartEl.getContext("2d");
activeChart = new Chart(ctx, { activeChart = new Chart(ctx, {
type: "line", type: "line",
data: { data: {
labels: filteredX, labels: filteredX,
datasets: [{ datasets: [{
label: chartConfigs[chartType].label, data: filteredY, label: chartConfigs[chartType].label, data: filteredY,
borderColor: chartConfigs[chartType].color, backgroundColor: chartConfigs[chartType].color + "20", borderColor: chartConfigs[chartType].color, backgroundColor: chartConfigs[chartType].color + "20",
fill: true, pointRadius: 0, tension: 0.1 fill: true, pointRadius: 0, tension: 0.1
}] }]
}, },
options: { options: {
responsive: true, maintainAspectRatio: false, responsive: true, maintainAspectRatio: false,
scales: { scales: {
x: { title: { display: true, text: xAxisConfigs[xAxisType].label }, ticks: { callback: xAxisType === "time" ? xAxisConfigs.time.formatter : (v) => v, maxRotation: 0, autoSkip: true, maxTicksLimit: 7 } }, 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 } } y: { title: { display: true, text: chartConfigs[chartType].unit } }
}, },
plugins: { legend: { display: false } }, plugins: { legend: { display: false } },
interaction: { intersect: false, mode: "index" }, interaction: { intersect: false, mode: "index" },
} }
}); });
} }
containerEl.querySelectorAll(".mystat-chart-tab").forEach(t => t.addEventListener("click", e => { containerEl.querySelectorAll(".statpress-chart-tab").forEach(t => t.addEventListener("click", e => {
e.preventDefault(); e.preventDefault();
containerEl.querySelector(".mystat-chart-tab.active").classList.remove("active"); containerEl.querySelector(".statpress-chart-tab.active").classList.remove("active");
e.currentTarget.classList.add("active"); e.currentTarget.classList.add("active");
renderChart(); renderChart();
})); }));
containerEl.querySelectorAll(\'input[name="xaxis-\' + uniqueId + \'"]\').forEach(r => r.addEventListener("change", renderChart)); containerEl.querySelectorAll(\'input[name="xaxis-\' + uniqueId + \'"]\').forEach(r => r.addEventListener("change", renderChart));
if (containerEl.querySelector(".mystat-chart-tab")) renderChart(); if (containerEl.querySelector(".statpress-chart-tab")) renderChart();
}); });
})();'; })();';
wp_add_inline_script( 'mystat-shortcode-loader-' . $activity->id, $js_script ); wp_add_inline_script( 'statpress-shortcode-loader-' . $activity->id, $js_script );
} }
?> ?>
<div class="mystat-single-activity-shortcode" id="<?php echo esc_attr( $unique_id ); ?>"> <div class="statpress-single-activity-shortcode" id="<?php echo esc_attr( $unique_id ); ?>">
<h4><?php echo esc_html( $activity->title ); ?></h4> <h4><?php echo esc_html( $activity->title ); ?></h4>
<p><em><?php echo esc_html( date_i18n( 'j F Y', strtotime( $activity->date ) ) ); ?></em></p> <p><em><?php echo esc_html( date_i18n( 'j F Y', strtotime( $activity->date ) ) ); ?></em></p>
<div class="mystat-single-columns-container"> <div class="statpress-single-columns-container">
<div class="mystat-single-col"> <div class="statpress-single-col">
<table class="mystat-single-summary-table"> <table class="statpress-single-summary-table">
<tbody> <tbody>
<?php $render_row( 'Dystans', number_format( $activity->distance, 2, ',', ' ' ), 'km' ); ?> <?php $render_row( 'Dystans', number_format( $activity->distance, 2, ',', ' ' ), 'km' ); ?>
<?php $render_row( 'Czas trwania', $activity->duration ); ?> <?php $render_row( 'Czas trwania', $activity->duration ); ?>
<?php $render_row( 'Średnia prędkość', number_format( $activity->avg_speed, 1, ',', ' ' ), 'km/h' ); ?> <?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( 'Suma wzniosów', $activity->total_elevation_gain, 'm' ); ?>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="mystat-single-col"> <div class="statpress-single-col">
<table class="mystat-single-summary-table"> <table class="statpress-single-summary-table">
<tbody> <tbody>
<?php $render_row( 'Kategoria', $activity->category_name ); ?> <?php $render_row( 'Kategoria', $activity->category_name ); ?>
<?php $render_row( 'Sprzęt', $activity->equipment_name ); ?> <?php $render_row( 'Sprzęt', $activity->equipment_name ); ?>
<?php if ( ! empty( $activity->strava_url ) ) : ?> <?php if ( ! empty( $activity->strava_url ) ) : ?>
<tr><th>Strava</th><td><a href="<?php echo esc_url( $activity->strava_url ); ?>" target="_blank" rel="noopener noreferrer">Zobacz aktywność</a></td></tr> <tr><th>Strava</th><td><a href="<?php echo esc_url( $activity->strava_url ); ?>" target="_blank" rel="noopener noreferrer">Zobacz aktywność</a></td></tr>
<?php endif; ?> <?php endif; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<?php if ( $has_gpx_data ) : ?> <?php if ( $has_gpx_data ) : ?>
<div id="<?php echo esc_attr( $map_id ); ?>" class="mystat-single-map" style="height: 350px; width: 100%; margin-top: 15px; border-radius: 5px; margin-bottom: 20px;"></div> <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 ) ) : ?> <?php if ( ! empty( $available_profiles ) ) : ?>
<div class="mystat-charts-container"> <div class="statpress-charts-container">
<div class="mystat-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-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="mystat-chart-tabs" style="display: flex; flex-wrap: wrap; gap: 5px;"> <div class="statpress-chart-tabs" style="display: flex; flex-wrap: wrap; gap: 5px;">
<?php <?php
$is_first = true; $is_first = true;
foreach ( $available_profiles as $key => $label ) : foreach ( $available_profiles as $key => $label ) :
?> ?>
<button data-type="<?php echo esc_attr( $key ); ?>" class="mystat-chart-tab <?php <button data-type="<?php echo esc_attr( $key ); ?>" class="statpress-chart-tab <?php
if ( $is_first ) { if ( $is_first ) {
echo 'active'; echo 'active';
$is_first = false; } $is_first = false; }
?> ?>
"><?php echo esc_html( $label ); ?></button> "><?php echo esc_html( $label ); ?></button>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<?php if ( $has_time_data ) : ?> <?php if ( $has_time_data ) : ?>
<div class="mystat-xaxis-switcher" style="padding-top: 5px; font-size: 0.9em; white-space: nowrap;"> <div class="statpress-xaxis-switcher" style="padding-top: 5px; font-size: 0.9em; white-space: nowrap;">
<strong>Oś X:</strong>&nbsp; <strong>Oś X:</strong>&nbsp;
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked> Dystans</label> <label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked> Dystans</label>
&nbsp; &nbsp;
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="time"> Czas</label> <label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="time"> Czas</label>
</div> </div>
<?php else : ?> <?php else : ?>
<input type="hidden" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked> <input type="hidden" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="mystat-chart-wrapper" style="position: relative; height:250px; width:100%;"> <div class="statpress-chart-wrapper" style="position: relative; height:250px; width:100%;">
<canvas id="<?php echo esc_attr( $chart_id ); ?>"></canvas> <canvas id="<?php echo esc_attr( $chart_id ); ?>"></canvas>
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php <?php
return ob_get_clean(); return ob_get_clean();
} }
+112
View File
@@ -36,6 +36,9 @@ require_once STATPRESS_PLUGIN_DIR . 'includes/admin/pages/page-yearly-summary.ph
require_once STATPRESS_PLUGIN_DIR . 'includes/admin/pages/page-infographic.php'; require_once STATPRESS_PLUGIN_DIR . 'includes/admin/pages/page-infographic.php';
require_once STATPRESS_PLUGIN_DIR . 'includes/admin/pages/page-import-csv.php'; require_once STATPRESS_PLUGIN_DIR . 'includes/admin/pages/page-import-csv.php';
global $statpress_plugin_hooks;
$statpress_plugin_hooks = array();
add_action( 'admin_menu', 'statpress_add_admin_menu' ); add_action( 'admin_menu', 'statpress_add_admin_menu' );
add_action( 'admin_init', 'statpress_admin_init_setup' ); add_action( 'admin_init', 'statpress_admin_init_setup' );
add_action( 'admin_enqueue_scripts', 'statpress_enqueue_admin_styles' ); add_action( 'admin_enqueue_scripts', 'statpress_enqueue_admin_styles' );
@@ -53,3 +56,112 @@ require_once STATPRESS_PLUGIN_DIR . 'includes/frontend/shortcodes.php';
add_action( 'wp_enqueue_scripts', 'statpress_enqueue_frontend_assets' ); add_action( 'wp_enqueue_scripts', 'statpress_enqueue_frontend_assets' );
add_action( 'init', 'statpress_register_shortcode' ); add_action( 'init', 'statpress_register_shortcode' );
// --- 5. MIGRACJA DANYCH (jednorazowa) ---
add_action( 'admin_init', 'statpress_handle_data_migration' );
add_action( 'admin_init', 'statpress_handle_admin_tools' );
/**
* Handles the one-time data migration from 'mystat_' tables to 'statpress_' tables.
*/
function statpress_handle_data_migration() {
// Check if the migration action is triggered, nonce is valid, and user has permissions.
if ( ! isset( $_GET['action'] ) || 'statpress_migrate_data' !== $_GET['action'] ) {
return;
}
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'statpress_migration_nonce' ) ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
global $wpdb;
// Define table columns to ensure robust migration, even if schemas differ.
$table_columns = array(
'activities' => 'id, category_id, date, title, distance, duration, calories, comment, strava_url, avg_heart_rate, max_heart_rate, avg_speed, max_speed, avg_cadence, max_cadence, total_elevation_gain, total_elevation_loss, min_altitude, max_altitude, equipment_id, gpx_url, event_type_id',
'categories' => 'id, name, icon, color',
'equipment' => 'id, name, type, purchase_date, initial_cost, status, notes',
'equipment_log' => 'id, equipment_id, log_date, log_type, description, cost, mileage',
'event_types' => 'id, name',
'goals' => 'id, name, goal_type, target_value, year, month, category_id',
);
$results = array();
$all_successful = true;
foreach ( $table_columns as $table_suffix => $columns ) {
$old_table = $wpdb->prefix . 'mystat_' . $table_suffix;
$new_table = $wpdb->prefix . 'statpress_' . $table_suffix;
// Check if old table exists and new table is empty
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$old_table}'" ) === $old_table ) {
// Check if there's anything to migrate
$old_table_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$old_table}" );
$new_table_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$new_table}" );
// Only migrate if the new table is empty but the old one has data.
if ( $old_table_count > 0 && 0 === $new_table_count ) {
// Use explicit column list for a robust query
$query = "INSERT INTO {$new_table} ({$columns}) SELECT {$columns} FROM {$old_table}";
$copied_rows = $wpdb->query( $query );
if ( false === $copied_rows ) {
$all_successful = false;
$results[ $table_suffix ] = array(
'status' => 'failure',
'error' => $wpdb->last_error,
);
} else {
$results[ $table_suffix ] = array(
'status' => 'success',
'count' => $copied_rows,
);
}
} else {
// If the new table is not empty, skip it.
$results[ $table_suffix ] = array(
'status' => 'skipped',
'count' => $new_table_count,
);
}
}
}
// Store the results in a transient to display a notice
set_transient( 'statpress_migration_results', $results, 60 );
// Mark migration as complete only if everything was successful or skipped.
if ( $all_successful ) {
update_option( 'statpress_migration_complete', true );
}
// Redirect to the main dashboard to show the notice and remove query args
wp_safe_redirect( admin_url( 'admin.php?page=statpress-dashboard' ) );
exit;
}
/**
* Handles various admin tool actions, like resetting the migration flag.
*/
function statpress_handle_admin_tools() {
// Check if the reset migration action is triggered
if ( isset( $_POST['statpress_action'] ) && 'reset_migration' === $_POST['statpress_action'] ) {
// Verify nonce and permissions
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'statpress_reset_migration_nonce' ) ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Delete the option that hides the migration button
delete_option( 'statpress_migration_complete' );
// Set a transient to show a success notice on the settings page
set_transient( 'statpress_migration_reset_notice', true, 60 );
wp_safe_redirect( admin_url( 'admin.php?page=statpress-settings' ) );
exit;
}
}