diff --git a/LICENSE b/LICENSE
index 795d267..b69d491 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2026 Jacek Fefliński
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-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
-SOFTWARE.
+MIT License
+
+Copyright (c) 2026 Jacek Fefliński
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+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
+SOFTWARE.
diff --git a/README.md b/README.md
index 9c6ee1d..0883ad8 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
-# wp-cycling-ststs
-WP plugin for stats
+# wp-cycling-ststs
+WP plugin for stats
diff --git a/assets/css/admin.css b/assets/css/admin.css
index 3eb6aac..0b2ef94 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -1,60 +1,101 @@
-/* Styles for WordPress Activity Stats Plugin - Admin Area */
-
-/* Infografika */
-.statpress-infographic-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 20px;
-}
-.statpress-infographic-card {
- background: #fff;
- border: 1px solid #e0e0e0;
- border-radius: 5px;
- padding: 15px;
- text-align: center;
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
-}
-.statpress-infographic-card h3 {
- margin-top: 0;
- color: #555;
- font-size: 1.1em;
-}
-.statpress-infographic-card p {
- font-size: 1.8em;
- font-weight: bold;
- color: #333;
- margin-bottom: 0;
-}
-
-/* Kontener dla szczegółów aktywności */
-#statpress-details-container {
- display: flex;
- flex-wrap: wrap;
- gap: 2%;
-}
-/* Szczegóły aktywności */
-.statpress-details-col {
- flex: 1 1 48%; /* Pozwala na elastyczne dopasowanie, z bazową szerokością 48% */
- min-width: 300px; /* Zapobiega zbytniemu ściskaniu kolumn na mniejszych ekranach */
-}
-
-/* Chart Controls */
-.statpress-chart-controls {
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- margin-bottom: 15px;
- border-bottom: 1px solid #ddd;
- padding-bottom: 10px;
-}
-
-.statpress-chart-tabs.nav-tab-wrapper {
- border-bottom: none;
- margin-bottom: 0;
-}
-
-.statpress-xaxis-switcher {
- padding-top: 5px;
- white-space: nowrap;
-}
+/* Styles for WordPress Activity Stats Plugin - Admin Area */
+
+/* Infografika */
+.statpress-infographic-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 20px;
+}
+.statpress-infographic-card {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 5px;
+ padding: 15px;
+ text-align: center;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+}
+.statpress-infographic-card h3 {
+ margin-top: 0;
+ color: #555;
+ font-size: 1.1em;
+}
+.statpress-infographic-card p {
+ font-size: 1.8em;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 0;
+}
+
+/* Kontener dla szczegółów aktywności */
+#statpress-details-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 2%;
+}
+/* Szczegóły aktywności */
+.statpress-details-col {
+ flex: 1 1 48%; /* Pozwala na elastyczne dopasowanie, z bazową szerokością 48% */
+ min-width: 300px; /* Zapobiega zbytniemu ściskaniu kolumn na mniejszych ekranach */
+}
+
+/* Chart Controls */
+.statpress-chart-controls {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ margin-bottom: 15px;
+ border-bottom: 1px solid #ddd;
+ padding-bottom: 10px;
+}
+
+.statpress-chart-tabs.nav-tab-wrapper {
+ border-bottom: none;
+ margin-bottom: 0;
+}
+
+.statpress-xaxis-switcher {
+ padding-top: 5px;
+ 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;
+}
diff --git a/assets/css/frontend.css b/assets/css/frontend.css
index 81580d8..b2b1bb3 100644
--- a/assets/css/frontend.css
+++ b/assets/css/frontend.css
@@ -1,103 +1,103 @@
-/* Styles for WordPress Activity Stats Plugin - Frontend Shortcodes */
-
-/* Shortcode [moje_statystyki] */
-.mystats-shortcode-container table {
- width: 100%;
- border-collapse: collapse;
- margin-bottom: 2em;
-}
-.mystats-shortcode-container th,
-.mystats-shortcode-container td {
- padding: 8px 12px;
- border: 1px solid #ddd;
- text-align: left;
-}
-.mystats-shortcode-container th {
- background-color: #f4f4f4;
-}
-
-.mystats-activity-table td:nth-child(4),
-.mystats-activity-table td:nth-child(5) {
- text-align: right;
-}
-
-/* Styles for the nested details table */
-.mystats-activity-details-row > .mystats-activity-details-cell {
- padding: 0 !important;
- border-top: none;
- border-bottom: 1px solid #ddd;
-}
-
-.mystats-nested-details-table {
- width: 100%;
- margin: 0;
- background-color: #fdfdfd;
- border: none !important;
- border-collapse: collapse;
-}
-
-.mystats-nested-details-table th,
-.mystats-nested-details-table td {
- border: none !important;
- padding: 6px 12px;
- text-align: center;
- font-size: 0.9em;
- width: 33.33%;
-}
-
-.mystats-nested-details-table th {
- background-color: transparent;
- font-weight: normal;
- color: #777;
- padding-top: 8px;
- padding-bottom: 2px;
-}
-
-.mystats-nested-details-table td {
- font-weight: bold;
- padding-top: 0;
- padding-bottom: 8px;
-}
-
-/* Shortcode [moje_statystyki_wpis] */
-.mystat-single-activity-shortcode {
- border: 1px solid #eee;
- padding: 15px;
- margin-bottom: 1.5em;
- border-radius: 5px;
- background: #f9f9f9;
-}
-.mystat-single-activity-shortcode h4 {
- margin-top: 0;
-}
-.mystat-single-activity-shortcode p em {
- color: #777;
- font-size: 0.9em;
-}
-.mystat-single-columns-container {
- display: flex;
- flex-wrap: wrap;
- gap: 30px;
- margin-bottom: 15px;
-}
-.mystat-single-col {
- flex: 1;
- min-width: 240px;
-}
-.mystat-single-summary-table {
- width: 100%;
- border-collapse: collapse;
-}
-.mystat-single-summary-table th,
-.mystat-single-summary-table td {
- padding: 4px 0;
- border: none;
- text-align: left;
- vertical-align: top;
-}
-.mystat-single-summary-table th {
- font-weight: bold;
- padding-right: 1em;
- white-space: nowrap;
- width: 1%;
-}
+/* Styles for WordPress Activity Stats Plugin - Frontend Shortcodes */
+
+/* Shortcode [statpress_summary] */
+.statpress-shortcode-container table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 2em;
+}
+.statpress-shortcode-container th,
+.statpress-shortcode-container td {
+ padding: 8px 12px;
+ border: 1px solid #ddd;
+ text-align: left;
+}
+.statpress-shortcode-container th {
+ background-color: #f4f4f4;
+}
+
+.statpress-activity-table td:nth-child(4),
+.statpress-activity-table td:nth-child(5) {
+ text-align: right;
+}
+
+/* Styles for the nested details table */
+.statpress-activity-details-row > .statpress-activity-details-cell {
+ padding: 0 !important;
+ border-top: none;
+ border-bottom: 1px solid #ddd;
+}
+
+.statpress-nested-details-table {
+ width: 100%;
+ margin: 0;
+ background-color: #fdfdfd;
+ border: none !important;
+ border-collapse: collapse;
+}
+
+.statpress-nested-details-table th,
+.statpress-nested-details-table td {
+ border: none !important;
+ padding: 6px 12px;
+ text-align: center;
+ font-size: 0.9em;
+ width: 33.33%;
+}
+
+.statpress-nested-details-table th {
+ background-color: transparent;
+ font-weight: normal;
+ color: #777;
+ padding-top: 8px;
+ padding-bottom: 2px;
+}
+
+.statpress-nested-details-table td {
+ font-weight: bold;
+ padding-top: 0;
+ padding-bottom: 8px;
+}
+
+/* Shortcode [statpress_activity] */
+.statpress-single-activity-shortcode {
+ border: 1px solid #eee;
+ padding: 15px;
+ margin-bottom: 1.5em;
+ border-radius: 5px;
+ background: #f9f9f9;
+}
+.statpress-single-activity-shortcode h4 {
+ margin-top: 0;
+}
+.statpress-single-activity-shortcode p em {
+ color: #777;
+ font-size: 0.9em;
+}
+.statpress-single-columns-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 30px;
+ margin-bottom: 15px;
+}
+.statpress-single-col {
+ flex: 1;
+ min-width: 240px;
+}
+.statpress-single-summary-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+.statpress-single-summary-table th,
+.statpress-single-summary-table td {
+ padding: 4px 0;
+ border: none;
+ text-align: left;
+ vertical-align: top;
+}
+.statpress-single-summary-table th {
+ font-weight: bold;
+ padding-right: 1em;
+ white-space: nowrap;
+ width: 1%;
+}
diff --git a/includes/activation.php b/includes/activation.php
index a0c4b12..e815486 100644
--- a/includes/activation.php
+++ b/includes/activation.php
@@ -1,140 +1,140 @@
-get_charset_collate();
-
- $table_categories = $wpdb->prefix . 'mystat_categories';
- $table_activities = $wpdb->prefix . 'mystat_activities';
- $table_event_types = $wpdb->prefix . 'mystat_event_types';
- $table_equipment = $wpdb->prefix . 'mystat_equipment';
- $table_equipment_log = $wpdb->prefix . 'mystat_equipment_log';
- $table_goals = $wpdb->prefix . 'mystat_goals';
-
- // SQL dla Kategorii
- $sql_cat = "CREATE TABLE $table_categories (
- id mediumint(9) NOT NULL AUTO_INCREMENT,
- name varchar(50) NOT NULL,
- icon varchar(50) NOT NULL,
- color varchar(20) NOT NULL,
- PRIMARY KEY (id)
- ) $charset_collate;";
-
- // SQL dla Typów Wydarzeń
- $sql_event_types = "CREATE TABLE $table_event_types (
- id mediumint(9) NOT NULL AUTO_INCREMENT,
- name varchar(100) NOT NULL,
- PRIMARY KEY (id)
- ) $charset_collate;";
-
- // SQL dla Sprzętu
- $sql_equipment = "CREATE TABLE $table_equipment (
- id mediumint(9) NOT NULL AUTO_INCREMENT,
- name varchar(100) NOT NULL,
- type varchar(50) DEFAULT 'Rower' NOT NULL,
- purchase_date date DEFAULT NULL,
- initial_cost decimal(10,2) DEFAULT NULL,
- status varchar(20) DEFAULT 'aktywny' NOT NULL, -- 'aktywny', 'sprzedany', 'wycofany'
- notes text,
- PRIMARY KEY (id)
- ) $charset_collate;";
-
- // SQL dla Dziennika Serwisowego Sprzętu
- $sql_equipment_log = "CREATE TABLE $table_equipment_log (
- id bigint(20) NOT NULL AUTO_INCREMENT,
- equipment_id mediumint(9) NOT NULL,
- log_date date NOT NULL,
- log_type varchar(50) NOT NULL, -- np. Naprawa, Zakup, Przegląd, Modyfikacja
- description text NOT NULL,
- cost decimal(10,2) DEFAULT NULL,
- mileage int(11) DEFAULT NULL, -- Przebieg sprzętu w momencie serwisu
- PRIMARY KEY (id),
- KEY equipment_id (equipment_id)
- ) $charset_collate;";
-
- // SQL dla Celów
- $sql_goals = "CREATE TABLE $table_goals (
- id mediumint(9) NOT NULL AUTO_INCREMENT,
- name varchar(255) NOT NULL,
- goal_type varchar(20) NOT NULL, -- 'distance', 'duration_sec', 'count'
- target_value decimal(10,2) NOT NULL,
- year smallint(4) NOT NULL,
- month tinyint(2) UNSIGNED DEFAULT NULL,
- category_id mediumint(9) DEFAULT NULL,
- PRIMARY KEY (id),
- KEY category_id (category_id)
- ) $charset_collate;";
-
- // SQL dla Aktywności
- $sql_act = "CREATE TABLE $table_activities (
- id bigint(20) NOT NULL AUTO_INCREMENT,
- category_id mediumint(9) NOT NULL,
- date date NOT NULL,
- title varchar(255) DEFAULT '' NOT NULL,
- distance decimal(10,2) DEFAULT 0.00,
- duration time DEFAULT '00:00:00',
- calories int(11) DEFAULT 0,
- comment text,
- strava_url varchar(255) DEFAULT NULL,
- avg_heart_rate smallint(5) UNSIGNED DEFAULT NULL,
- max_heart_rate smallint(5) UNSIGNED DEFAULT NULL,
- avg_speed decimal(5,2) DEFAULT NULL,
- max_speed decimal(5,2) DEFAULT NULL,
- avg_cadence smallint(5) UNSIGNED DEFAULT NULL,
- max_cadence smallint(5) UNSIGNED DEFAULT NULL,
- total_elevation_gain int(11) DEFAULT NULL,
- total_elevation_loss int(11) DEFAULT NULL,
- min_altitude int(11) DEFAULT NULL,
- max_altitude int(11) DEFAULT NULL,
- equipment_id mediumint(9) DEFAULT NULL,
- gpx_url varchar(255) DEFAULT NULL,
- event_type_id mediumint(9) DEFAULT NULL,
- PRIMARY KEY (id)
- ) $charset_collate;";
-
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
- dbDelta( $sql_goals );
- dbDelta( $sql_equipment );
- dbDelta( $sql_equipment_log );
- dbDelta( $sql_cat );
- dbDelta( $sql_event_types );
- dbDelta( $sql_act );
-
- // Dodanie domyślnych kategorii, jeśli tabela jest pusta
- 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' => 'Bieganie', 'icon' => 'dashicons-businessman', 'color' => '#e74c3c' ) );
- }
-
- // Dodanie domyślnych typów wydarzeń, jeśli tabela jest pusta
- 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' );
- foreach ( $default_event_types as $type_name ) {
- $wpdb->insert( $table_event_types, array( 'name' => $type_name ) );
- }
- // Ustawienie domyślnego typu "Trening" dla istniejących aktywności, które go nie mają
- $training_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_event_types WHERE name = %s", 'Trening' ) );
- if ( $training_id ) {
- $wpdb->query( $wpdb->prepare( "UPDATE $table_activities SET event_type_id = %d WHERE event_type_id IS NULL OR event_type_id = 0", $training_id ) );
- }
- }
-
- // Dodanie domyślnego sprzętu, jeśli tabela jest pusta
- if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_equipment" ) ) {
- $wpdb->insert( $table_equipment, array( 'name' => 'Giant Revolt', 'type' => 'Rower', 'status' => 'aktywny' ) );
- }
+get_charset_collate();
+
+ $table_categories = $wpdb->prefix . 'statpress_categories';
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $table_event_types = $wpdb->prefix . 'statpress_event_types';
+ $table_equipment = $wpdb->prefix . 'statpress_equipment';
+ $table_equipment_log = $wpdb->prefix . 'statpress_equipment_log';
+ $table_goals = $wpdb->prefix . 'statpress_goals';
+
+ // SQL dla Kategorii
+ $sql_cat = "CREATE TABLE $table_categories (
+ id mediumint(9) NOT NULL AUTO_INCREMENT,
+ name varchar(50) NOT NULL,
+ icon varchar(50) NOT NULL,
+ color varchar(20) NOT NULL,
+ PRIMARY KEY (id)
+ ) $charset_collate;";
+
+ // SQL dla Typów Wydarzeń
+ $sql_event_types = "CREATE TABLE $table_event_types (
+ id mediumint(9) NOT NULL AUTO_INCREMENT,
+ name varchar(100) NOT NULL,
+ PRIMARY KEY (id)
+ ) $charset_collate;";
+
+ // SQL dla Sprzętu
+ $sql_equipment = "CREATE TABLE $table_equipment (
+ id mediumint(9) NOT NULL AUTO_INCREMENT,
+ name varchar(100) NOT NULL,
+ type varchar(50) DEFAULT 'Rower' NOT NULL,
+ purchase_date date DEFAULT NULL,
+ initial_cost decimal(10,2) DEFAULT NULL,
+ status varchar(20) DEFAULT 'aktywny' NOT NULL, -- 'aktywny', 'sprzedany', 'wycofany'
+ notes text,
+ PRIMARY KEY (id)
+ ) $charset_collate;";
+
+ // SQL dla Dziennika Serwisowego Sprzętu
+ $sql_equipment_log = "CREATE TABLE $table_equipment_log (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ equipment_id mediumint(9) NOT NULL,
+ log_date date NOT NULL,
+ log_type varchar(50) NOT NULL, -- np. Naprawa, Zakup, Przegląd, Modyfikacja
+ description text NOT NULL,
+ cost decimal(10,2) DEFAULT NULL,
+ mileage int(11) DEFAULT NULL, -- Przebieg sprzętu w momencie serwisu
+ PRIMARY KEY (id),
+ KEY equipment_id (equipment_id)
+ ) $charset_collate;";
+
+ // SQL dla Celów
+ $sql_goals = "CREATE TABLE $table_goals (
+ id mediumint(9) NOT NULL AUTO_INCREMENT,
+ name varchar(255) NOT NULL,
+ goal_type varchar(20) NOT NULL, -- 'distance', 'duration_sec', 'count'
+ target_value decimal(10,2) NOT NULL,
+ year smallint(4) NOT NULL,
+ month tinyint(2) UNSIGNED DEFAULT NULL,
+ category_id mediumint(9) DEFAULT NULL,
+ PRIMARY KEY (id),
+ KEY category_id (category_id)
+ ) $charset_collate;";
+
+ // SQL dla Aktywności
+ $sql_act = "CREATE TABLE $table_activities (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ category_id mediumint(9) NOT NULL,
+ date date NOT NULL,
+ title varchar(255) DEFAULT '' NOT NULL,
+ distance decimal(10,2) DEFAULT 0.00,
+ duration time DEFAULT '00:00:00',
+ calories int(11) DEFAULT 0,
+ comment text,
+ strava_url varchar(255) DEFAULT NULL,
+ avg_heart_rate smallint(5) UNSIGNED DEFAULT NULL,
+ max_heart_rate smallint(5) UNSIGNED DEFAULT NULL,
+ avg_speed decimal(5,2) DEFAULT NULL,
+ max_speed decimal(5,2) DEFAULT NULL,
+ avg_cadence smallint(5) UNSIGNED DEFAULT NULL,
+ max_cadence smallint(5) UNSIGNED DEFAULT NULL,
+ total_elevation_gain int(11) DEFAULT NULL,
+ total_elevation_loss int(11) DEFAULT NULL,
+ min_altitude int(11) DEFAULT NULL,
+ max_altitude int(11) DEFAULT NULL,
+ equipment_id mediumint(9) DEFAULT NULL,
+ gpx_url varchar(255) DEFAULT NULL,
+ event_type_id mediumint(9) DEFAULT NULL,
+ PRIMARY KEY (id)
+ ) $charset_collate;";
+
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+ dbDelta( $sql_goals );
+ dbDelta( $sql_equipment );
+ dbDelta( $sql_equipment_log );
+ dbDelta( $sql_cat );
+ dbDelta( $sql_event_types );
+ dbDelta( $sql_act );
+
+ // Dodanie domyślnych kategorii, jeśli tabela jest pusta
+ 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' => 'Bieganie', 'icon' => 'dashicons-businessman', 'color' => '#e74c3c' ) );
+ }
+
+ // Dodanie domyślnych typów wydarzeń, jeśli tabela jest pusta
+ 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' );
+ foreach ( $default_event_types as $type_name ) {
+ $wpdb->insert( $table_event_types, array( 'name' => $type_name ) );
+ }
+ // Ustawienie domyślnego typu "Trening" dla istniejących aktywności, które go nie mają
+ $training_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_event_types WHERE name = %s", 'Trening' ) );
+ if ( $training_id ) {
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_activities SET event_type_id = %d WHERE event_type_id IS NULL OR event_type_id = 0", $training_id ) );
+ }
+ }
+
+ // Dodanie domyślnego sprzętu, jeśli tabela jest pusta
+ if ( 0 === $wpdb->get_var( "SELECT COUNT(*) FROM $table_equipment" ) ) {
+ $wpdb->insert( $table_equipment, array( 'name' => 'Giant Revolt', 'type' => 'Rower', 'status' => 'aktywny' ) );
+ }
}
\ No newline at end of file
diff --git a/includes/admin/hooks.php b/includes/admin/hooks.php
index e64090d..451cd35 100644
--- a/includes/admin/hooks.php
+++ b/includes/admin/hooks.php
@@ -1,27 +1,27 @@
-
Dodaj Nowy Trening ';
- // Obsługa zapisu formularza (musi być przed renderowaniem, aby wyświetlić komunikat)
- statpress_handle_activity_form_submission();
- // Formularz dodawania
- statpress_render_add_form();
- echo '';
-}
-
-function statpress_edit_activity_page() {
- global $wpdb;
- $activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0;
-
- if ( $activity_id === 0 ) {
- echo 'Błąd Nie podano ID aktywności do edycji.
';
- return;
- }
-
- // Handle form submission for update
- statpress_handle_activity_form_submission();
-
- $table_activities = $wpdb->prefix . 'statpress_activities';
- $activity = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_activities WHERE id = %d", $activity_id ) );
-
- if ( ! $activity ) {
- echo 'Błąd Nie znaleziono aktywności o podanym ID.
';
- return;
- }
-
- echo '
Edytuj Trening ';
- statpress_render_add_form( $activity );
- echo '';
-}
-
-/**
- * Obsługa zapisu nowego lub edytowanego wpisu do bazy danych
- */
-function statpress_handle_activity_form_submission() {
- global $wpdb;
-
- // Sprawdź czy formularz został wysłany
- if ( ! isset( $_POST['statpress_submit_activity'] ) ) {
- return;
- }
-
- $activity_id = isset( $_POST['activity_id'] ) ? intval( $_POST['activity_id'] ) : 0;
- $nonce_action = $activity_id > 0 ? 'statpress_edit_entry_' . $activity_id : 'statpress_add_entry';
-
- // Weryfikacja bezpieczeństwa (Nonce)
- if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], $nonce_action ) ) {
- echo 'Błąd weryfikacji bezpieczeństwa formularza.
';
- return;
- }
-
- // Use the refactored function to save data.
- // We can pass $_POST directly as the function will sanitize it.
- $result = statpress_save_activity_data( $_POST, $activity_id );
-
- if ( $activity_id > 0 ) {
- $message = 'Trening zaktualizowany pomyślnie!';
- } else {
- $message = 'Trening dodany pomyślnie!';
- }
-
- if ( $result ) {
- echo '' . esc_html( $message ) . '
';
- } else {
- echo 'Wystąpił błąd podczas zapisu do bazy.
';
- }
-}
-
-/**
- * Renderowanie formularza HTML
- */
-function statpress_render_add_form( $activity = null ) {
- // Enqueue media scripts for the uploader
- wp_enqueue_media();
-
- global $wpdb;
- $table_categories = $wpdb->prefix . 'statpress_categories';
- $table_event_types = $wpdb->prefix . 'statpress_event_types';
- $table_equipment = $wpdb->prefix . 'statpress_equipment';
- $categories = $wpdb->get_results( "SELECT * FROM $table_categories ORDER BY name ASC" );
- $event_types = $wpdb->get_results( "SELECT * FROM $table_event_types ORDER BY name ASC" );
- $equipment_list = $wpdb->get_results( "SELECT * FROM $table_equipment ORDER BY name ASC" );
-
- $is_edit_mode = ! is_null( $activity );
- $nonce_action = $is_edit_mode ? 'statpress_edit_entry_' . $activity->id : 'statpress_add_entry';
- $form_title = $is_edit_mode ? 'Edytuj Aktywność' : 'Dodaj Nową Aktywność';
- $button_text = $is_edit_mode ? 'Zaktualizuj Trening' : 'Zapisz Trening';
-
- ?>
-
- Dodaj Nowy Trening ';
+ // Obsługa zapisu formularza (musi być przed renderowaniem, aby wyświetlić komunikat)
+ statpress_handle_activity_form_submission();
+ // Formularz dodawania
+ statpress_render_add_form();
+ echo '';
+}
+
+function statpress_edit_activity_page() {
+ global $wpdb;
+ $activity_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0;
+
+ if ( $activity_id === 0 ) {
+ echo 'Błąd Nie podano ID aktywności do edycji.
';
+ return;
+ }
+
+ // Handle form submission for update
+ statpress_handle_activity_form_submission();
+
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $activity = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_activities WHERE id = %d", $activity_id ) );
+
+ if ( ! $activity ) {
+ echo 'Błąd Nie znaleziono aktywności o podanym ID.
';
+ return;
+ }
+
+ echo '
Edytuj Trening ';
+ statpress_render_add_form( $activity );
+ echo '';
+}
+
+/**
+ * Obsługa zapisu nowego lub edytowanego wpisu do bazy danych
+ */
+function statpress_handle_activity_form_submission() {
+ global $wpdb;
+
+ // Sprawdź czy formularz został wysłany
+ if ( ! isset( $_POST['statpress_submit_activity'] ) ) {
+ return;
+ }
+
+ $activity_id = isset( $_POST['activity_id'] ) ? intval( $_POST['activity_id'] ) : 0;
+ $nonce_action = $activity_id > 0 ? 'statpress_edit_entry_' . $activity_id : 'statpress_add_entry';
+
+ // Weryfikacja bezpieczeństwa (Nonce)
+ if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], $nonce_action ) ) {
+ echo 'Błąd weryfikacji bezpieczeństwa formularza.
';
+ return;
+ }
+
+ // Use the refactored function to save data.
+ // We can pass $_POST directly as the function will sanitize it.
+ $result = statpress_save_activity_data( $_POST, $activity_id );
+
+ if ( $activity_id > 0 ) {
+ $message = 'Trening zaktualizowany pomyślnie!';
+ } else {
+ $message = 'Trening dodany pomyślnie!';
+ }
+
+ if ( $result ) {
+ echo '' . esc_html( $message ) . '
';
+ } else {
+ echo 'Wystąpił błąd podczas zapisu do bazy.
';
+ }
+}
+
+/**
+ * Renderowanie formularza HTML
+ */
+function statpress_render_add_form( $activity = null ) {
+ // Enqueue media scripts for the uploader
+ wp_enqueue_media();
+
+ global $wpdb;
+ $table_categories = $wpdb->prefix . 'statpress_categories';
+ $table_event_types = $wpdb->prefix . 'statpress_event_types';
+ $table_equipment = $wpdb->prefix . 'statpress_equipment';
+ $categories = $wpdb->get_results( "SELECT * FROM $table_categories ORDER BY name ASC" );
+ $event_types = $wpdb->get_results( "SELECT * FROM $table_event_types ORDER BY name ASC" );
+ $equipment_list = $wpdb->get_results( "SELECT * FROM $table_equipment ORDER BY name ASC" );
+
+ $is_edit_mode = ! is_null( $activity );
+ $nonce_action = $is_edit_mode ? 'statpress_edit_entry_' . $activity->id : 'statpress_add_entry';
+ $form_title = $is_edit_mode ? 'Edytuj Aktywność' : 'Dodaj Nową Aktywność';
+ $button_text = $is_edit_mode ? 'Zaktualizuj Trening' : 'Zapisz Trening';
+
+ ?>
+
+ Błąd Nie podano ID aktywności.
';
- return;
- }
-
- $table_activities = $wpdb->prefix . 'statpress_activities';
- $table_categories = $wpdb->prefix . 'statpress_categories';
- $table_event_types = $wpdb->prefix . 'statpress_event_types';
- $table_equipment = $wpdb->prefix . 'statpress_equipment';
-
- $sql = $wpdb->prepare(
- "
- SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name
- FROM $table_activities a
- LEFT JOIN $table_categories c ON a.category_id = c.id
- LEFT JOIN $table_event_types et ON a.event_type_id = et.id
- LEFT JOIN $table_equipment eq ON a.equipment_id = eq.id
- WHERE a.id = %d
- ",
- $activity_id
- );
-
- $activity = $wpdb->get_row( $sql );
-
- if ( ! $activity ) {
- echo 'Błąd Nie znaleziono aktywności o podanym ID.
';
- return;
- }
-
- // Funkcja pomocnicza do wyświetlania wiersza, jeśli wartość istnieje
- $render_row = function( $label, $value, $unit = '' ) {
- if ( ! is_null( $value ) && '' !== $value ) {
- echo '';
- echo '' . esc_html( $label ) . ' ';
- echo '' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . ' ';
- echo ' ';
- }
- };
-
- // Prepare map and chart data if GPX exists
- $gpx_data = array();
- $has_gpx_data = false;
- if ( ! empty( $activity->gpx_url ) ) {
- $gpx_data = statpress_parse_gpx_data( $activity->gpx_url );
- $has_gpx_data = ! empty( $gpx_data['points'] );
- }
-
- if ( $has_gpx_data ) {
- wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
- wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', array(), '1.9.4', true );
- wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
-
- wp_register_script( 'statpress-details-loader', false );
- wp_enqueue_script( 'statpress-details-loader' );
-
- // Check which profiles have data
- $available_profiles = array();
- if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
- $available_profiles['elevation'] = 'Wysokość';}
- if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
- $available_profiles['speed'] = 'Prędkość';}
- if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
- $available_profiles['hr'] = 'Tętno';}
- if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
- $available_profiles['cadence'] = 'Kadencja';}
-
- $has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
-
- $chart_js = '
- const track_points = ' . json_encode( $gpx_data['points'] ) . ';
- const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
- let activeChart = null;
-
- if (typeof L !== "undefined" && track_points.length > 0) {
- const map = L.map("statpress-activity-map");
- L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© OpenStreetMap \' }).addTo(map);
- const polyline = L.polyline(track_points, {color: "' . esc_js( $activity->color ) . '"}).addTo(map);
- map.fitBounds(polyline.getBounds().pad(0.1));
- L.marker(track_points[0]).addTo(map).bindPopup("Start");
- L.marker(track_points[track_points.length - 1]).addTo(map).bindPopup("Koniec");
- }
-
- const chartConfigs = {
- elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
- speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
- hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
- cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
- };
- const xAxisConfigs = {
- distance: { label: "Dystans (km)", data: profiles.distance },
- time: {
- label: "Czas", data: profiles.time,
- formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8)
- }
- };
-
- function renderChart() {
- if (activeChart) activeChart.destroy();
- const chartType = document.querySelector(".statpress-chart-tabs .nav-tab-active").getAttribute("href").substring(1);
- const xAxisType = document.querySelector(\'input[name="statpress_xaxis"]:checked\').value;
-
- const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
- const filteredY = [], filteredX = [];
- if(yData) {
- for(let i=0; i v, maxRotation: 0, autoSkip: true, maxTicksLimit: 10 } },
- y: { title: { display: true, text: chartConfigs[chartType].unit } }
- },
- plugins: { legend: { display: false } },
- interaction: { intersect: false, mode: "index" },
- }
- });
- }
-
- document.querySelectorAll(".statpress-chart-tabs .nav-tab").forEach(t => t.addEventListener("click", e => {
- e.preventDefault();
- document.querySelector(".statpress-chart-tabs .nav-tab-active").classList.remove("nav-tab-active");
- e.target.classList.add("nav-tab-active");
- renderChart();
- }));
- document.querySelectorAll(\'input[name="statpress_xaxis"]\').forEach(r => r.addEventListener("change", renderChart));
- if (document.querySelector(".statpress-chart-tabs .nav-tab")) renderChart();
- ';
- wp_add_inline_script( 'statpress-details-loader', 'document.addEventListener("DOMContentLoaded", function() {' . $chart_js . '});' );
- }
-
- ?>
-
-
- Szczegóły treningu: title ); ?>
-
- Edytuj
-
-
-
-
← Powrót do listy aktywności
-
-
-
-
-
-
-
Notatki i linki
-
-
-
-
-
Mapa Trasy
-
-
-
-
Wykresy
-
-
-
- gpx_url ) ) : ?>
-
-
-
Nie udało się wczytać danych z pliku GPX lub plik jest uszkodzony/pusty. Brak danych do wyświetlenia mapy i wykresów.
-
-
-
-
-
-
- Błąd Nie podano ID aktywności.
';
+ return;
+ }
+
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $table_categories = $wpdb->prefix . 'statpress_categories';
+ $table_event_types = $wpdb->prefix . 'statpress_event_types';
+ $table_equipment = $wpdb->prefix . 'statpress_equipment';
+
+ $sql = $wpdb->prepare(
+ "
+ SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name
+ FROM $table_activities a
+ LEFT JOIN $table_categories c ON a.category_id = c.id
+ LEFT JOIN $table_event_types et ON a.event_type_id = et.id
+ LEFT JOIN $table_equipment eq ON a.equipment_id = eq.id
+ WHERE a.id = %d
+ ",
+ $activity_id
+ );
+
+ $activity = $wpdb->get_row( $sql );
+
+ if ( ! $activity ) {
+ echo 'Błąd Nie znaleziono aktywności o podanym ID.
';
+ return;
+ }
+
+ // Funkcja pomocnicza do wyświetlania wiersza, jeśli wartość istnieje
+ $render_row = function( $label, $value, $unit = '' ) {
+ if ( ! is_null( $value ) && '' !== $value ) {
+ echo '';
+ echo '' . esc_html( $label ) . ' ';
+ echo '' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . ' ';
+ echo ' ';
+ }
+ };
+
+ // Prepare map and chart data if GPX exists
+ $gpx_data = array();
+ $has_gpx_data = false;
+ if ( ! empty( $activity->gpx_url ) ) {
+ $gpx_data = statpress_parse_gpx_data( $activity->gpx_url );
+ $has_gpx_data = ! empty( $gpx_data['points'] );
+ }
+
+ if ( $has_gpx_data ) {
+ wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
+ wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', array(), '1.9.4', true );
+ wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
+
+ wp_register_script( 'statpress-details-loader', false );
+ wp_enqueue_script( 'statpress-details-loader' );
+
+ // Check which profiles have data
+ $available_profiles = array();
+ if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
+ $available_profiles['elevation'] = 'Wysokość';}
+ if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
+ $available_profiles['speed'] = 'Prędkość';}
+ if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
+ $available_profiles['hr'] = 'Tętno';}
+ if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
+ $available_profiles['cadence'] = 'Kadencja';}
+
+ $has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
+
+ $chart_js = '
+ const track_points = ' . json_encode( $gpx_data['points'] ) . ';
+ const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
+ let activeChart = null;
+
+ if (typeof L !== "undefined" && track_points.length > 0) {
+ const map = L.map("statpress-activity-map");
+ L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© OpenStreetMap \' }).addTo(map);
+ const polyline = L.polyline(track_points, {color: "' . esc_js( $activity->color ) . '"}).addTo(map);
+ map.fitBounds(polyline.getBounds().pad(0.1));
+ L.marker(track_points[0]).addTo(map).bindPopup("Start");
+ L.marker(track_points[track_points.length - 1]).addTo(map).bindPopup("Koniec");
+ }
+
+ const chartConfigs = {
+ elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
+ speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
+ hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
+ cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
+ };
+ const xAxisConfigs = {
+ distance: { label: "Dystans (km)", data: profiles.distance },
+ time: {
+ label: "Czas", data: profiles.time,
+ formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8)
+ }
+ };
+
+ function renderChart() {
+ if (activeChart) activeChart.destroy();
+ const chartType = document.querySelector(".statpress-chart-tabs .nav-tab-active").getAttribute("href").substring(1);
+ const xAxisType = document.querySelector(\'input[name="statpress_xaxis"]:checked\').value;
+
+ const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
+ const filteredY = [], filteredX = [];
+ if(yData) {
+ for(let i=0; i v, maxRotation: 0, autoSkip: true, maxTicksLimit: 10 } },
+ y: { title: { display: true, text: chartConfigs[chartType].unit } }
+ },
+ plugins: { legend: { display: false } },
+ interaction: { intersect: false, mode: "index" },
+ }
+ });
+ }
+
+ document.querySelectorAll(".statpress-chart-tabs .nav-tab").forEach(t => t.addEventListener("click", e => {
+ e.preventDefault();
+ document.querySelector(".statpress-chart-tabs .nav-tab-active").classList.remove("nav-tab-active");
+ e.target.classList.add("nav-tab-active");
+ renderChart();
+ }));
+ document.querySelectorAll(\'input[name="statpress_xaxis"]\').forEach(r => r.addEventListener("change", renderChart));
+ if (document.querySelector(".statpress-chart-tabs .nav-tab")) renderChart();
+ ';
+ wp_add_inline_script( 'statpress-details-loader', 'document.addEventListener("DOMContentLoaded", function() {' . $chart_js . '});' );
+ }
+
+ ?>
+
+
+ Szczegóły treningu: title ); ?>
+
+ Edytuj
+
+
+
+
← Powrót do listy aktywności
+
+
+
+
+
+
+
Notatki i linki
+
+
+
+
+
Mapa Trasy
+
+
+
+
Wykresy
+
+
+
+ gpx_url ) ) : ?>
+
+
+
Nie udało się wczytać danych z pliku GPX lub plik jest uszkodzony/pusty. Brak danych do wyświetlenia mapy i wykresów.
+
+
+
+
+
+
+ StatPress Dashboard ';
- statpress_render_history_table();
- echo '';
-}
-
-function statpress_render_history_table() {
- global $wpdb;
-
- // 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
- // Jeśli tabele są "sztywne" (bez prefixu wp_), usuń $wpdb->prefix.
- $table_activities = $wpdb->prefix . 'statpress_activities';
- $table_categories = $wpdb->prefix . 'statpress_categories';
-
- // --- 1. OBSŁUGA USUWANIA (DELETE) ---
- if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'statpress_delete' === $_GET['action'] ) {
- $activity_id = intval( $_GET['id'] );
-
- // Weryfikacja bezpieczeństwa (Nonce)
- if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_' . $activity_id ) ) {
- $result = $wpdb->delete(
- $table_activities,
- array( 'id' => $activity_id ),
- array( '%d' )
- );
-
- if ( $result ) {
- echo 'Aktywność została usunięta.
';
- } else {
- echo 'Wystąpił błąd podczas usuwania.
';
- }
- } else {
- echo 'Błąd weryfikacji bezpieczeństwa (Nonce).
';
- }
- }
-
- // --- 2. USTAWIENIA PAGINACJI ---
- $items_per_page = 20; // Ile wpisów na stronę
- $current_page = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
- $offset = ( $current_page - 1 ) * $items_per_page;
-
- // --- 3. POBIERANIE DANYCH (SELECT) ---
- // Pobranie całkowitej liczby wpisów do paginacji
- $total_items = $wpdb->get_var( "SELECT COUNT(id) FROM $table_activities" );
- $total_pages = ceil( $total_items / $items_per_page );
-
- // Pobieramy wpisy dla bieżącej strony
- $sql = $wpdb->prepare(
- "
- SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name
- FROM $table_activities a
- LEFT JOIN $table_categories c ON a.category_id = c.id
- LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id
- LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
- ORDER BY a.date DESC, a.id DESC
- LIMIT %d OFFSET %d
- ",
- $items_per_page,
- $offset
- );
-
- $activities = $wpdb->get_results( $sql );
-
- // --- 4. WIDOK TABELI (HTML) ---
- ?>
-
-
Historia Aktywności
-
- 1 ) : ?>
-
-
- aktywności
- add_query_arg( 'paged', '%#%' ),
- 'format' => '',
- 'total' => $total_pages,
- 'current' => $current_page,
- 'prev_text' => '« Poprzednia',
- 'next_text' => 'Następna »',
- )
- );
- ?>
-
-
-
-
-
-
-
- Ikona
- Data
- Tytuł
- Kategoria
- Typ
- Sprzęt
- Dystans (km)
- Czas
- Śr. prędkość
- Akcja
-
-
-
-
-
- 'statpress_delete',
- 'id' => $row->id,
- )
- ),
- 'statpress_delete_' . $row->id
- );
-
- $edit_url = add_query_arg(
- array(
- 'page' => 'statpress-edit-activity',
- 'id' => $row->id,
- ),
- admin_url( 'admin.php' )
- );
-
- $details_url = add_query_arg(
- array(
- 'page' => 'statpress-view-activity',
- 'id' => $row->id,
- ),
- admin_url( 'admin.php' )
- );
- ?>
-
-
- icon ) ) : ?>
-
-
-
- date ); ?>
- title ? wp_trim_words( $row->title, 6 ) : '(bez tytułu)' ); ?>
- category_name ); ?>
- event_type_name ); ?>
- equipment_name ); ?>
- distance, 2, ',', ' ' ); ?>
- duration ); ?>
- avg_speed ? number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h' : '-'; ?>
-
- Edytuj
- Szczegóły
-
- Usuń
-
-
-
-
-
-
- Brak zarejestrowanych aktywności. Dodaj pierwszy trening powyżej!
-
-
-
-
-
- 1 ) : ?>
-
-
- aktywności
- add_query_arg( 'paged', '%#%' ),
- 'format' => '',
- 'total' => $total_pages,
- 'current' => $current_page,
- 'prev_text' => '« Poprzednia',
- 'next_text' => 'Następna »',
- )
- );
- ?>
-
-
-
-
- StatPress Dashboard ';
+
+ // --- MIGRATION NOTICE ---
+ // Show the migration button if it hasn't been completed yet.
+ if ( ! get_option( 'statpress_migration_complete' ) ) {
+ $migration_url = wp_nonce_url(
+ admin_url( 'admin.php?page=statpress-dashboard&action=statpress_migrate_data' ),
+ 'statpress_migration_nonce'
+ );
+ echo '';
+ echo '
Migracja danych StatPress ';
+ echo '
Wygląda na to, że istnieją dane w starych tabelach (mystat_*), które można przenieść. Kliknij przycisk poniżej, aby rozpocząć proces.
';
+ echo '
Ważne: Ta operacja jest jednorazowa i powinna być wykonana tylko raz, na pustej instalacji wtyczki StatPress.
';
+ echo '
Rozpocznij migrację danych ';
+ echo '
';
+ }
+
+ // Show the results of the migration after it's done.
+ $migration_results = get_transient( 'statpress_migration_results' );
+ if ( $migration_results ) {
+ echo '';
+ echo '
Migracja zakończona! ';
+ echo '
';
+ echo '
Twoje dane powinny być teraz widoczne. Stare tabele (' . esc_html( $GLOBALS['wpdb']->prefix ) . 'mystat_*) 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.
';
+ echo '
';
+ delete_transient( 'statpress_migration_results' );
+ }
+ // --- END MIGRATION NOTICE ---
+
+ statpress_render_history_table();
+ echo '';
+}
+
+function statpress_render_history_table() {
+ global $wpdb;
+
+ // 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
+ // Jeśli tabele są "sztywne" (bez prefixu wp_), usuń $wpdb->prefix.
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $table_categories = $wpdb->prefix . 'statpress_categories';
+
+ // --- 1. OBSŁUGA USUWANIA (DELETE) ---
+ if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'statpress_delete' === $_GET['action'] ) {
+ $activity_id = intval( $_GET['id'] );
+
+ // Weryfikacja bezpieczeństwa (Nonce)
+ if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_' . $activity_id ) ) {
+ $result = $wpdb->delete(
+ $table_activities,
+ array( 'id' => $activity_id ),
+ array( '%d' )
+ );
+
+ if ( $result ) {
+ echo 'Aktywność została usunięta.
';
+ } else {
+ echo 'Wystąpił błąd podczas usuwania.
';
+ }
+ } else {
+ echo 'Błąd weryfikacji bezpieczeństwa (Nonce).
';
+ }
+ }
+
+ // --- 2. USTAWIENIA PAGINACJI ---
+ $items_per_page = 20; // Ile wpisów na stronę
+ $current_page = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
+ $offset = ( $current_page - 1 ) * $items_per_page;
+
+ // --- 3. POBIERANIE DANYCH (SELECT) ---
+ // Pobranie całkowitej liczby wpisów do paginacji
+ $total_items = $wpdb->get_var( "SELECT COUNT(id) FROM $table_activities" );
+ $total_pages = ceil( $total_items / $items_per_page );
+
+ // Pobieramy wpisy dla bieżącej strony
+ $sql = $wpdb->prepare(
+ "
+ SELECT a.*, c.name as category_name, c.icon, c.color, et.name as event_type_name, eq.name as equipment_name
+ FROM $table_activities a
+ LEFT JOIN $table_categories c ON a.category_id = c.id
+ LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id
+ LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
+ ORDER BY a.date DESC, a.id DESC
+ LIMIT %d OFFSET %d
+ ",
+ $items_per_page,
+ $offset
+ );
+
+ $activities = $wpdb->get_results( $sql );
+
+ // --- 4. WIDOK TABELI (HTML) ---
+ ?>
+
+
Historia Aktywności
+
+ 1 ) : ?>
+
+
+ aktywności
+ add_query_arg( 'paged', '%#%' ),
+ 'format' => '',
+ 'total' => $total_pages,
+ 'current' => $current_page,
+ 'prev_text' => '« Poprzednia',
+ 'next_text' => 'Następna »',
+ )
+ );
+ ?>
+
+
+
+
+
+
+
+ Ikona
+ Data
+ Tytuł
+ Kategoria
+ Typ
+ Sprzęt
+ Dystans (km)
+ Czas
+ Śr. prędkość
+ Akcja
+
+
+
+
+
+ 'statpress_delete',
+ 'id' => $row->id,
+ )
+ ),
+ 'statpress_delete_' . $row->id
+ );
+
+ $edit_url = add_query_arg(
+ array(
+ 'page' => 'statpress-edit-activity',
+ 'id' => $row->id,
+ ),
+ admin_url( 'admin.php' )
+ );
+
+ $details_url = add_query_arg(
+ array(
+ 'page' => 'statpress-view-activity',
+ 'id' => $row->id,
+ ),
+ admin_url( 'admin.php' )
+ );
+ ?>
+
+
+ icon ) ) : ?>
+
+
+
+ date ); ?>
+ title ? wp_trim_words( $row->title, 6 ) : '(bez tytułu)' ); ?>
+ category_name ); ?>
+ event_type_name ); ?>
+ equipment_name ); ?>
+ distance, 2, ',', ' ' ); ?>
+ duration ); ?>
+ avg_speed ? number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h' : '-'; ?>
+
+ Edytuj
+ Szczegóły
+
+ Usuń
+
+
+
+
+
+
+ Brak zarejestrowanych aktywności. Dodaj pierwszy trening powyżej!
+
+
+
+
+
+ 1 ) : ?>
+
+
+ aktywności
+ add_query_arg( 'paged', '%#%' ),
+ 'format' => '',
+ 'total' => $total_pages,
+ 'current' => $current_page,
+ 'prev_text' => '« Poprzednia',
+ 'next_text' => 'Następna »',
+ )
+ );
+ ?>
+
+
+
+
+ prefix . 'statpress_equipment';
- $message = '';
- $notice_class = '';
-
- // Handle POST requests (add/update)
- if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_equipment' ) ) {
- $item_id = isset( $_POST['equipment_id'] ) ? intval( $_POST['equipment_id'] ) : 0;
- $data = array(
- 'name' => sanitize_text_field( $_POST['equipment_name'] ),
- 'type' => sanitize_text_field( $_POST['equipment_type'] ),
- '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'] ) ),
- 'status' => sanitize_text_field( $_POST['status'] ),
- 'notes' => sanitize_textarea_field( $_POST['notes'] ),
- );
-
- if ( ! empty( $data['name'] ) ) {
- if ( $item_id > 0 ) { // Update
- $result = $wpdb->update( $table_equipment, $data, array( 'id' => $item_id ) );
- if ( false !== $result ) {
- $message = 'Sprzęt zaktualizowany.';
- $notice_class = 'notice-success';
- } else {
- $message = 'Błąd podczas aktualizacji sprzętu: ' . $wpdb->last_error;
- $notice_class = 'notice-error';
- }
- } else { // Insert
- $result = $wpdb->insert( $table_equipment, $data );
- if ( false !== $result ) {
- $message = 'Sprzęt dodany.';
- $notice_class = 'notice-success';
- } else {
- $message = 'Błąd podczas dodawania sprzętu: ' . $wpdb->last_error;
- $notice_class = 'notice-error';
- }
- }
- } else {
- $message = 'Nazwa sprzętu nie może być pusta.';
- $notice_class = 'notice-error';
- }
- }
-
- // Handle GET requests (delete)
- if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
- if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_equipment_' . $_GET['id'] ) ) {
- // 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'] ) ) );
- if ( 0 == $usage_count ) {
- $wpdb->delete( $table_equipment, array( 'id' => intval( $_GET['id'] ) ) );
- // Usuń również powiązane wpisy w dzienniku
- $wpdb->delete( $wpdb->prefix . 'statpress_equipment_log', array( 'equipment_id' => intval( $_GET['id'] ) ) );
- $message = 'Sprzęt usunięty.';
- $notice_class = 'notice-success';
- } else {
- $message = 'Nie można usunąć sprzętu, ponieważ jest przypisany do ' . $usage_count . ' aktywności. Zmień jego status na "wycofany".';
- $notice_class = 'notice-error';
- }
- }
- }
-
- // Prepare for form (for editing)
- $item_to_edit = null;
- $statuses = array( 'aktywny', 'sprzedany', 'wycofany' );
- 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'] ) ) );
- }
-
- $table_activities = $wpdb->prefix . 'statpress_activities';
- $equipment_list = $wpdb->get_results("
- SELECT
- eq.id,
- eq.name,
- eq.type,
- eq.status,
- SUM(a.distance) as total_distance,
- COUNT(a.id) as activity_count
- FROM
- {$table_equipment} eq
- LEFT JOIN
- {$table_activities} a ON eq.id = a.equipment_id
- GROUP BY
- eq.id, eq.name, eq.type, eq.status
- ORDER BY
- eq.status ASC, eq.name ASC"
- );
- ?>
-
-
Zarządzaj Sprzętem
-
-
-
-
-
-
-
-
-
- Nazwa Przebieg Liczba aktywności Status Akcje
-
-
- id );
- ?>
-
- name ); ?> type ); ?>
- total_distance ? number_format( $item->total_distance, 2, ',', ' ' ) . ' km' : '0 km'; ?>
- activity_count; ?>
- status ) ); ?>
-
- Dziennik / Serwis
- Edytuj
- Usuń
-
-
-
-
-
-
-
-
-
- Błąd Nie podano ID sprzętu.
';
- return;
- }
-
- $table_equipment = $wpdb->prefix . 'statpress_equipment';
- $table_equipment_log = $wpdb->prefix . 'statpress_equipment_log';
- $table_activities = $wpdb->prefix . 'statpress_activities';
- $message = '';
- $notice_class = '';
-
- // --- Handle Service Log form submissions (add/update/delete) ---
- if ( isset( $_POST['submit_log'] ) && check_admin_referer( 'statpress_manage_equipment_log' ) ) {
- $log_id = isset( $_POST['log_id'] ) ? intval( $_POST['log_id'] ) : 0;
- $log_data = array(
- 'equipment_id' => $equipment_id,
- 'log_date' => sanitize_text_field( $_POST['log_date'] ),
- 'log_type' => sanitize_text_field( $_POST['log_type'] ),
- 'description' => sanitize_textarea_field( $_POST['description'] ),
- 'cost' => empty( $_POST['cost'] ) ? null : floatval( str_replace( ',', '.', $_POST['cost'] ) ),
- 'mileage' => empty( $_POST['mileage'] ) ? null : intval( $_POST['mileage'] ),
- );
-
- if ( ! empty( $log_data['log_date'] ) && ! empty( $log_data['log_type'] ) && ! empty( $log_data['description'] ) ) {
- if ( $log_id > 0 ) {
- $wpdb->update( $table_equipment_log, $log_data, array( 'id' => $log_id ) );
- $message = 'Wpis w dzienniku zaktualizowany.';
- } else {
- $wpdb->insert( $table_equipment_log, $log_data );
- $message = 'Wpis dodany do dziennika.';
- }
- $notice_class = 'notice-success';
- } else {
- $message = 'Wypełnij wymagane pola (Data, Typ, Opis).';
- $notice_class = 'notice-error';
- }
- }
-
- 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'] ) ) {
- $wpdb->delete( $table_equipment_log, array( 'id' => intval( $_GET['log_id'] ) ) );
- $message = 'Wpis z dziennika usunięty.';
- $notice_class = 'notice-success';
- }
- }
-
- // --- Get Data ---
- $equipment = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment WHERE id = %d", $equipment_id ) );
- if ( ! $equipment ) {
- echo 'Błąd Nie znaleziono sprzętu o podanym ID.
';
- return;
- }
-
- $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 ) );
- $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;
- 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_types = array( 'Naprawa', 'Zakup części', 'Przegląd', 'Modyfikacja', 'Inne' );
-
- ?>
-
-
Dziennik serwisowy: name ); ?>
-
← Powrót do listy sprzętu
-
-
-
-
-
-
-
-
-
-
Całkowity przebieg: km
- 0 ) : ?>
-
Całkowity koszt serwisu: zł
-
-
Status: status ) ); ?>
- purchase_date ) : ?>
Data zakupu: purchase_date ); ?>
- initial_cost ) : ?>
Koszt zakupu: initial_cost, 2, ',', ' ' ); ?> zł
- notes ) : ?>
Notatki: notes ) ); ?>
-
-
-
-
-
-
-
-
-
-
-
- Suma kosztów:
- zł
-
-
- Data Typ Opis Koszt Przebieg Akcje
-
-
- Brak wpisów w dzienniku.
-
-
-
- log_date ); ?>
- log_type ); ?>
- description ) ); ?>
- cost ? number_format( $log->cost, 2, ',', ' ' ) . ' zł' : '-'; ?>
- mileage ? number_format( $log->mileage, 0, '', ' ' ) . ' km' : '-'; ?>
-
- Edytuj |
- Usuń
-
-
-
-
-
-
-
-
-
-
- prefix . 'statpress_equipment';
+ $message = '';
+ $notice_class = '';
+
+ // Handle POST requests (add/update)
+ if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_equipment' ) ) {
+ $item_id = isset( $_POST['equipment_id'] ) ? intval( $_POST['equipment_id'] ) : 0;
+ $data = array(
+ 'name' => sanitize_text_field( $_POST['equipment_name'] ),
+ 'type' => sanitize_text_field( $_POST['equipment_type'] ),
+ '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'] ) ),
+ 'status' => sanitize_text_field( $_POST['status'] ),
+ 'notes' => sanitize_textarea_field( $_POST['notes'] ),
+ );
+
+ if ( ! empty( $data['name'] ) ) {
+ if ( $item_id > 0 ) { // Update
+ $result = $wpdb->update( $table_equipment, $data, array( 'id' => $item_id ) );
+ if ( false !== $result ) {
+ $message = 'Sprzęt zaktualizowany.';
+ $notice_class = 'notice-success';
+ } else {
+ $message = 'Błąd podczas aktualizacji sprzętu: ' . $wpdb->last_error;
+ $notice_class = 'notice-error';
+ }
+ } else { // Insert
+ $result = $wpdb->insert( $table_equipment, $data );
+ if ( false !== $result ) {
+ $message = 'Sprzęt dodany.';
+ $notice_class = 'notice-success';
+ } else {
+ $message = 'Błąd podczas dodawania sprzętu: ' . $wpdb->last_error;
+ $notice_class = 'notice-error';
+ }
+ }
+ } else {
+ $message = 'Nazwa sprzętu nie może być pusta.';
+ $notice_class = 'notice-error';
+ }
+ }
+
+ // Handle GET requests (delete)
+ if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
+ if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_equipment_' . $_GET['id'] ) ) {
+ // 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'] ) ) );
+ if ( 0 == $usage_count ) {
+ $wpdb->delete( $table_equipment, array( 'id' => intval( $_GET['id'] ) ) );
+ // Usuń również powiązane wpisy w dzienniku
+ $wpdb->delete( $wpdb->prefix . 'statpress_equipment_log', array( 'equipment_id' => intval( $_GET['id'] ) ) );
+ $message = 'Sprzęt usunięty.';
+ $notice_class = 'notice-success';
+ } else {
+ $message = 'Nie można usunąć sprzętu, ponieważ jest przypisany do ' . $usage_count . ' aktywności. Zmień jego status na "wycofany".';
+ $notice_class = 'notice-error';
+ }
+ }
+ }
+
+ // Prepare for form (for editing)
+ $item_to_edit = null;
+ $statuses = array( 'aktywny', 'sprzedany', 'wycofany' );
+ 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'] ) ) );
+ }
+
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $equipment_list = $wpdb->get_results("
+ SELECT
+ eq.id,
+ eq.name,
+ eq.type,
+ eq.status,
+ SUM(a.distance) as total_distance,
+ COUNT(a.id) as activity_count
+ FROM
+ {$table_equipment} eq
+ LEFT JOIN
+ {$table_activities} a ON eq.id = a.equipment_id
+ GROUP BY
+ eq.id, eq.name, eq.type, eq.status
+ ORDER BY
+ eq.status ASC, eq.name ASC"
+ );
+ ?>
+
+
Zarządzaj Sprzętem
+
+
+
+
+
+
+
+
+
+ Nazwa Przebieg Liczba aktywności Status Akcje
+
+
+ id );
+ ?>
+
+ name ); ?> type ); ?>
+ total_distance ? number_format( $item->total_distance, 2, ',', ' ' ) . ' km' : '0 km'; ?>
+ activity_count; ?>
+ status ) ); ?>
+
+ Dziennik / Serwis
+ Edytuj
+ Usuń
+
+
+
+
+
+
+
+
+
+ Błąd Nie podano ID sprzętu.
';
+ return;
+ }
+
+ $table_equipment = $wpdb->prefix . 'statpress_equipment';
+ $table_equipment_log = $wpdb->prefix . 'statpress_equipment_log';
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $message = '';
+ $notice_class = '';
+
+ // --- Handle Service Log form submissions (add/update/delete) ---
+ if ( isset( $_POST['submit_log'] ) && check_admin_referer( 'statpress_manage_equipment_log' ) ) {
+ $log_id = isset( $_POST['log_id'] ) ? intval( $_POST['log_id'] ) : 0;
+ $log_data = array(
+ 'equipment_id' => $equipment_id,
+ 'log_date' => sanitize_text_field( $_POST['log_date'] ),
+ 'log_type' => sanitize_text_field( $_POST['log_type'] ),
+ 'description' => sanitize_textarea_field( $_POST['description'] ),
+ 'cost' => empty( $_POST['cost'] ) ? null : floatval( str_replace( ',', '.', $_POST['cost'] ) ),
+ 'mileage' => empty( $_POST['mileage'] ) ? null : intval( $_POST['mileage'] ),
+ );
+
+ if ( ! empty( $log_data['log_date'] ) && ! empty( $log_data['log_type'] ) && ! empty( $log_data['description'] ) ) {
+ if ( $log_id > 0 ) {
+ $wpdb->update( $table_equipment_log, $log_data, array( 'id' => $log_id ) );
+ $message = 'Wpis w dzienniku zaktualizowany.';
+ } else {
+ $wpdb->insert( $table_equipment_log, $log_data );
+ $message = 'Wpis dodany do dziennika.';
+ }
+ $notice_class = 'notice-success';
+ } else {
+ $message = 'Wypełnij wymagane pola (Data, Typ, Opis).';
+ $notice_class = 'notice-error';
+ }
+ }
+
+ 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'] ) ) {
+ $wpdb->delete( $table_equipment_log, array( 'id' => intval( $_GET['log_id'] ) ) );
+ $message = 'Wpis z dziennika usunięty.';
+ $notice_class = 'notice-success';
+ }
+ }
+
+ // --- Get Data ---
+ $equipment = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_equipment WHERE id = %d", $equipment_id ) );
+ if ( ! $equipment ) {
+ echo 'Błąd Nie znaleziono sprzętu o podanym ID.
';
+ return;
+ }
+
+ $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 ) );
+ $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;
+ 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_types = array( 'Naprawa', 'Zakup części', 'Przegląd', 'Modyfikacja', 'Inne' );
+
+ ?>
+
+
Dziennik serwisowy: name ); ?>
+
← Powrót do listy sprzętu
+
+
+
+
+
+
+
+
+
+
Całkowity przebieg: km
+ 0 ) : ?>
+
Całkowity koszt serwisu: zł
+
+
Status: status ) ); ?>
+ purchase_date ) : ?>
Data zakupu: purchase_date ); ?>
+ initial_cost ) : ?>
Koszt zakupu: initial_cost, 2, ',', ' ' ); ?> zł
+ notes ) : ?>
Notatki: notes ) ); ?>
+
+
+
+
+
+
+
+
+
+
+
+ Suma kosztów:
+ zł
+
+
+ Data Typ Opis Koszt Przebieg Akcje
+
+
+ Brak wpisów w dzienniku.
+
+
+
+ log_date ); ?>
+ log_type ); ?>
+ description ) ); ?>
+ cost ? number_format( $log->cost, 2, ',', ' ' ) . ' zł' : '-'; ?>
+ mileage ? number_format( $log->mileage, 0, '', ' ' ) . ' km' : '-'; ?>
+
+ Edytuj |
+ Usuń
+
+
+
+
+
+
+
+
+
+
+ prefix . 'statpress_event_types';
- $message = '';
- $notice_class = '';
-
- // Handle POST requests (add/update)
- if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_event_type' ) ) {
- $name = sanitize_text_field( $_POST['event_type_name'] );
- $type_id = isset( $_POST['event_type_id'] ) ? intval( $_POST['event_type_id'] ) : 0;
-
- if ( ! empty( $name ) ) {
- if ( $type_id > 0 ) { // Update
- $wpdb->update( $table_event_types, array( 'name' => $name ), array( 'id' => $type_id ) );
- $message = 'Typ wydarzenia zaktualizowany.';
- $notice_class = 'notice-success';
- } else { // Insert
- $wpdb->insert( $table_event_types, array( 'name' => $name ) );
- $message = 'Typ wydarzenia dodany.';
- $notice_class = 'notice-success';
- }
- } else {
- $message = 'Nazwa typu wydarzenia nie może być pusta.';
- $notice_class = 'notice-error';
- }
- }
-
- // Handle GET requests (delete)
- if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
- if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_event_type_' . $_GET['id'] ) ) {
- $wpdb->delete( $table_event_types, array( 'id' => intval( $_GET['id'] ) ) );
- $message = 'Typ wydarzenia usunięty.';
- $notice_class = 'notice-success';
- }
- }
-
- // Prepare for form (for editing)
- $item_to_edit = null;
- if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) {
- $item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_event_types WHERE id = %d", intval( $_GET['id'] ) ) );
- }
-
- $event_types = $wpdb->get_results( "SELECT * FROM $table_event_types ORDER BY name ASC" );
- ?>
-
-
Typy Wydarzeń
-
-
-
-
-
-
- prefix . 'statpress_event_types';
+ $message = '';
+ $notice_class = '';
+
+ // Handle POST requests (add/update)
+ if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_event_type' ) ) {
+ $name = sanitize_text_field( $_POST['event_type_name'] );
+ $type_id = isset( $_POST['event_type_id'] ) ? intval( $_POST['event_type_id'] ) : 0;
+
+ if ( ! empty( $name ) ) {
+ if ( $type_id > 0 ) { // Update
+ $wpdb->update( $table_event_types, array( 'name' => $name ), array( 'id' => $type_id ) );
+ $message = 'Typ wydarzenia zaktualizowany.';
+ $notice_class = 'notice-success';
+ } else { // Insert
+ $wpdb->insert( $table_event_types, array( 'name' => $name ) );
+ $message = 'Typ wydarzenia dodany.';
+ $notice_class = 'notice-success';
+ }
+ } else {
+ $message = 'Nazwa typu wydarzenia nie może być pusta.';
+ $notice_class = 'notice-error';
+ }
+ }
+
+ // Handle GET requests (delete)
+ if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
+ if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_event_type_' . $_GET['id'] ) ) {
+ $wpdb->delete( $table_event_types, array( 'id' => intval( $_GET['id'] ) ) );
+ $message = 'Typ wydarzenia usunięty.';
+ $notice_class = 'notice-success';
+ }
+ }
+
+ // Prepare for form (for editing)
+ $item_to_edit = null;
+ if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) {
+ $item_to_edit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_event_types WHERE id = %d", intval( $_GET['id'] ) ) );
+ }
+
+ $event_types = $wpdb->get_results( "SELECT * FROM $table_event_types ORDER BY name ASC" );
+ ?>
+
+
Typy Wydarzeń
+
+
+
+
+
+
+ prefix . 'statpress_activities';
-
- $sql_select = '';
- switch ( $goal->goal_type ) {
- case 'distance':
- $sql_select = 'SUM(distance)';
- break;
- case 'duration_sec':
- $sql_select = 'SUM(TIME_TO_SEC(duration))';
- break;
- case 'count':
- $sql_select = 'COUNT(id)';
- break;
- default:
- return array(
- 'current_value' => 0,
- 'percentage' => 0,
- );
- }
-
- $where_clauses = array();
- $where_clauses[] = $wpdb->prepare( 'YEAR(date) = %d', $goal->year );
-
- if ( ! empty( $goal->month ) ) {
- $where_clauses[] = $wpdb->prepare( 'MONTH(date) = %d', $goal->month );
- }
- if ( ! empty( $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 );
-
- $current_value = (float) $wpdb->get_var( $sql );
- $percentage = ( $goal->target_value > 0 ) ? ( $current_value / $goal->target_value ) * 100 : 0;
-
- return array(
- 'current_value' => $current_value,
- 'percentage' => $percentage,
- );
-}
-
-function statpress_goals_page() {
- global $wpdb;
- $table_goals = $wpdb->prefix . 'statpress_goals';
- $table_categories = $wpdb->prefix . 'statpress_categories';
- $message = '';
- $notice_class = '';
-
- // Handle POST requests (add/update)
- if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_goal' ) ) {
- $goal_id = isset( $_POST['goal_id'] ) ? intval( $_POST['goal_id'] ) : 0;
-
- // Convert hours to seconds for duration goal type before saving
- $target_value = floatval( str_replace( ',', '.', $_POST['target_value'] ) );
- if ( 'duration_sec' === $_POST['goal_type'] ) {
- $target_value *= 3600;
- }
-
- $data = array(
- 'name' => sanitize_text_field( $_POST['goal_name'] ),
- 'goal_type' => sanitize_text_field( $_POST['goal_type'] ),
- 'target_value' => $target_value,
- 'year' => intval( $_POST['year'] ),
- 'month' => 'all' === $_POST['month'] ? null : intval( $_POST['month'] ),
- '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 ( $goal_id > 0 ) { // Update
- $wpdb->update( $table_goals, $data, array( 'id' => $goal_id ) );
- $message = 'Cel zaktualizowany.';
- $notice_class = 'notice-success';
- } else { // Insert
- $wpdb->insert( $table_goals, $data );
- $message = 'Cel dodany.';
- $notice_class = 'notice-success';
- }
- } else {
- $message = 'Wypełnij poprawnie wszystkie wymagane pola (Nazwa, Typ, Cel, Rok).';
- $notice_class = 'notice-error';
- }
- }
-
- // Handle GET requests (delete)
- if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
- if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_goal_' . $_GET['id'] ) ) {
- $wpdb->delete( $table_goals, array( 'id' => intval( $_GET['id'] ) ) );
- $message = 'Cel usunięty.';
- $notice_class = 'notice-success';
- }
- }
-
- // Prepare for form (for editing)
- $item_to_edit = null;
- 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'] ) ) );
- }
-
- $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" );
- ?>
-
-
Zarządzaj Celami
-
-
-
-
-
-
-
-
-
- Cel Postęp Akcje
-
-
- Brak zdefiniowanych celów.
-
-
- goal_type ) {
- $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 ) );
- } elseif ( 'distance' === $goal->goal_type ) {
- $target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km';
- $current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km';
- } else { // count
- $target_formatted = (int) $goal->target_value;
- $current_formatted = (int) $progress['current_value'];
- }
- ?>
-
-
- name ); ?>
-
- year ); ?>
- month ) { echo ' / ' . date_i18n( 'F', mktime( 0, 0, 0, $goal->month, 10 ) ); } ?>
- category_name ) { echo ' / ' . esc_html( $goal->category_name ); } ?>
-
-
-
-
- z (%)
-
-
- Edytuj |
- Usuń
-
-
-
-
-
-
-
-
-
-
-
- prefix . 'statpress_activities';
+
+ $sql_select = '';
+ switch ( $goal->goal_type ) {
+ case 'distance':
+ $sql_select = 'SUM(distance)';
+ break;
+ case 'duration_sec':
+ $sql_select = 'SUM(TIME_TO_SEC(duration))';
+ break;
+ case 'count':
+ $sql_select = 'COUNT(id)';
+ break;
+ default:
+ return array(
+ 'current_value' => 0,
+ 'percentage' => 0,
+ );
+ }
+
+ $where_clauses = array();
+ $where_clauses[] = $wpdb->prepare( 'YEAR(date) = %d', $goal->year );
+
+ if ( ! empty( $goal->month ) ) {
+ $where_clauses[] = $wpdb->prepare( 'MONTH(date) = %d', $goal->month );
+ }
+ if ( ! empty( $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 );
+
+ $current_value = (float) $wpdb->get_var( $sql );
+ $percentage = ( $goal->target_value > 0 ) ? ( $current_value / $goal->target_value ) * 100 : 0;
+
+ return array(
+ 'current_value' => $current_value,
+ 'percentage' => $percentage,
+ );
+}
+
+function statpress_goals_page() {
+ global $wpdb;
+ $table_goals = $wpdb->prefix . 'statpress_goals';
+ $table_categories = $wpdb->prefix . 'statpress_categories';
+ $message = '';
+ $notice_class = '';
+
+ // Handle POST requests (add/update)
+ if ( isset( $_POST['submit'] ) && check_admin_referer( 'statpress_manage_goal' ) ) {
+ $goal_id = isset( $_POST['goal_id'] ) ? intval( $_POST['goal_id'] ) : 0;
+
+ // Convert hours to seconds for duration goal type before saving
+ $target_value = floatval( str_replace( ',', '.', $_POST['target_value'] ) );
+ if ( 'duration_sec' === $_POST['goal_type'] ) {
+ $target_value *= 3600;
+ }
+
+ $data = array(
+ 'name' => sanitize_text_field( $_POST['goal_name'] ),
+ 'goal_type' => sanitize_text_field( $_POST['goal_type'] ),
+ 'target_value' => $target_value,
+ 'year' => intval( $_POST['year'] ),
+ 'month' => 'all' === $_POST['month'] ? null : intval( $_POST['month'] ),
+ '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 ( $goal_id > 0 ) { // Update
+ $wpdb->update( $table_goals, $data, array( 'id' => $goal_id ) );
+ $message = 'Cel zaktualizowany.';
+ $notice_class = 'notice-success';
+ } else { // Insert
+ $wpdb->insert( $table_goals, $data );
+ $message = 'Cel dodany.';
+ $notice_class = 'notice-success';
+ }
+ } else {
+ $message = 'Wypełnij poprawnie wszystkie wymagane pola (Nazwa, Typ, Cel, Rok).';
+ $notice_class = 'notice-error';
+ }
+ }
+
+ // Handle GET requests (delete)
+ if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' === $_GET['action'] ) {
+ if ( wp_verify_nonce( $_GET['_wpnonce'], 'statpress_delete_goal_' . $_GET['id'] ) ) {
+ $wpdb->delete( $table_goals, array( 'id' => intval( $_GET['id'] ) ) );
+ $message = 'Cel usunięty.';
+ $notice_class = 'notice-success';
+ }
+ }
+
+ // Prepare for form (for editing)
+ $item_to_edit = null;
+ 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'] ) ) );
+ }
+
+ $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" );
+ ?>
+
+
Zarządzaj Celami
+
+
+
+
+
+
+
+
+
+ Cel Postęp Akcje
+
+
+ Brak zdefiniowanych celów.
+
+
+ goal_type ) {
+ $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 ) );
+ } elseif ( 'distance' === $goal->goal_type ) {
+ $target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km';
+ $current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km';
+ } else { // count
+ $target_formatted = (int) $goal->target_value;
+ $current_formatted = (int) $progress['current_value'];
+ }
+ ?>
+
+
+ name ); ?>
+
+ year ); ?>
+ month ) { echo ' / ' . date_i18n( 'F', mktime( 0, 0, 0, $goal->month, 10 ) ); } ?>
+ category_name ) { echo ' / ' . esc_html( $goal->category_name ); } ?>
+
+
+
+
+ z (%)
+
+
+ Edytuj |
+ Usuń
+
+
+
+
+
+
+
+
+
+
+
+ Importuj aktywności z pliku CSV ';
-
- // Handle the form submission
- if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['statpress_csv_import_nonce_field'] ) ) {
- statpress_handle_csv_import();
- }
-
- // Display the form
- ?>
-
-
-
-
Aby zaimportować dane, możesz wgrać plik CSV LUB wkleić dane bezpośrednio w pole tekstowe poniżej. Ta druga opcja jest zalecana, jeśli napotykasz błędy z plikiem.
-
Wymagane kolumny: Data, Tytuł, Dystans oraz Typ aktywności (lub Kategoria).
-
Opcjonalne kolumny: Czas (format HH:MM:SS), Kalorie, Średnie tętno, Maksymalne tętno, Średnia prędkość, Maksymalna prędkość, Średni rytm pedałowania, Maksymalny rytm pedałowania, Całkowity wznios, Całkowity spadek, Minimalna wysokość, Maksymalna wysokość, Sprzęt, Typ wydarzenia.
-
Ważne:
-
- Data musi być w formacie YYYY-MM-DD.
- Dystans i prędkość: użyj kropki jako separatora dziesiętnego (np. 10.5).
- Nazwy w kolumnach Typ aktywności, Sprzęt, Typ wydarzenia muszą dokładnie odpowiadać nazwom zdefiniowanym w ustawieniach wtyczki. Jeśli nazwa nie zostanie znaleziona, wiersz zostanie pominięty.
-
-
-
-
-
-
-
-
-
-
- ';
-}
-
-function statpress_handle_csv_import() {
- global $wpdb;
-
- if ( ! isset( $_POST['statpress_csv_import_nonce_field'] ) || ! wp_verify_nonce( $_POST['statpress_csv_import_nonce_field'], 'statpress_csv_import_nonce' ) ) {
- echo 'Błąd weryfikacji bezpieczeństwa.
';
- return;
- }
-
- if ( ! current_user_can( 'manage_options' ) ) {
- echo 'Nie masz wystarczających uprawnień.
';
- return;
- }
-
- // Unify input source: prefer textarea, fall back to file upload.
- $csv_content = '';
- if ( ! empty( $_POST['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'] ) {
- $csv_content = file_get_contents( $_FILES['statpress_csv_file']['tmp_name'] );
- }
-
- if ( empty( trim( $csv_content ) ) ) {
- echo 'Nie podano danych do importu. Wgraj plik lub wklej dane w pole tekstowe.
';
- return;
- }
-
- // Mapowanie polskich i angielskich nazw kolumn na wewnętrzne klucze
- $column_map = array(
- // Polish => English
- 'typ aktywności' => 'category_name',
- 'data' => 'date',
- 'tytuł' => 'title',
- 'dystans' => 'distance',
- 'kalorie' => 'calories',
- 'czas' => 'duration',
- 'średnie tętno' => 'avg_heart_rate',
- 'maksymalne tętno' => 'max_heart_rate',
- 'średnia prędkość' => 'avg_speed',
- 'maksymalna prędkość' => 'max_speed',
- 'średni rytm pedałowania' => 'avg_cadence',
- 'maksymalny rytm pedałowania' => 'max_cadence',
- 'całkowity wznios' => 'total_elevation_gain',
- 'całkowity spadek' => 'total_elevation_loss',
- 'minimalna wysokość' => 'min_altitude',
- 'maksymalna wysokość' => 'max_altitude',
- 'sprzęt' => 'equipment_name',
- 'typ wydarzenia' => 'event_type_name',
- 'komentarz' => 'comment',
- 'link do strava' => 'strava_url',
- // English keys for compatibility
- 'category_name' => 'category_name',
- 'date' => 'date',
- 'title' => 'title',
- 'distance' => 'distance',
- 'calories' => 'calories',
- 'duration' => 'duration',
- 'avg_heart_rate' => 'avg_heart_rate',
- 'max_heart_rate' => 'max_heart_rate',
- 'avg_speed' => 'avg_speed',
- 'max_speed' => 'max_speed',
- 'avg_cadence' => 'avg_cadence',
- 'max_cadence' => 'max_cadence',
- 'total_elevation_gain' => 'total_elevation_gain',
- 'total_elevation_loss' => 'total_elevation_loss',
- 'min_altitude' => 'min_altitude',
- 'max_altitude' => 'max_altitude',
- 'equipment_name' => 'equipment_name',
- 'event_type_name' => 'event_type_name',
- 'comment' => 'comment',
- 'strava_url' => 'strava_url',
- );
-
- // --- START: Robust, case-insensitive lookup ---
- $table_categories = $wpdb->prefix . 'statpress_categories';
- $table_event_types = $wpdb->prefix . 'statpress_event_types';
- $table_equipment = $wpdb->prefix . 'statpress_equipment';
-
- $create_lookup = function( $table_name ) use ( $wpdb ) {
- $items = $wpdb->get_results( "SELECT id, name FROM {$table_name}" );
- $lookup = array();
- if ( is_array( $items ) ) {
- foreach ( $items as $item ) {
- // Use a robust trim to handle various whitespace characters and make it case-insensitive
- $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $item->name );
- $lookup[ mb_strtolower( $clean_name, 'UTF-8' ) ] = $item->id;
- }
- }
- return $lookup;
- };
-
- $categories_lookup = $create_lookup( $table_categories );
- $event_types_lookup = $create_lookup( $table_event_types );
- $equipment_lookup = $create_lookup( $table_equipment );
- // --- END: Robust, case-insensitive lookup ---
-
- // Process the CSV file
- $table_activities = $wpdb->prefix . 'statpress_activities';
- $imported_count = 0;
- $skipped_details = array();
- $row_number = 1; // Header is row 1
-
- // Normalize line endings and split into lines
- $lines = str_replace( array( "\r\n", "\r" ), "\n", $csv_content );
- $lines = explode( "\n", $lines );
-
- if ( empty( $lines ) || empty( trim( $lines[0] ) ) ) {
- echo 'Podane dane CSV są puste.
';
- return;
- }
-
- // --- Delimiter and BOM detection ---
- $first_line = $lines[0];
- $delimiter = ( substr_count( $first_line, ';' ) > substr_count( $first_line, ',' ) ) ? ';' : ',';
-
- // --- BOM removal from first header element ---
- $bom = "\xEF\xBB\xBF";
- if ( substr( $first_line, 0, 3 ) === $bom ) {
- $lines[0] = substr( $first_line, 3 );
- }
-
- $header_raw = str_getcsv( array_shift( $lines ), $delimiter );
- $header_raw = array_map( 'trim', $header_raw );
-
- // Translate header from Polish/English to internal keys
- $header = array();
- foreach ( $header_raw as $col ) {
- $header[] = $column_map[ strtolower( $col ) ] ?? 'ignored_' . uniqid();
- }
-
- $required_internal_keys = array( 'date', 'title', 'category_name', 'distance' );
- $missing_keys = array_diff( $required_internal_keys, $header );
- if ( ! empty( $missing_keys ) ) {
- $key_to_polish_map = array(
- 'date' => 'Data',
- 'title' => 'Tytuł',
- 'category_name' => 'Kategoria / Typ aktywności',
- 'distance' => 'Dystans',
- );
- $missing_polish_names = array_map( fn( $key ) => $key_to_polish_map[ $key ] ?? $key, $missing_keys );
- echo 'Brak wymaganych kolumn w danych CSV: ' . esc_html( implode( ', ', $missing_polish_names ) ) . '
';
- return;
- }
-
- $parse_and_round_int = fn( $val ) => round( floatval( str_replace( ',', '.', $val ) ) );
- $null_if_empty = fn( $value ) => '' !== $value ? $value : null;
-
- foreach ( $lines as $line ) {
- $row_number++;
- if ( empty( trim( $line ) ) ) {
- continue; // Skip empty lines
- }
- $data = str_getcsv( $line, $delimiter );
-
- if ( count( $data ) !== count( $header ) ) {
- $skipped_details[] = array(
- 'row' => $row_number,
- 'reason' => 'Nieprawidłowa liczba kolumn (oczekiwano ' . count( $header ) . ', otrzymano ' . count( $data ) . ').',
- 'data' => $line,
- );
- continue;
- }
- $row_data = array_combine( $header, $data );
-
- // Detailed validation
- $validation_errors = array();
- if ( empty( $row_data['date'] ) ) {
- $validation_errors[] = 'brak daty';
- }
- if ( empty( $row_data['title'] ) ) {
- $validation_errors[] = 'brak tytułu';
- }
- if ( ! isset( $row_data['distance'] ) || '' === $row_data['distance'] ) {
- $validation_errors[] = 'brak dystansu';
- }
-
- $category_name = $row_data['category_name'] ?? '';
- $clean_category_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $category_name );
- $category_id = $categories_lookup[ mb_strtolower( $clean_category_name, 'UTF-8' ) ] ?? null;
-
- if ( empty( $clean_category_name ) ) {
- $validation_errors[] = 'brak nazwy kategorii';
- } elseif ( is_null( $category_id ) ) {
- $available_categories_from_db = $wpdb->get_col( "SELECT name FROM $table_categories ORDER BY name" );
- $validation_errors[] = 'nieznana kategoria: "' . esc_html( $category_name ) . '". Sprawdź, czy nazwa jest poprawna. Dostępne w bazie: "' . esc_html( implode( '", "', $available_categories_from_db ) ) . '".';
- }
-
- if ( ! empty( $validation_errors ) ) {
- $skipped_details[] = array(
- 'row' => $row_number,
- 'reason' => ucfirst( implode( ', ', $validation_errors ) ) . '.',
- 'data' => $line,
- );
- continue;
- }
-
- // Get IDs for optional fields using the same robust method
- $get_id = function( $name, $lookup_table ) {
- $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $name );
- return $lookup_table[ mb_strtolower( $clean_name, 'UTF-8' ) ] ?? null;
- };
-
- $equipment_id = $get_id( $row_data['equipment_name'] ?? '', $equipment_lookup );
- $event_type_id = $get_id( $row_data['event_type_name'] ?? '', $event_types_lookup );
-
- $insert_data = array(
- 'date' => sanitize_text_field( $row_data['date'] ),
- 'title' => sanitize_text_field( $row_data['title'] ),
- 'category_id' => $category_id,
- 'distance' => floatval( str_replace( ',', '.', $row_data['distance'] ) ),
- 'duration' => isset( $row_data['duration'] ) ? sanitize_text_field( $row_data['duration'] ) : '00:00:00',
- 'calories' => isset( $row_data['calories'] ) ? intval( str_replace( ',', '.', $row_data['calories'] ) ) : 0,
- 'comment' => isset( $row_data['comment'] ) ? sanitize_textarea_field( $row_data['comment'] ) : null,
- 'strava_url' => isset( $row_data['strava_url'] ) ? $null_if_empty( esc_url_raw( $row_data['strava_url'] ) ) : null,
- 'avg_heart_rate' => isset( $row_data['avg_heart_rate'] ) ? $null_if_empty( $parse_and_round_int( $row_data['avg_heart_rate'] ) ) : null,
- 'max_heart_rate' => isset( $row_data['max_heart_rate'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_heart_rate'] ) ) : null,
- 'avg_speed' => isset( $row_data['avg_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $row_data['avg_speed'] ) ) ) : null,
- 'max_speed' => isset( $row_data['max_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $row_data['max_speed'] ) ) ) : null,
- 'avg_cadence' => isset( $row_data['avg_cadence'] ) ? $null_if_empty( $parse_and_round_int( $row_data['avg_cadence'] ) ) : null,
- 'max_cadence' => isset( $row_data['max_cadence'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_cadence'] ) ) : null,
- 'total_elevation_gain' => isset( $row_data['total_elevation_gain'] ) ? $null_if_empty( $parse_and_round_int( $row_data['total_elevation_gain'] ) ) : null,
- 'total_elevation_loss' => isset( $row_data['total_elevation_loss'] ) ? $null_if_empty( $parse_and_round_int( $row_data['total_elevation_loss'] ) ) : null,
- 'min_altitude' => isset( $row_data['min_altitude'] ) ? $null_if_empty( $parse_and_round_int( $row_data['min_altitude'] ) ) : null,
- 'max_altitude' => isset( $row_data['max_altitude'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_altitude'] ) ) : null,
- 'equipment_id' => $equipment_id,
- 'event_type_id' => $event_type_id,
- );
-
- if ( $wpdb->insert( $table_activities, $insert_data ) ) {
- $imported_count++;
- } else {
- $skipped_details[] = array(
- 'row' => $row_number,
- 'reason' => 'Błąd zapisu do bazy danych. (' . esc_html( $wpdb->last_error ) . ')',
- 'data' => $line,
- );
- }
- }
-
- if ( $imported_count > 0 ) {
- echo 'Pomyślnie zaimportowano ' . esc_html( $imported_count ) . ' aktywności.
';}
- if ( ! empty( $skipped_details ) ) {
- echo '';
- echo '
Pominięto ' . count( $skipped_details ) . ' wierszy z powodu błędów ';
- echo '
';
- echo '
Wiersz Powód pominięcia Dane wiersza ';
- foreach ( $skipped_details as $error ) {
- echo '';
- echo '' . esc_html( $error['row'] ) . ' ';
- echo '' . esc_html( $error['reason'] ) . ' ';
- echo '' . esc_html( wp_trim_words( $error['data'], 25, '...' ) ) . ' ';
- echo ' ';
- }
- echo '
';
- echo '
';
- }
- if ( 0 === $imported_count && empty( $skipped_details ) && $row_number > 1 ) {
- echo 'Dane CSV nie zawierały żadnych poprawnych wierszy do importu.
';
- } elseif ( 1 === $row_number ) {
- echo 'Dane CSV były puste lub zawierały tylko nagłówek.
';}
+Importuj aktywności z pliku CSV ';
+
+ // Handle the form submission
+ if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['statpress_csv_import_nonce_field'] ) ) {
+ statpress_handle_csv_import();
+ }
+
+ // Display the form
+ ?>
+
+
+
+
Aby zaimportować dane, możesz wgrać plik CSV LUB wkleić dane bezpośrednio w pole tekstowe poniżej. Ta druga opcja jest zalecana, jeśli napotykasz błędy z plikiem.
+
Wymagane kolumny: Data, Tytuł, Dystans oraz Typ aktywności (lub Kategoria).
+
Opcjonalne kolumny: Czas (format HH:MM:SS), Kalorie, Średnie tętno, Maksymalne tętno, Średnia prędkość, Maksymalna prędkość, Średni rytm pedałowania, Maksymalny rytm pedałowania, Całkowity wznios, Całkowity spadek, Minimalna wysokość, Maksymalna wysokość, Sprzęt, Typ wydarzenia.
+
Ważne:
+
+ Data musi być w formacie YYYY-MM-DD.
+ Dystans i prędkość: użyj kropki jako separatora dziesiętnego (np. 10.5).
+ Nazwy w kolumnach Typ aktywności, Sprzęt, Typ wydarzenia muszą dokładnie odpowiadać nazwom zdefiniowanym w ustawieniach wtyczki. Jeśli nazwa nie zostanie znaleziona, wiersz zostanie pominięty.
+
+
+
+
+
+
+
+
+
+
+ ';
+}
+
+function statpress_handle_csv_import() {
+ global $wpdb;
+
+ if ( ! isset( $_POST['statpress_csv_import_nonce_field'] ) || ! wp_verify_nonce( $_POST['statpress_csv_import_nonce_field'], 'statpress_csv_import_nonce' ) ) {
+ echo 'Błąd weryfikacji bezpieczeństwa.
';
+ return;
+ }
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ echo 'Nie masz wystarczających uprawnień.
';
+ return;
+ }
+
+ // Unify input source: prefer textarea, fall back to file upload.
+ $csv_content = '';
+ if ( ! empty( $_POST['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'] ) {
+ $csv_content = file_get_contents( $_FILES['statpress_csv_file']['tmp_name'] );
+ }
+
+ if ( empty( trim( $csv_content ) ) ) {
+ echo 'Nie podano danych do importu. Wgraj plik lub wklej dane w pole tekstowe.
';
+ return;
+ }
+
+ // Mapowanie polskich i angielskich nazw kolumn na wewnętrzne klucze
+ $column_map = array(
+ // Polish => English
+ 'typ aktywności' => 'category_name',
+ 'data' => 'date',
+ 'tytuł' => 'title',
+ 'dystans' => 'distance',
+ 'kalorie' => 'calories',
+ 'czas' => 'duration',
+ 'średnie tętno' => 'avg_heart_rate',
+ 'maksymalne tętno' => 'max_heart_rate',
+ 'średnia prędkość' => 'avg_speed',
+ 'maksymalna prędkość' => 'max_speed',
+ 'średni rytm pedałowania' => 'avg_cadence',
+ 'maksymalny rytm pedałowania' => 'max_cadence',
+ 'całkowity wznios' => 'total_elevation_gain',
+ 'całkowity spadek' => 'total_elevation_loss',
+ 'minimalna wysokość' => 'min_altitude',
+ 'maksymalna wysokość' => 'max_altitude',
+ 'sprzęt' => 'equipment_name',
+ 'typ wydarzenia' => 'event_type_name',
+ 'komentarz' => 'comment',
+ 'link do strava' => 'strava_url',
+ // English keys for compatibility
+ 'category_name' => 'category_name',
+ 'date' => 'date',
+ 'title' => 'title',
+ 'distance' => 'distance',
+ 'calories' => 'calories',
+ 'duration' => 'duration',
+ 'avg_heart_rate' => 'avg_heart_rate',
+ 'max_heart_rate' => 'max_heart_rate',
+ 'avg_speed' => 'avg_speed',
+ 'max_speed' => 'max_speed',
+ 'avg_cadence' => 'avg_cadence',
+ 'max_cadence' => 'max_cadence',
+ 'total_elevation_gain' => 'total_elevation_gain',
+ 'total_elevation_loss' => 'total_elevation_loss',
+ 'min_altitude' => 'min_altitude',
+ 'max_altitude' => 'max_altitude',
+ 'equipment_name' => 'equipment_name',
+ 'event_type_name' => 'event_type_name',
+ 'comment' => 'comment',
+ 'strava_url' => 'strava_url',
+ );
+
+ // --- START: Robust, case-insensitive lookup ---
+ $table_categories = $wpdb->prefix . 'statpress_categories';
+ $table_event_types = $wpdb->prefix . 'statpress_event_types';
+ $table_equipment = $wpdb->prefix . 'statpress_equipment';
+
+ $create_lookup = function( $table_name ) use ( $wpdb ) {
+ $items = $wpdb->get_results( "SELECT id, name FROM {$table_name}" );
+ $lookup = array();
+ if ( is_array( $items ) ) {
+ foreach ( $items as $item ) {
+ // Use a robust trim to handle various whitespace characters and make it case-insensitive
+ $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $item->name );
+ $lookup[ mb_strtolower( $clean_name, 'UTF-8' ) ] = $item->id;
+ }
+ }
+ return $lookup;
+ };
+
+ $categories_lookup = $create_lookup( $table_categories );
+ $event_types_lookup = $create_lookup( $table_event_types );
+ $equipment_lookup = $create_lookup( $table_equipment );
+ // --- END: Robust, case-insensitive lookup ---
+
+ // Process the CSV file
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $imported_count = 0;
+ $skipped_details = array();
+ $row_number = 1; // Header is row 1
+
+ // Normalize line endings and split into lines
+ $lines = str_replace( array( "\r\n", "\r" ), "\n", $csv_content );
+ $lines = explode( "\n", $lines );
+
+ if ( empty( $lines ) || empty( trim( $lines[0] ) ) ) {
+ echo 'Podane dane CSV są puste.
';
+ return;
+ }
+
+ // --- Delimiter and BOM detection ---
+ $first_line = $lines[0];
+ $delimiter = ( substr_count( $first_line, ';' ) > substr_count( $first_line, ',' ) ) ? ';' : ',';
+
+ // --- BOM removal from first header element ---
+ $bom = "\xEF\xBB\xBF";
+ if ( substr( $first_line, 0, 3 ) === $bom ) {
+ $lines[0] = substr( $first_line, 3 );
+ }
+
+ $header_raw = str_getcsv( array_shift( $lines ), $delimiter );
+ $header_raw = array_map( 'trim', $header_raw );
+
+ // Translate header from Polish/English to internal keys
+ $header = array();
+ foreach ( $header_raw as $col ) {
+ $header[] = $column_map[ strtolower( $col ) ] ?? 'ignored_' . uniqid();
+ }
+
+ $required_internal_keys = array( 'date', 'title', 'category_name', 'distance' );
+ $missing_keys = array_diff( $required_internal_keys, $header );
+ if ( ! empty( $missing_keys ) ) {
+ $key_to_polish_map = array(
+ 'date' => 'Data',
+ 'title' => 'Tytuł',
+ 'category_name' => 'Kategoria / Typ aktywności',
+ 'distance' => 'Dystans',
+ );
+ $missing_polish_names = array_map( fn( $key ) => $key_to_polish_map[ $key ] ?? $key, $missing_keys );
+ echo 'Brak wymaganych kolumn w danych CSV: ' . esc_html( implode( ', ', $missing_polish_names ) ) . '
';
+ return;
+ }
+
+ $parse_and_round_int = fn( $val ) => round( floatval( str_replace( ',', '.', $val ) ) );
+ $null_if_empty = fn( $value ) => '' !== $value ? $value : null;
+
+ foreach ( $lines as $line ) {
+ $row_number++;
+ if ( empty( trim( $line ) ) ) {
+ continue; // Skip empty lines
+ }
+ $data = str_getcsv( $line, $delimiter );
+
+ if ( count( $data ) !== count( $header ) ) {
+ $skipped_details[] = array(
+ 'row' => $row_number,
+ 'reason' => 'Nieprawidłowa liczba kolumn (oczekiwano ' . count( $header ) . ', otrzymano ' . count( $data ) . ').',
+ 'data' => $line,
+ );
+ continue;
+ }
+ $row_data = array_combine( $header, $data );
+
+ // Detailed validation
+ $validation_errors = array();
+ if ( empty( $row_data['date'] ) ) {
+ $validation_errors[] = 'brak daty';
+ }
+ if ( empty( $row_data['title'] ) ) {
+ $validation_errors[] = 'brak tytułu';
+ }
+ if ( ! isset( $row_data['distance'] ) || '' === $row_data['distance'] ) {
+ $validation_errors[] = 'brak dystansu';
+ }
+
+ $category_name = $row_data['category_name'] ?? '';
+ $clean_category_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $category_name );
+ $category_id = $categories_lookup[ mb_strtolower( $clean_category_name, 'UTF-8' ) ] ?? null;
+
+ if ( empty( $clean_category_name ) ) {
+ $validation_errors[] = 'brak nazwy kategorii';
+ } elseif ( is_null( $category_id ) ) {
+ $available_categories_from_db = $wpdb->get_col( "SELECT name FROM $table_categories ORDER BY name" );
+ $validation_errors[] = 'nieznana kategoria: "' . esc_html( $category_name ) . '". Sprawdź, czy nazwa jest poprawna. Dostępne w bazie: "' . esc_html( implode( '", "', $available_categories_from_db ) ) . '".';
+ }
+
+ if ( ! empty( $validation_errors ) ) {
+ $skipped_details[] = array(
+ 'row' => $row_number,
+ 'reason' => ucfirst( implode( ', ', $validation_errors ) ) . '.',
+ 'data' => $line,
+ );
+ continue;
+ }
+
+ // Get IDs for optional fields using the same robust method
+ $get_id = function( $name, $lookup_table ) {
+ $clean_name = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $name );
+ return $lookup_table[ mb_strtolower( $clean_name, 'UTF-8' ) ] ?? null;
+ };
+
+ $equipment_id = $get_id( $row_data['equipment_name'] ?? '', $equipment_lookup );
+ $event_type_id = $get_id( $row_data['event_type_name'] ?? '', $event_types_lookup );
+
+ $insert_data = array(
+ 'date' => sanitize_text_field( $row_data['date'] ),
+ 'title' => sanitize_text_field( $row_data['title'] ),
+ 'category_id' => $category_id,
+ 'distance' => floatval( str_replace( ',', '.', $row_data['distance'] ) ),
+ 'duration' => isset( $row_data['duration'] ) ? sanitize_text_field( $row_data['duration'] ) : '00:00:00',
+ 'calories' => isset( $row_data['calories'] ) ? intval( str_replace( ',', '.', $row_data['calories'] ) ) : 0,
+ 'comment' => isset( $row_data['comment'] ) ? sanitize_textarea_field( $row_data['comment'] ) : null,
+ 'strava_url' => isset( $row_data['strava_url'] ) ? $null_if_empty( esc_url_raw( $row_data['strava_url'] ) ) : null,
+ 'avg_heart_rate' => isset( $row_data['avg_heart_rate'] ) ? $null_if_empty( $parse_and_round_int( $row_data['avg_heart_rate'] ) ) : null,
+ 'max_heart_rate' => isset( $row_data['max_heart_rate'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_heart_rate'] ) ) : null,
+ 'avg_speed' => isset( $row_data['avg_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $row_data['avg_speed'] ) ) ) : null,
+ 'max_speed' => isset( $row_data['max_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $row_data['max_speed'] ) ) ) : null,
+ 'avg_cadence' => isset( $row_data['avg_cadence'] ) ? $null_if_empty( $parse_and_round_int( $row_data['avg_cadence'] ) ) : null,
+ 'max_cadence' => isset( $row_data['max_cadence'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_cadence'] ) ) : null,
+ 'total_elevation_gain' => isset( $row_data['total_elevation_gain'] ) ? $null_if_empty( $parse_and_round_int( $row_data['total_elevation_gain'] ) ) : null,
+ 'total_elevation_loss' => isset( $row_data['total_elevation_loss'] ) ? $null_if_empty( $parse_and_round_int( $row_data['total_elevation_loss'] ) ) : null,
+ 'min_altitude' => isset( $row_data['min_altitude'] ) ? $null_if_empty( $parse_and_round_int( $row_data['min_altitude'] ) ) : null,
+ 'max_altitude' => isset( $row_data['max_altitude'] ) ? $null_if_empty( $parse_and_round_int( $row_data['max_altitude'] ) ) : null,
+ 'equipment_id' => $equipment_id,
+ 'event_type_id' => $event_type_id,
+ );
+
+ if ( $wpdb->insert( $table_activities, $insert_data ) ) {
+ $imported_count++;
+ } else {
+ $skipped_details[] = array(
+ 'row' => $row_number,
+ 'reason' => 'Błąd zapisu do bazy danych. (' . esc_html( $wpdb->last_error ) . ')',
+ 'data' => $line,
+ );
+ }
+ }
+
+ if ( $imported_count > 0 ) {
+ echo 'Pomyślnie zaimportowano ' . esc_html( $imported_count ) . ' aktywności.
';}
+ if ( ! empty( $skipped_details ) ) {
+ echo '';
+ echo '
Pominięto ' . count( $skipped_details ) . ' wierszy z powodu błędów ';
+ echo '
';
+ echo '
Wiersz Powód pominięcia Dane wiersza ';
+ foreach ( $skipped_details as $error ) {
+ echo '';
+ echo '' . esc_html( $error['row'] ) . ' ';
+ echo '' . esc_html( $error['reason'] ) . ' ';
+ echo '' . esc_html( wp_trim_words( $error['data'], 25, '...' ) ) . ' ';
+ echo ' ';
+ }
+ echo '
';
+ echo '
';
+ }
+ if ( 0 === $imported_count && empty( $skipped_details ) && $row_number > 1 ) {
+ echo 'Dane CSV nie zawierały żadnych poprawnych wierszy do importu.
';
+ } elseif ( 1 === $row_number ) {
+ echo 'Dane CSV były puste lub zawierały tylko nagłówek.
';}
}
\ No newline at end of file
diff --git a/includes/admin/pages/page-infographic.php b/includes/admin/pages/page-infographic.php
index 09e0003..bad342f 100644
--- a/includes/admin/pages/page-infographic.php
+++ b/includes/admin/pages/page-infographic.php
@@ -1,159 +1,159 @@
-prefix . 'statpress_activities';
- $table_categories = $wpdb->prefix . 'statpress_categories';
-
- $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' );
-
- // Pobierz dostępne lata z bazy danych
- $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" );
- if ( empty( $available_years ) ) {
- $available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych
- }
-
- // --- 1. Statystyki ogólne (wszystkie lata) ---
- $overall_stats = $wpdb->get_row(
- "
- SELECT
- SUM(distance) as total_distance,
- SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration,
- SUM(total_elevation_gain) as total_elevation_gain,
- COUNT(id) as total_activities
- FROM $table_activities
- "
- );
-
- // --- 2. Statystyki dla wybranego roku ---
- $yearly_stats = $wpdb->get_row(
- $wpdb->prepare(
- "
- SELECT
- SUM(distance) as total_distance,
- SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration,
- SUM(total_elevation_gain) as total_elevation_gain,
- COUNT(id) as total_activities
- FROM $table_activities
- WHERE YEAR(date) = %d
- ",
- $current_year
- )
- );
-
- // --- 3. Dane dla wykresu kołowego (dystans per kategoria dla wybranego roku) ---
- $category_distance_data = $wpdb->get_results(
- $wpdb->prepare(
- "
- SELECT c.name as category_name, c.color, SUM(a.distance) as total_distance
- FROM $table_activities a
- LEFT JOIN $table_categories c ON a.category_id = c.id
- WHERE YEAR(a.date) = %d
- GROUP BY c.name, c.color
- HAVING SUM(a.distance) > 0
- ORDER BY total_distance DESC
- ",
- $current_year
- )
- );
-
- $chart_labels = array();
- $chart_data = array();
- $chart_colors = array();
- foreach ( $category_distance_data as $data ) {
- $chart_labels[] = $data->category_name;
- $chart_data[] = round( (float) $data->total_distance, 2 );
- $chart_colors[] = $data->color;
- }
-
- // Włączenie skryptów dla Chart.js
- wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
- wp_register_script( 'statpress-infographic-chart-loader', false );
- wp_enqueue_script( 'statpress-infographic-chart-loader' );
- wp_add_inline_script(
- 'statpress-infographic-chart-loader',
- '
- document.addEventListener("DOMContentLoaded", function() {
- const ctx = document.getElementById("statpressCategoryPieChart");
- if (!ctx) return;
- new Chart(ctx, {
- type: "pie",
- data: {
- labels: ' . json_encode( $chart_labels ) . ',
- datasets: [{
- data: ' . json_encode( $chart_data ) . ',
- backgroundColor: ' . json_encode( $chart_colors ) . ',
- hoverOffset: 4
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: {
- position: "right",
- },
- title: {
- display: true,
- text: "Dystans wg kategorii w ' . esc_js( $current_year ) . '"
- }
- }
- }
- });
- });
- '
- );
-
- ?>
-
-
Infografika Statystyk Sportowych
-
-
-
-
-
- Filtruj według roku
-
-
- >
-
-
-
-
-
-
-
-
-
-
-
Dystans total_distance, 2, ',', ' ' ); ?> km
-
-
Wznios total_elevation_gain, 0, ',', ' ' ); ?> m
-
Aktywności total_activities, 0, ',', ' ' ); ?>
-
-
-
-
-
-
-
Dystans total_distance, 2, ',', ' ' ); ?> km
-
-
Wznios total_elevation_gain, 0, ',', ' ' ); ?> m
-
Aktywności total_activities, 0, ',', ' ' ); ?>
-
-
-
-
-
- prefix . 'statpress_activities';
+ $table_categories = $wpdb->prefix . 'statpress_categories';
+
+ $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' );
+
+ // Pobierz dostępne lata z bazy danych
+ $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" );
+ if ( empty( $available_years ) ) {
+ $available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych
+ }
+
+ // --- 1. Statystyki ogólne (wszystkie lata) ---
+ $overall_stats = $wpdb->get_row(
+ "
+ SELECT
+ SUM(distance) as total_distance,
+ SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration,
+ SUM(total_elevation_gain) as total_elevation_gain,
+ COUNT(id) as total_activities
+ FROM $table_activities
+ "
+ );
+
+ // --- 2. Statystyki dla wybranego roku ---
+ $yearly_stats = $wpdb->get_row(
+ $wpdb->prepare(
+ "
+ SELECT
+ SUM(distance) as total_distance,
+ SEC_TO_TIME(SUM(TIME_TO_SEC(duration))) as total_duration,
+ SUM(total_elevation_gain) as total_elevation_gain,
+ COUNT(id) as total_activities
+ FROM $table_activities
+ WHERE YEAR(date) = %d
+ ",
+ $current_year
+ )
+ );
+
+ // --- 3. Dane dla wykresu kołowego (dystans per kategoria dla wybranego roku) ---
+ $category_distance_data = $wpdb->get_results(
+ $wpdb->prepare(
+ "
+ SELECT c.name as category_name, c.color, SUM(a.distance) as total_distance
+ FROM $table_activities a
+ LEFT JOIN $table_categories c ON a.category_id = c.id
+ WHERE YEAR(a.date) = %d
+ GROUP BY c.name, c.color
+ HAVING SUM(a.distance) > 0
+ ORDER BY total_distance DESC
+ ",
+ $current_year
+ )
+ );
+
+ $chart_labels = array();
+ $chart_data = array();
+ $chart_colors = array();
+ foreach ( $category_distance_data as $data ) {
+ $chart_labels[] = $data->category_name;
+ $chart_data[] = round( (float) $data->total_distance, 2 );
+ $chart_colors[] = $data->color;
+ }
+
+ // Włączenie skryptów dla Chart.js
+ wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
+ wp_register_script( 'statpress-infographic-chart-loader', false );
+ wp_enqueue_script( 'statpress-infographic-chart-loader' );
+ wp_add_inline_script(
+ 'statpress-infographic-chart-loader',
+ '
+ document.addEventListener("DOMContentLoaded", function() {
+ const ctx = document.getElementById("statpressCategoryPieChart");
+ if (!ctx) return;
+ new Chart(ctx, {
+ type: "pie",
+ data: {
+ labels: ' . json_encode( $chart_labels ) . ',
+ datasets: [{
+ data: ' . json_encode( $chart_data ) . ',
+ backgroundColor: ' . json_encode( $chart_colors ) . ',
+ hoverOffset: 4
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ position: "right",
+ },
+ title: {
+ display: true,
+ text: "Dystans wg kategorii w ' . esc_js( $current_year ) . '"
+ }
+ }
+ }
+ });
+ });
+ '
+ );
+
+ ?>
+
+
Infografika Statystyk Sportowych
+
+
+
+
+
+ Filtruj według roku
+
+
+ >
+
+
+
+
+
+
+
+
+
+
+
Dystans total_distance, 2, ',', ' ' ); ?> km
+
+
Wznios total_elevation_gain, 0, ',', ' ' ); ?> m
+
Aktywności total_activities, 0, ',', ' ' ); ?>
+
+
+
+
+
+
+
Dystans total_distance, 2, ',', ' ' ); ?> km
+
+
Wznios total_elevation_gain, 0, ',', ' ' ); ?> m
+
Aktywności total_activities, 0, ',', ' ' ); ?>
+
+
+
+
+
+
-
-
Ustawienia Wtyczki Statystyk
-
-
-
-
- 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.';
- echo '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.
';
-}
-
-function statpress_render_lat_field() {
- $options = get_option( 'statpress_privacy_options' );
- $latitude = isset( $options['latitude'] ) ? esc_attr( $options['latitude'] ) : '';
- echo " ";
-}
-
-function statpress_render_lon_field() {
- $options = get_option( 'statpress_privacy_options' );
- $longitude = isset( $options['longitude'] ) ? esc_attr( $options['longitude'] ) : '';
- echo " ";
-}
-
-function statpress_render_radius_field() {
- $options = get_option( 'statpress_privacy_options' );
- $radius = isset( $options['radius'] ) ? esc_attr( $options['radius'] ) : '500';
- echo " metrów";
-}
-
-function statpress_sanitize_privacy_options( $input ) {
- $sanitized_input = array();
- if ( isset( $input['latitude'] ) ) {
- $sanitized_input['latitude'] = floatval( str_replace( ',', '.', $input['latitude'] ) );
- }
- if ( isset( $input['longitude'] ) ) {
- $sanitized_input['longitude'] = floatval( str_replace( ',', '.', $input['longitude'] ) );
- }
- if ( isset( $input['radius'] ) ) {
- $sanitized_input['radius'] = abs( intval( $input['radius'] ) );
- }
- return $sanitized_input;
-}
-
-function statpress_api_section_callback() {
- echo 'Ustawienia związane z integracją wtyczki z zewnętrznymi aplikacjami, np. mobilnymi.
';
-}
-
-function statpress_render_enable_api_field() {
- $options = get_option( 'statpress_api_options' );
- $checked = isset( $options['enable_api'] ) && $options['enable_api'] ? 'checked' : '';
- echo " Włącz REST API ";
- echo '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.
';
-}
-
-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;
+
+
+
Status migracji został zresetowany. Wróć do głównego panelu , aby ponownie uruchomić migrację.
';
+ delete_transient( 'statpress_migration_reset_notice' );
+ }
+ ?>
+ Ustawienia Wtyczki Statystyk
+
+
+
+
+
+ Narzędzia deweloperskie
+
+
+
+
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.
+
+
+
+
+
+
+ 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.';
+ echo '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.
';
+}
+
+function statpress_render_lat_field() {
+ $options = get_option( 'statpress_privacy_options' );
+ $latitude = isset( $options['latitude'] ) ? esc_attr( $options['latitude'] ) : '';
+ echo " ";
+}
+
+function statpress_render_lon_field() {
+ $options = get_option( 'statpress_privacy_options' );
+ $longitude = isset( $options['longitude'] ) ? esc_attr( $options['longitude'] ) : '';
+ echo " ";
+}
+
+function statpress_render_radius_field() {
+ $options = get_option( 'statpress_privacy_options' );
+ $radius = isset( $options['radius'] ) ? esc_attr( $options['radius'] ) : '500';
+ echo " metrów";
+}
+
+function statpress_sanitize_privacy_options( $input ) {
+ $sanitized_input = array();
+ if ( isset( $input['latitude'] ) ) {
+ $sanitized_input['latitude'] = floatval( str_replace( ',', '.', $input['latitude'] ) );
+ }
+ if ( isset( $input['longitude'] ) ) {
+ $sanitized_input['longitude'] = floatval( str_replace( ',', '.', $input['longitude'] ) );
+ }
+ if ( isset( $input['radius'] ) ) {
+ $sanitized_input['radius'] = abs( intval( $input['radius'] ) );
+ }
+ return $sanitized_input;
+}
+
+function statpress_api_section_callback() {
+ echo 'Ustawienia związane z integracją wtyczki z zewnętrznymi aplikacjami, np. mobilnymi.
';
+}
+
+function statpress_render_enable_api_field() {
+ $options = get_option( 'statpress_api_options' );
+ $checked = isset( $options['enable_api'] ) && $options['enable_api'] ? 'checked' : '';
+ echo " Włącz REST API ";
+ echo '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.
';
+}
+
+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;
}
\ No newline at end of file
diff --git a/includes/admin/pages/page-yearly-summary.php b/includes/admin/pages/page-yearly-summary.php
index 9d97e43..5cc66fc 100644
--- a/includes/admin/pages/page-yearly-summary.php
+++ b/includes/admin/pages/page-yearly-summary.php
@@ -1,292 +1,292 @@
-prefix . 'statpress_activities';
-
- $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' );
-
- // Pobierz dostępne lata z bazy danych
- $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" );
- if ( empty( $available_years ) ) {
- $available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych
- }
-
- // --- GOALS SECTION ---
- $table_goals = $wpdb->prefix . 'statpress_goals';
- $goals_for_year = $wpdb->get_results(
- $wpdb->prepare(
- "SELECT * FROM {$table_goals} WHERE year = %d ORDER BY name ASC",
- $current_year
- )
- );
-
- // Zapytanie SQL do grupowania danych miesięcznie
- $sql = $wpdb->prepare(
- "
- SELECT
- MONTH(date) as month_num,
- SUM(distance) as total_distance,
- SUM(calories) as total_calories,
- SUM(TIME_TO_SEC(duration)) as total_seconds,
- COUNT(id) as activity_count
- FROM $table_activities
- WHERE YEAR(date) = %d
- GROUP BY month_num
- ORDER BY month_num ASC
- ",
- $current_year
- );
-
- $monthly_summary = $wpdb->get_results( $sql, OBJECT_K ); // OBJECT_K zwróci tablicę z kluczami będącymi numerami miesięcy
-
- // Przygotowanie danych dla wszystkich 12 miesięcy
- $full_year_summary = array();
- $total_year_distance = 0;
- $total_year_calories = 0;
- $total_year_seconds = 0;
-
- // Określ, ile miesięcy pokazać, aby uniknąć zer dla przyszłych miesięcy
- $this_year = (int) current_time( 'Y' );
- $this_month = (int) current_time( 'n' );
- $loop_until_month = 12; // Domyślnie dla lat ubiegłych
-
- if ( $current_year === $this_year ) {
- // Dla bieżącego roku, pokaż miesiące do bieżącego miesiąca
- $loop_until_month = $this_month;
- } elseif ( $current_year > $this_year ) {
- // Dla przyszłych lat, pokaż miesiące tylko do ostatniego, w którym są dane
- $last_month_with_data = $wpdb->get_var( $wpdb->prepare( "SELECT MAX(MONTH(date)) FROM $table_activities WHERE YEAR(date) = %d", $current_year ) );
- $loop_until_month = $last_month_with_data ? (int) $last_month_with_data : 0;
- }
-
- for ( $i = 1; $i <= $loop_until_month; $i++ ) {
- $month_name = date_i18n( 'F', mktime( 0, 0, 0, $i, 10 ) ); // Nazwa miesiąca
- $data = isset( $monthly_summary[ $i ] ) ? $monthly_summary[ $i ] : null;
-
- $full_year_summary[ $i ] = (object) array(
- 'month_name' => $month_name,
- 'total_distance' => $data ? $data->total_distance : 0,
- 'total_calories' => $data ? (int) $data->total_calories : 0,
- 'total_seconds' => $data ? (int) $data->total_seconds : 0,
- 'activity_count' => $data ? (int) $data->activity_count : 0,
- );
-
- $total_year_distance += $full_year_summary[ $i ]->total_distance;
- $total_year_seconds += $full_year_summary[ $i ]->total_seconds;
- $total_year_calories += $full_year_summary[ $i ]->total_calories;
- }
-
- $total_year_hours = floor( $total_year_seconds / 3600 );
- $total_year_minutes = floor( ( $total_year_seconds % 3600 ) / 60 );
- $total_year_duration_formatted = sprintf( '%d godz. %d min.', $total_year_hours, $total_year_minutes );
-
- // Przygotowanie danych dla wykresu
- $chart_labels_js = array();
- $chart_datasets = array(
- 'distance' => array(),
- 'duration' => array(),
- 'calories' => array(),
- 'activities' => array(),
- );
-
- foreach ( $full_year_summary as $month_data ) {
- $chart_labels_js[] = $month_data->month_name;
- $chart_datasets['distance'][] = round( (float) $month_data->total_distance, 2 );
- $chart_datasets['duration'][] = round( $month_data->total_seconds / 3600, 2 ); // w godzinach
- $chart_datasets['calories'][] = $month_data->total_calories;
- $chart_datasets['activities'][] = $month_data->activity_count;
- }
-
- // Włączenie skryptów dla Chart.js
- wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
- wp_register_script( 'statpress-chart-loader', false );
- wp_enqueue_script( 'statpress-chart-loader' );
-
- $chart_configs = array(
- 'distance' => array(
- 'label' => 'Dystans (km)',
- 'data' => $chart_datasets['distance'],
- 'backgroundColor' => 'rgba(52, 152, 219, 0.5)',
- 'borderColor' => 'rgba(52, 152, 219, 1)',
- 'yAxisLabel' => 'Kilometry',
- ),
- 'duration' => array(
- 'label' => 'Czas trwania (godz.)',
- 'data' => $chart_datasets['duration'],
- 'backgroundColor' => 'rgba(26, 188, 156, 0.5)',
- 'borderColor' => 'rgba(26, 188, 156, 1)',
- 'yAxisLabel' => 'Godziny',
- ),
- 'calories' => array(
- 'label' => 'Kalorie (kcal)',
- 'data' => $chart_datasets['calories'],
- 'backgroundColor' => 'rgba(231, 76, 60, 0.5)',
- 'borderColor' => 'rgba(231, 76, 60, 1)',
- 'yAxisLabel' => 'kcal',
- ),
- 'activities' => array(
- 'label' => 'Liczba aktywności',
- 'data' => $chart_datasets['activities'],
- 'backgroundColor' => 'rgba(241, 196, 15, 0.5)',
- 'borderColor' => 'rgba(241, 196, 15, 1)',
- 'yAxisLabel' => 'Ilość',
- ),
- );
-
- wp_add_inline_script(
- 'statpress-chart-loader',
- '
- document.addEventListener("DOMContentLoaded", function() {
- const chartLabels = ' . json_encode( $chart_labels_js ) . ';
- const chartConfigs = ' . json_encode( $chart_configs ) . ';
- let activeChart = null;
-
- const tabs = document.querySelectorAll(".nav-tab");
- tabs.forEach(tab => {
- tab.addEventListener("click", function(e) {
- e.preventDefault();
- tabs.forEach(t => t.classList.remove("nav-tab-active"));
- this.classList.add("nav-tab-active");
-
- const chartType = this.getAttribute("href").substring(1);
- renderChart(chartType);
- });
- });
-
- function renderChart(type) {
- if (activeChart) {
- activeChart.destroy();
- }
- const config = chartConfigs[type];
- const ctx = document.getElementById("statpressYearlyChart").getContext("2d");
- activeChart = new Chart(ctx, {
- type: "bar",
- data: {
- labels: chartLabels,
- datasets: [{
- label: config.label,
- data: config.data,
- backgroundColor: config.backgroundColor,
- borderColor: config.borderColor,
- borderWidth: 1
- }]
- },
- options: {
- responsive: true, maintainAspectRatio: false,
- scales: { y: { beginAtZero: true, title: { display: true, text: config.yAxisLabel } } }
- }
- });
- }
-
- // Render initial chart
- if (tabs.length > 0) {
- renderChart(tabs[0].getAttribute("href").substring(1));
- }
- });
- '
- );
-
- ?>
-
-
Podsumowanie Roczne
-
-
-
- Filtruj według roku
-
-
-
-
- >
-
-
-
-
-
-
-
-
-
-
-
-
- goal_type ) {
- $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 ) );
- } elseif ( 'distance' === $goal->goal_type ) {
- $target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km';
- $current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km';
- } else { // count
- $target_formatted = (int) $goal->target_value;
- $current_formatted = (int) $progress['current_value'];
- }
- ?>
-
-
- name ); ?>
- / (%)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Miesiąc Dystans (km) Kalorie (kcal) Czas
-
-
-
-
- month_name ); ?>
- total_distance, 2, ',', ' ' ); ?>
- total_calories, 0, ',', ' ' ); ?>
- total_seconds ) ); ?>
-
-
-
- SUMA ROCZNA
-
-
-
-
-
-
-
- prefix . 'statpress_activities';
+
+ $current_year = isset( $_GET['year'] ) ? intval( $_GET['year'] ) : current_time( 'Y' );
+
+ // Pobierz dostępne lata z bazy danych
+ $available_years = $wpdb->get_col( "SELECT DISTINCT YEAR(date) FROM $table_activities ORDER BY YEAR(date) DESC" );
+ if ( empty( $available_years ) ) {
+ $available_years = array( current_time( 'Y' ) ); // Domyślny rok, jeśli brak danych
+ }
+
+ // --- GOALS SECTION ---
+ $table_goals = $wpdb->prefix . 'statpress_goals';
+ $goals_for_year = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT * FROM {$table_goals} WHERE year = %d ORDER BY name ASC",
+ $current_year
+ )
+ );
+
+ // Zapytanie SQL do grupowania danych miesięcznie
+ $sql = $wpdb->prepare(
+ "
+ SELECT
+ MONTH(date) as month_num,
+ SUM(distance) as total_distance,
+ SUM(calories) as total_calories,
+ SUM(TIME_TO_SEC(duration)) as total_seconds,
+ COUNT(id) as activity_count
+ FROM $table_activities
+ WHERE YEAR(date) = %d
+ GROUP BY month_num
+ ORDER BY month_num ASC
+ ",
+ $current_year
+ );
+
+ $monthly_summary = $wpdb->get_results( $sql, OBJECT_K ); // OBJECT_K zwróci tablicę z kluczami będącymi numerami miesięcy
+
+ // Przygotowanie danych dla wszystkich 12 miesięcy
+ $full_year_summary = array();
+ $total_year_distance = 0;
+ $total_year_calories = 0;
+ $total_year_seconds = 0;
+
+ // Określ, ile miesięcy pokazać, aby uniknąć zer dla przyszłych miesięcy
+ $this_year = (int) current_time( 'Y' );
+ $this_month = (int) current_time( 'n' );
+ $loop_until_month = 12; // Domyślnie dla lat ubiegłych
+
+ if ( $current_year === $this_year ) {
+ // Dla bieżącego roku, pokaż miesiące do bieżącego miesiąca
+ $loop_until_month = $this_month;
+ } elseif ( $current_year > $this_year ) {
+ // Dla przyszłych lat, pokaż miesiące tylko do ostatniego, w którym są dane
+ $last_month_with_data = $wpdb->get_var( $wpdb->prepare( "SELECT MAX(MONTH(date)) FROM $table_activities WHERE YEAR(date) = %d", $current_year ) );
+ $loop_until_month = $last_month_with_data ? (int) $last_month_with_data : 0;
+ }
+
+ for ( $i = 1; $i <= $loop_until_month; $i++ ) {
+ $month_name = date_i18n( 'F', mktime( 0, 0, 0, $i, 10 ) ); // Nazwa miesiąca
+ $data = isset( $monthly_summary[ $i ] ) ? $monthly_summary[ $i ] : null;
+
+ $full_year_summary[ $i ] = (object) array(
+ 'month_name' => $month_name,
+ 'total_distance' => $data ? $data->total_distance : 0,
+ 'total_calories' => $data ? (int) $data->total_calories : 0,
+ 'total_seconds' => $data ? (int) $data->total_seconds : 0,
+ 'activity_count' => $data ? (int) $data->activity_count : 0,
+ );
+
+ $total_year_distance += $full_year_summary[ $i ]->total_distance;
+ $total_year_seconds += $full_year_summary[ $i ]->total_seconds;
+ $total_year_calories += $full_year_summary[ $i ]->total_calories;
+ }
+
+ $total_year_hours = floor( $total_year_seconds / 3600 );
+ $total_year_minutes = floor( ( $total_year_seconds % 3600 ) / 60 );
+ $total_year_duration_formatted = sprintf( '%d godz. %d min.', $total_year_hours, $total_year_minutes );
+
+ // Przygotowanie danych dla wykresu
+ $chart_labels_js = array();
+ $chart_datasets = array(
+ 'distance' => array(),
+ 'duration' => array(),
+ 'calories' => array(),
+ 'activities' => array(),
+ );
+
+ foreach ( $full_year_summary as $month_data ) {
+ $chart_labels_js[] = $month_data->month_name;
+ $chart_datasets['distance'][] = round( (float) $month_data->total_distance, 2 );
+ $chart_datasets['duration'][] = round( $month_data->total_seconds / 3600, 2 ); // w godzinach
+ $chart_datasets['calories'][] = $month_data->total_calories;
+ $chart_datasets['activities'][] = $month_data->activity_count;
+ }
+
+ // Włączenie skryptów dla Chart.js
+ wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
+ wp_register_script( 'statpress-chart-loader', false );
+ wp_enqueue_script( 'statpress-chart-loader' );
+
+ $chart_configs = array(
+ 'distance' => array(
+ 'label' => 'Dystans (km)',
+ 'data' => $chart_datasets['distance'],
+ 'backgroundColor' => 'rgba(52, 152, 219, 0.5)',
+ 'borderColor' => 'rgba(52, 152, 219, 1)',
+ 'yAxisLabel' => 'Kilometry',
+ ),
+ 'duration' => array(
+ 'label' => 'Czas trwania (godz.)',
+ 'data' => $chart_datasets['duration'],
+ 'backgroundColor' => 'rgba(26, 188, 156, 0.5)',
+ 'borderColor' => 'rgba(26, 188, 156, 1)',
+ 'yAxisLabel' => 'Godziny',
+ ),
+ 'calories' => array(
+ 'label' => 'Kalorie (kcal)',
+ 'data' => $chart_datasets['calories'],
+ 'backgroundColor' => 'rgba(231, 76, 60, 0.5)',
+ 'borderColor' => 'rgba(231, 76, 60, 1)',
+ 'yAxisLabel' => 'kcal',
+ ),
+ 'activities' => array(
+ 'label' => 'Liczba aktywności',
+ 'data' => $chart_datasets['activities'],
+ 'backgroundColor' => 'rgba(241, 196, 15, 0.5)',
+ 'borderColor' => 'rgba(241, 196, 15, 1)',
+ 'yAxisLabel' => 'Ilość',
+ ),
+ );
+
+ wp_add_inline_script(
+ 'statpress-chart-loader',
+ '
+ document.addEventListener("DOMContentLoaded", function() {
+ const chartLabels = ' . json_encode( $chart_labels_js ) . ';
+ const chartConfigs = ' . json_encode( $chart_configs ) . ';
+ let activeChart = null;
+
+ const tabs = document.querySelectorAll(".nav-tab");
+ tabs.forEach(tab => {
+ tab.addEventListener("click", function(e) {
+ e.preventDefault();
+ tabs.forEach(t => t.classList.remove("nav-tab-active"));
+ this.classList.add("nav-tab-active");
+
+ const chartType = this.getAttribute("href").substring(1);
+ renderChart(chartType);
+ });
+ });
+
+ function renderChart(type) {
+ if (activeChart) {
+ activeChart.destroy();
+ }
+ const config = chartConfigs[type];
+ const ctx = document.getElementById("statpressYearlyChart").getContext("2d");
+ activeChart = new Chart(ctx, {
+ type: "bar",
+ data: {
+ labels: chartLabels,
+ datasets: [{
+ label: config.label,
+ data: config.data,
+ backgroundColor: config.backgroundColor,
+ borderColor: config.borderColor,
+ borderWidth: 1
+ }]
+ },
+ options: {
+ responsive: true, maintainAspectRatio: false,
+ scales: { y: { beginAtZero: true, title: { display: true, text: config.yAxisLabel } } }
+ }
+ });
+ }
+
+ // Render initial chart
+ if (tabs.length > 0) {
+ renderChart(tabs[0].getAttribute("href").substring(1));
+ }
+ });
+ '
+ );
+
+ ?>
+
+
Podsumowanie Roczne
+
+
+
+ Filtruj według roku
+
+
+
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ goal_type ) {
+ $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 ) );
+ } elseif ( 'distance' === $goal->goal_type ) {
+ $target_formatted = number_format( $goal->target_value, 0, ',', ' ' ) . ' km';
+ $current_formatted = number_format( $progress['current_value'], 2, ',', ' ' ) . ' km';
+ } else { // count
+ $target_formatted = (int) $goal->target_value;
+ $current_formatted = (int) $progress['current_value'];
+ }
+ ?>
+
+
+ name ); ?>
+ / (%)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Miesiąc Dystans (km) Kalorie (kcal) Czas
+
+
+
+
+ month_name ); ?>
+ total_distance, 2, ',', ' ' ); ?>
+ total_calories, 0, ',', ' ' ); ?>
+ total_seconds ) ); ?>
+
+
+
+ SUMA ROCZNA
+
+
+
+
+
+
+
+ WP_REST_Server::READABLE,
- 'callback' => 'statpress_get_activities_api',
- 'permission_callback' => 'statpress_api_permissions_check',
- 'args' => array(
- 'page' => array(
- 'validate_callback' => 'is_numeric',
- ),
- 'per_page' => array(
- 'validate_callback' => 'is_numeric',
- ),
- ),
- ),
- array(
- 'methods' => WP_REST_Server::CREATABLE,
- 'callback' => 'statpress_create_activity_api',
- 'permission_callback' => 'statpress_api_permissions_check',
- ),
- )
- );
-
- // Route for a single activity
- register_rest_route(
- $namespace,
- '/activities/(?P[\d]+)',
- array(
- array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => 'statpress_get_activity_api',
- 'permission_callback' => 'statpress_api_permissions_check',
- ),
- array(
- 'methods' => WP_REST_Server::EDITABLE,
- 'callback' => 'statpress_update_activity_api',
- 'permission_callback' => 'statpress_api_permissions_check',
- ),
- array(
- 'methods' => WP_REST_Server::DELETABLE,
- 'callback' => 'statpress_delete_activity_api',
- 'permission_callback' => 'statpress_api_permissions_check',
- ),
- )
- );
-}
-
-/**
- * Permission check for API endpoints.
- *
- * @return bool
- */
-function statpress_api_permissions_check() {
- return current_user_can( 'manage_options' );
-}
-
-/**
- * Get a collection of activities.
- *
- * @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';
-
- $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;
- $offset = ( $page - 1 ) * $per_page;
-
- $sql = $wpdb->prepare(
- "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
- LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
- ORDER BY a.date DESC, a.id DESC
- LIMIT %d OFFSET %d",
- $per_page,
- $offset
- );
-
- $results = $wpdb->get_results( $sql );
-
- return new WP_REST_Response( $results, 200 );
-}
-
-/**
- * Get a single activity.
- *
- * @param WP_REST_Request $request Full details about the request.
- * @return WP_REST_Response|WP_Error
- */
-function statpress_get_activity_api( WP_REST_Request $request ) {
- global $wpdb;
- $id = (int) $request['id'];
-
- $sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}statpress_activities WHERE id = %d", $id );
- $activity = $wpdb->get_row( $sql );
-
- if ( ! $activity ) {
- return new WP_Error( 'not_found', 'Activity not found', array( 'status' => 404 ) );
- }
-
- return new WP_REST_Response( $activity, 200 );
-}
-
-/**
- * Create a new activity.
- *
- * @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 ) {
- $params = $request->get_json_params();
- $activity_id = statpress_save_activity_data( $params );
-
- if ( ! $activity_id ) {
- return new WP_Error( 'cant-create', 'Error creating activity', array( 'status' => 500 ) );
- }
-
- $response = statpress_get_activity_api( new WP_REST_Request( 'GET', "/statpress/v1/activities/{$activity_id}" ) );
- $response->set_status( 201 ); // 201 Created
- 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 );
+ WP_REST_Server::READABLE,
+ 'callback' => 'statpress_get_activities_api',
+ 'permission_callback' => 'statpress_api_permissions_check',
+ 'args' => array(
+ 'page' => array(
+ 'validate_callback' => 'is_numeric',
+ ),
+ 'per_page' => array(
+ 'validate_callback' => 'is_numeric',
+ ),
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => 'statpress_create_activity_api',
+ 'permission_callback' => 'statpress_api_permissions_check',
+ ),
+ )
+ );
+
+ // Route for a single activity
+ register_rest_route(
+ $namespace,
+ '/activities/(?P[\d]+)',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => 'statpress_get_activity_api',
+ 'permission_callback' => 'statpress_api_permissions_check',
+ ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => 'statpress_update_activity_api',
+ 'permission_callback' => 'statpress_api_permissions_check',
+ ),
+ array(
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => 'statpress_delete_activity_api',
+ 'permission_callback' => 'statpress_api_permissions_check',
+ ),
+ )
+ );
+
+ // Route for parsing GPX file summary
+ register_rest_route(
+ $namespace,
+ '/gpx/parse-summary',
+ array(
+ 'methods' => WP_REST_Server::CREATABLE, // Use POST to send URL in the body
+ 'callback' => 'statpress_parse_gpx_summary_api',
+ 'permission_callback' => 'statpress_api_permissions_check',
+ 'args' => array(
+ 'gpx_url' => array(
+ 'required' => true,
+ 'validate_callback' => 'esc_url_raw',
+ ),
+ ),
+ )
+ );
+}
+
+/**
+ * Permission check for API endpoints.
+ *
+ * @return bool
+ */
+function statpress_api_permissions_check() {
+ return current_user_can( 'manage_options' );
+}
+
+/**
+ * Get a collection of activities.
+ *
+ * @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';
+
+ $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;
+ $offset = ( $page - 1 ) * $per_page;
+
+ // Get total items for pagination headers
+ $total_items = (int) $wpdb->get_var( "SELECT COUNT(id) FROM $table_activities" );
+ $total_pages = ceil( $total_items / $per_page );
+
+ $sql = $wpdb->prepare(
+ "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
+ LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
+ ORDER BY a.date DESC, a.id DESC
+ LIMIT %d OFFSET %d",
+ $per_page,
+ $offset
+ );
+
+ $results = $wpdb->get_results( $sql );
+
+ $response = new WP_REST_Response( $results, 200 );
+ $response->header( 'X-WP-Total', $total_items );
+ $response->header( 'X-WP-TotalPages', $total_pages );
+
+ return $response;
+}
+
+/**
+ * Get a single activity.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error
+ */
+function statpress_get_activity_api( WP_REST_Request $request ) {
+ global $wpdb;
+ $id = (int) $request['id'];
+
+ // Use the same rich query as the collection endpoint for consistency.
+ $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
+ LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id
+ LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
+ WHERE a.id = %d",
+ $id
+ );
+ $activity = $wpdb->get_row( $sql );
+
+ if ( ! $activity ) {
+ return new WP_Error( 'not_found', 'Activity not found', array( 'status' => 404 ) );
+ }
+
+ return new WP_REST_Response( $activity, 200 );
+}
+
+/**
+ * Create a new activity.
+ *
+ * @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 ) {
+ $params = $request->get_json_params();
+ $activity_id = statpress_save_activity_data( $params );
+
+ if ( ! $activity_id ) {
+ return new WP_Error( 'cant-create', 'Error creating activity', array( 'status' => 500 ) );
+ }
+
+ // Create a new request object to fetch the newly created activity.
+ $new_request = new WP_REST_Request();
+ $new_request->set_param( 'id', $activity_id );
+ $response = statpress_get_activity_api( $new_request );
+ $response->set_status( 201 ); // 201 Created
+ 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 );
}
\ No newline at end of file
diff --git a/includes/core/crud-activity.php b/includes/core/crud-activity.php
index 4b603e5..70c8415 100644
--- a/includes/core/crud-activity.php
+++ b/includes/core/crud-activity.php
@@ -1,95 +1,95 @@
-prefix . 'statpress_activities';
-
- // Helper to convert empty strings to NULL for the database.
- $null_if_empty = function( $value ) {
- // Also check for 0 as we don't want to nullify it for numeric fields.
- return ( '' !== $value && ! is_null( $value ) ) ? $value : null;
- };
-
- // Sanitize and prepare data.
- $prepared_data = array(
- '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' ),
- 'title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '',
- 'distance' => isset( $data['distance'] ) ? floatval( str_replace( ',', '.', $data['distance'] ) ) : 0,
- 'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : '00:00:00',
- 'calories' => isset( $data['calories'] ) ? intval( $data['calories'] ) : 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,
- '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,
- '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,
- '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,
- '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,
- '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,
- '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,
- 'event_type_id' => isset( $data['event_type_id'] ) ? $null_if_empty( intval( $data['event_type_id'] ) ) : null,
- );
-
- // Data formats for $wpdb.
- $format = array(
- '%d', // category_id
- '%s', // date
- '%s', // title
- '%f', // distance
- '%s', // duration
- '%d', // calories
- '%s', // comment
- '%s', // strava_url
- '%d', // avg_heart_rate
- '%d', // max_heart_rate
- '%f', // avg_speed
- '%f', // max_speed
- '%d', // avg_cadence
- '%d', // max_cadence
- '%d', // total_elevation_gain
- '%d', // total_elevation_loss
- '%d', // min_altitude
- '%d', // max_altitude
- '%d', // equipment_id
- '%s', // gpx_url
- '%d', // event_type_id
- );
-
- if ( $activity_id > 0 ) {
- // UPDATE
- $result = $wpdb->update( $table_activities, $prepared_data, array( 'id' => $activity_id ), $format, array( '%d' ) );
- if ( false !== $result ) {
- return $activity_id;
- }
- } else {
- // INSERT
- $result = $wpdb->insert( $table_activities, $prepared_data, $format );
- if ( $result ) {
- return $wpdb->insert_id;
- }
- }
-
- return false;
+prefix . 'statpress_activities';
+
+ // Helper to convert empty strings to NULL for the database.
+ $null_if_empty = function( $value ) {
+ // Also check for 0 as we don't want to nullify it for numeric fields.
+ return ( '' !== $value && ! is_null( $value ) ) ? $value : null;
+ };
+
+ // Sanitize and prepare data.
+ $prepared_data = array(
+ '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' ),
+ 'title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '',
+ 'distance' => isset( $data['distance'] ) ? floatval( str_replace( ',', '.', $data['distance'] ) ) : 0,
+ 'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : '00:00:00',
+ 'calories' => isset( $data['calories'] ) ? intval( $data['calories'] ) : 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,
+ '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,
+ '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,
+ '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,
+ '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,
+ '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,
+ '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,
+ 'event_type_id' => isset( $data['event_type_id'] ) ? $null_if_empty( intval( $data['event_type_id'] ) ) : null,
+ );
+
+ // Data formats for $wpdb.
+ $format = array(
+ '%d', // category_id
+ '%s', // date
+ '%s', // title
+ '%f', // distance
+ '%s', // duration
+ '%d', // calories
+ '%s', // comment
+ '%s', // strava_url
+ '%d', // avg_heart_rate
+ '%d', // max_heart_rate
+ '%f', // avg_speed
+ '%f', // max_speed
+ '%d', // avg_cadence
+ '%d', // max_cadence
+ '%d', // total_elevation_gain
+ '%d', // total_elevation_loss
+ '%d', // min_altitude
+ '%d', // max_altitude
+ '%d', // equipment_id
+ '%s', // gpx_url
+ '%d', // event_type_id
+ );
+
+ if ( $activity_id > 0 ) {
+ // UPDATE
+ $result = $wpdb->update( $table_activities, $prepared_data, array( 'id' => $activity_id ), $format, array( '%d' ) );
+ if ( false !== $result ) {
+ return $activity_id;
+ }
+ } else {
+ // INSERT
+ $result = $wpdb->insert( $table_activities, $prepared_data, $format );
+ if ( $result ) {
+ return $wpdb->insert_id;
+ }
+ }
+
+ return false;
}
\ No newline at end of file
diff --git a/includes/core/gpx-parser.php b/includes/core/gpx-parser.php
index e307880..9417c79 100644
--- a/includes/core/gpx-parser.php
+++ b/includes/core/gpx-parser.php
@@ -1,139 +1,297 @@
- 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();
- }
-
- // --- Privacy Zone ---
- $privacy_options = get_option( 'statpress_privacy_options' );
- $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_lon = $privacy_enabled ? (float) $privacy_options['longitude'] : 0;
- $privacy_radius_km = $privacy_enabled ? ( (int) $privacy_options['radius'] ) / 1000 : 0; // Convert meters to km
-
- libxml_use_internal_errors( true );
- $gpx = simplexml_load_string( $gpx_content );
- libxml_clear_errors();
-
- if ( false === $gpx ) {
- return array();
- }
-
- // Use XPath to be more robust against different GPX structures/namespaces
- $gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' );
- $trackpoints = $gpx->xpath( '//gpx:trkpt' );
- if ( empty( $trackpoints ) ) {
- $trackpoints = $gpx->xpath( '//trkpt' ); // Fallback for files without namespace
- }
-
- $raw_points = array();
- $start_time = null;
-
- foreach ( $trackpoints as $trkpt ) {
- if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) {
- $extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null;
- $time = isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null;
- if ( $time && is_null( $start_time ) ) {
- $start_time = $time;
- }
-
- $hr_val = ( $extensions && isset( $extensions->TrackPointExtension->hr ) ) ? (int) $extensions->TrackPointExtension->hr : null;
- $cad_val = ( $extensions && isset( $extensions->TrackPointExtension->cad ) ) ? (int) $extensions->TrackPointExtension->cad : null;
-
- $raw_points[] = array(
- 'lat' => (float) $trkpt['lat'],
- 'lon' => (float) $trkpt['lon'],
- 'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null,
- 'time_offset' => $start_time && $time ? $time - $start_time : null,
- 'hr' => $hr_val,
- 'cad' => $cad_val,
- );
- }
- }
-
- if ( empty( $raw_points ) ) {
- return array();
- }
-
- // Process raw points to calculate profiles
- $map_points = array();
- $profiles = array(
- 'distance' => array(),
- 'time' => array(),
- 'elevation' => array(),
- 'speed' => array(),
- 'hr' => array(),
- 'cadence' => array(),
- );
- $cumulative_distance = 0;
-
- $haversine = function( $lat1, $lon1, $lat2, $lon2 ) {
- $earth_radius = 6371; // in km
- $dLat = deg2rad( $lat2 - $lat1 );
- $dLon = deg2rad( $lon2 - $lon1 );
- $a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 );
- $c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) );
- return $earth_radius * $c;
- };
-
- foreach ( $raw_points as $i => $point ) {
- $is_in_privacy_zone = false;
- if ( $privacy_enabled ) {
- $distance_from_center = $haversine( $privacy_center_lat, $privacy_center_lon, $point['lat'], $point['lon'] );
- if ( $distance_from_center <= $privacy_radius_km ) {
- $is_in_privacy_zone = true;
- }
- }
-
- if ( ! $is_in_privacy_zone ) {
- $map_points[] = array( $point['lat'], $point['lon'] );
- $speed = null;
-
- if ( $i > 0 ) {
- $prev_point = $raw_points[ $i - 1 ];
- $distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); // km
- $cumulative_distance += $distance_delta;
-
- if ( ! is_null( $point['time_offset'] ) && ! is_null( $prev_point['time_offset'] ) ) {
- $time_delta = $point['time_offset'] - $prev_point['time_offset']; // seconds
- if ( $time_delta > 0 ) {
- $speed = ( $distance_delta * 3600 ) / $time_delta; // km/h
- }
- }
- }
-
- $profiles['distance'][] = round( $cumulative_distance, 3 );
- $profiles['time'][] = $point['time_offset'];
- $profiles['elevation'][] = ! is_null( $point['ele'] ) ? round( $point['ele'], 2 ) : null;
- $profiles['speed'][] = ! is_null( $speed ) ? round( $speed, 1 ) : null;
- $profiles['hr'][] = $point['hr'];
- $profiles['cadence'][] = $point['cad'];
- }
- }
-
- return array(
- 'points' => $map_points,
- 'profiles' => $profiles,
- );
+ 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();
+ }
+
+ // --- Privacy Zone ---
+ $privacy_options = get_option( 'statpress_privacy_options' );
+ $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_lon = $privacy_enabled ? (float) $privacy_options['longitude'] : 0;
+ $privacy_radius_km = $privacy_enabled ? ( (int) $privacy_options['radius'] ) / 1000 : 0; // Convert meters to km
+
+ libxml_use_internal_errors( true );
+ $gpx = simplexml_load_string( $gpx_content );
+ libxml_clear_errors();
+
+ if ( false === $gpx ) {
+ return array();
+ }
+
+ // Use XPath to be more robust against different GPX structures/namespaces
+ $gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' );
+ $trackpoints = $gpx->xpath( '//gpx:trkpt' );
+ if ( empty( $trackpoints ) ) {
+ $trackpoints = $gpx->xpath( '//trkpt' ); // Fallback for files without namespace
+ }
+
+ $raw_points = array();
+ $start_time = null;
+
+ foreach ( $trackpoints as $trkpt ) {
+ if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) {
+ $extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null;
+ $time = isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null;
+ if ( $time && is_null( $start_time ) ) {
+ $start_time = $time;
+ }
+
+ $hr_val = ( $extensions && isset( $extensions->TrackPointExtension->hr ) ) ? (int) $extensions->TrackPointExtension->hr : null;
+ $cad_val = ( $extensions && isset( $extensions->TrackPointExtension->cad ) ) ? (int) $extensions->TrackPointExtension->cad : null;
+
+ $raw_points[] = array(
+ 'lat' => (float) $trkpt['lat'],
+ 'lon' => (float) $trkpt['lon'],
+ 'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null,
+ 'time_offset' => $start_time && $time ? $time - $start_time : null,
+ 'hr' => $hr_val,
+ 'cad' => $cad_val,
+ );
+ }
+ }
+
+ if ( empty( $raw_points ) ) {
+ return array();
+ }
+
+ // Process raw points to calculate profiles
+ $map_points = array();
+ $profiles = array(
+ 'distance' => array(),
+ 'time' => array(),
+ 'elevation' => array(),
+ 'speed' => array(),
+ 'hr' => array(),
+ 'cadence' => array(),
+ );
+ $cumulative_distance = 0;
+
+ $haversine = function( $lat1, $lon1, $lat2, $lon2 ) {
+ $earth_radius = 6371; // in km
+ $dLat = deg2rad( $lat2 - $lat1 );
+ $dLon = deg2rad( $lon2 - $lon1 );
+ $a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 );
+ $c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) );
+ return $earth_radius * $c;
+ };
+
+ foreach ( $raw_points as $i => $point ) {
+ $is_in_privacy_zone = false;
+ if ( $privacy_enabled ) {
+ $distance_from_center = $haversine( $privacy_center_lat, $privacy_center_lon, $point['lat'], $point['lon'] );
+ if ( $distance_from_center <= $privacy_radius_km ) {
+ $is_in_privacy_zone = true;
+ }
+ }
+
+ if ( ! $is_in_privacy_zone ) {
+ $map_points[] = array( $point['lat'], $point['lon'] );
+ $speed = null;
+
+ if ( $i > 0 ) {
+ $prev_point = $raw_points[ $i - 1 ];
+ $distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); // km
+ $cumulative_distance += $distance_delta;
+
+ if ( ! is_null( $point['time_offset'] ) && ! is_null( $prev_point['time_offset'] ) ) {
+ $time_delta = $point['time_offset'] - $prev_point['time_offset']; // seconds
+ if ( $time_delta > 0 ) {
+ $speed = ( $distance_delta * 3600 ) / $time_delta; // km/h
+ }
+ }
+ }
+
+ $profiles['distance'][] = round( $cumulative_distance, 3 );
+ $profiles['time'][] = $point['time_offset'];
+ $profiles['elevation'][] = ! is_null( $point['ele'] ) ? round( $point['ele'], 2 ) : null;
+ $profiles['speed'][] = ! is_null( $speed ) ? round( $speed, 1 ) : null;
+ $profiles['hr'][] = $point['hr'];
+ $profiles['cadence'][] = $point['cad'];
+ }
+ }
+
+ return array(
+ 'points' => $map_points,
+ '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'],
+ );
}
\ No newline at end of file
diff --git a/includes/core/gpx-upload.php b/includes/core/gpx-upload.php
index 836413a..170443c 100644
--- a/includes/core/gpx-upload.php
+++ b/includes/core/gpx-upload.php
@@ -1,33 +1,33 @@
- current_time( 'Y' ),
- 'month' => current_time( 'n' ),
- ),
- $atts,
- 'moje_statystyki'
- );
-
- $year = intval( $atts['year'] );
- $month = intval( $atts['month'] );
-
- // Pobieranie danych z bazy
- $table_activities = $wpdb->prefix . 'mystat_activities';
- $sql = $wpdb->prepare(
- "
- SELECT a.*, c.name as category_name, eq.name as equipment_name
- FROM $table_activities a
- LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id
- LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id
- WHERE YEAR(a.date) = %d AND MONTH(a.date) = %d
- ORDER BY a.date ASC
- ",
- $year,
- $month
- );
-
- $activities = $wpdb->get_results( $sql );
-
- // Obliczanie podsumowań
- $total_distance = 0;
- $total_seconds = 0;
- foreach ( $activities as $activity ) {
- $total_distance += $activity->distance;
- if ( ! empty( $activity->duration ) ) {
- list($h, $m, $s) = explode( ':', $activity->duration );
- $total_seconds += $h * 3600 + $m * 60 + $s;
- }
- }
- $hours = floor( $total_seconds / 3600 );
- $minutes = floor( ( $total_seconds % 3600 ) / 60 );
- $total_duration_formatted = sprintf( '%d godz. %d min.', $hours, $minutes );
-
- // Rozpoczęcie buforowania wyjścia
- ob_start();
- ?>
-
-
-
Podsumowanie miesiąca
-
-
-
- Całkowity dystans
- Całkowity czas
-
-
-
-
- km
-
-
-
-
-
-
Lista aktywności
-
-
-
- Data
- Tytuł
- Kategoria
-
- Dystans
- Czas
- Sprzęt
-
-
-
-
-
-
- date ) ) ); ?>
- title ); ?>
- category_name ); ?>
- distance, 2, ',', ' ' ); ?> km
- duration ); ?>
- equipment_name ); ?>
-
- avg_speed || $row->avg_heart_rate || $row->avg_cadence ) : ?>
-
-
-
-
-
- avg_speed ) { echo 'Śr. prędkość '; } ?>
- avg_heart_rate ) { echo 'Śr. tętno '; } ?>
- avg_cadence ) { echo 'Śr. kadencja '; } ?>
-
-
-
-
- avg_speed ) { echo '' . number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h '; } ?>
- avg_heart_rate ) { echo '' . $row->avg_heart_rate . ' bpm '; } ?>
- avg_cadence ) { echo '' . $row->avg_cadence . ' rpm '; } ?>
-
-
-
-
-
-
-
-
-
- Brak aktywności w tym miesiącu.
-
-
-
-
-
- 0,
- ),
- $atts,
- 'moje_statystyki_wpis'
- );
-
- $activity_id = intval( $atts['id'] );
-
- if ( 0 === $activity_id ) {
- return 'Błąd: Nie podano ID wpisu w shortcode.
';
- }
-
- // Pobieranie danych z bazy
- $table_activities = $wpdb->prefix . 'mystat_activities';
- $sql = $wpdb->prepare(
- "
- SELECT a.*, c.name as category_name, c.color as category_color, et.name as event_type_name, eq.name as equipment_name
- FROM $table_activities a
- LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id
- LEFT JOIN {$wpdb->prefix}mystat_event_types et ON a.event_type_id = et.id
- LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id
- WHERE a.id = %d
- ",
- $activity_id
- );
-
- $activity = $wpdb->get_row( $sql );
-
- if ( ! $activity ) {
- return 'Błąd: Nie znaleziono wpisu o ID ' . esc_html( $activity_id ) . '.
';
- }
-
- // Funkcja pomocnicza do wyświetlania wiersza
- $render_row = function( $label, $value, $unit = '' ) {
- if ( ! is_null( $value ) && '' !== $value && 0 != $value ) {
- echo '';
- echo '' . esc_html( $label ) . ' ';
- echo '' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . ' ';
- echo ' ';
- }
- };
-
- ob_start();
-
- // Prepare map and chart data if GPX exists
- $gpx_data = array();
- $has_gpx_data = false;
- if ( ! empty( $activity->gpx_url ) ) {
- $gpx_data = mystat_parse_gpx_data( $activity->gpx_url );
- $has_gpx_data = ! empty( $gpx_data['points'] );
- }
-
- $unique_id = 'mystat-activity-' . esc_attr( $activity->id );
-
- if ( $has_gpx_data ) {
- wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
- wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', array(), '1.9.4', true );
- wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
-
- $map_id = 'map-' . $unique_id;
- $chart_id = 'chart-' . $unique_id;
-
- $available_profiles = array();
- if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
- $available_profiles['elevation'] = 'Wysokość';}
- if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
- $available_profiles['speed'] = 'Prędkość';}
- if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
- $available_profiles['hr'] = 'Tętno';}
- if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
- $available_profiles['cadence'] = 'Kadencja';}
-
- $has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
-
- wp_register_script( 'mystat-shortcode-loader-' . $activity->id, false );
- wp_enqueue_script( 'mystat-shortcode-loader-' . $activity->id );
-
- $js_script = '
- (function() {
- document.addEventListener("DOMContentLoaded", function() {
- const uniqueId = "' . esc_js( $unique_id ) . '";
- const containerEl = document.getElementById(uniqueId);
- if (!containerEl || typeof L === "undefined" || typeof Chart === "undefined") return;
-
- const trackPoints = ' . json_encode( $gpx_data['points'] ) . ';
- const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
- let activeChart = null;
-
- const mapId = "' . esc_js( $map_id ) . '";
- const mapEl = document.getElementById(mapId);
- if (mapEl && trackPoints.length > 0) {
- if (mapEl._leaflet_id) mapEl._leaflet_id = null;
- const map = L.map(mapId);
- L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© OpenStreetMap \' }).addTo(map);
- const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map);
- map.fitBounds(polyline.getBounds().pad(0.1));
- }
-
- const chartId = "' . esc_js( $chart_id ) . '";
- const chartEl = document.getElementById(chartId);
- if (!chartEl) return;
-
- const chartConfigs = {
- elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
- speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
- hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
- cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
- };
- const xAxisConfigs = {
- distance: { label: "Dystans (km)", data: profiles.distance },
- time: { label: "Czas", data: profiles.time, formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8) }
- };
-
- function renderChart() {
- if (activeChart) activeChart.destroy();
- const activeTab = containerEl.querySelector(".mystat-chart-tab.active");
- if (!activeTab) return;
- const chartType = activeTab.dataset.type;
- const xAxisRadio = containerEl.querySelector(\'input[name="xaxis-\' + uniqueId + \'"]:checked\');
- if (!xAxisRadio) return;
- const xAxisType = xAxisRadio.value;
-
- const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
- const filteredY = [], filteredX = [];
- if(yData) {
- for(let i=0; i v, maxRotation: 0, autoSkip: true, maxTicksLimit: 7 } },
- y: { title: { display: true, text: chartConfigs[chartType].unit } }
- },
- plugins: { legend: { display: false } },
- interaction: { intersect: false, mode: "index" },
- }
- });
- }
-
- containerEl.querySelectorAll(".mystat-chart-tab").forEach(t => t.addEventListener("click", e => {
- e.preventDefault();
- containerEl.querySelector(".mystat-chart-tab.active").classList.remove("active");
- e.currentTarget.classList.add("active");
- renderChart();
- }));
- containerEl.querySelectorAll(\'input[name="xaxis-\' + uniqueId + \'"]\').forEach(r => r.addEventListener("change", renderChart));
- if (containerEl.querySelector(".mystat-chart-tab")) renderChart();
- });
- })();';
- wp_add_inline_script( 'mystat-shortcode-loader-' . $activity->id, $js_script );
- }
-
- ?>
-
-
title ); ?>
-
date ) ) ); ?>
-
-
-
-
-
- distance, 2, ',', ' ' ), 'km' ); ?>
- duration ); ?>
- avg_speed, 1, ',', ' ' ), 'km/h' ); ?>
- total_elevation_gain, 'm' ); ?>
-
-
-
-
-
-
- category_name ); ?>
- equipment_name ); ?>
- strava_url ) ) : ?>
- Strava Zobacz aktywność
-
-
-
-
-
-
-
-
-
-
-
-
-
- $label ) :
- ?>
-
-
-
-
-
- Oś X:
- Dystans
-
- Czas
-
-
-
-
-
-
-
-
-
-
-
-
- current_time( 'Y' ),
+ 'month' => current_time( 'n' ),
+ ),
+ $atts,
+ 'statpress_summary'
+ );
+
+ $year = intval( $atts['year'] );
+ $month = intval( $atts['month'] );
+
+ // Pobieranie danych z bazy
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $sql = $wpdb->prepare(
+ "
+ SELECT a.*, c.name as category_name, eq.name as equipment_name
+ FROM $table_activities a
+ LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
+ LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
+ WHERE YEAR(a.date) = %d AND MONTH(a.date) = %d
+ ORDER BY a.date ASC
+ ",
+ $year,
+ $month
+ );
+
+ $activities = $wpdb->get_results( $sql );
+
+ // Obliczanie podsumowań
+ $total_distance = 0;
+ $total_seconds = 0;
+ foreach ( $activities as $activity ) {
+ $total_distance += $activity->distance;
+ if ( ! empty( $activity->duration ) ) {
+ list($h, $m, $s) = explode( ':', $activity->duration );
+ $total_seconds += $h * 3600 + $m * 60 + $s;
+ }
+ }
+ $hours = floor( $total_seconds / 3600 );
+ $minutes = floor( ( $total_seconds % 3600 ) / 60 );
+ $total_duration_formatted = sprintf( '%d godz. %d min.', $hours, $minutes );
+
+ // Rozpoczęcie buforowania wyjścia
+ ob_start();
+ ?>
+
+
+
Podsumowanie miesiąca
+
+
+
+ Całkowity dystans
+ Całkowity czas
+
+
+
+
+ km
+
+
+
+
+
+
Lista aktywności
+
+
+
+ Data
+ Tytuł
+ Kategoria
+
+ Dystans
+ Czas
+ Sprzęt
+
+
+
+
+
+
+ date ) ) ); ?>
+ title ); ?>
+ category_name ); ?>
+ distance, 2, ',', ' ' ); ?> km
+ duration ); ?>
+ equipment_name ); ?>
+
+ avg_speed || $row->avg_heart_rate || $row->avg_cadence ) : ?>
+
+
+
+
+
+ avg_speed ) { echo 'Śr. prędkość '; } ?>
+ avg_heart_rate ) { echo 'Śr. tętno '; } ?>
+ avg_cadence ) { echo 'Śr. kadencja '; } ?>
+
+
+
+
+ avg_speed ) { echo '' . number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h '; } ?>
+ avg_heart_rate ) { echo '' . $row->avg_heart_rate . ' bpm '; } ?>
+ avg_cadence ) { echo '' . $row->avg_cadence . ' rpm '; } ?>
+
+
+
+
+
+
+
+
+
+ Brak aktywności w tym miesiącu.
+
+
+
+
+
+ 0,
+ ),
+ $atts,
+ 'statpress_activity'
+ );
+
+ $activity_id = intval( $atts['id'] );
+
+ if ( 0 === $activity_id ) {
+ return 'Błąd: Nie podano ID wpisu w shortcode.
';
+ }
+
+ // Pobieranie danych z bazy
+ $table_activities = $wpdb->prefix . 'statpress_activities';
+ $sql = $wpdb->prepare(
+ "
+ SELECT a.*, c.name as category_name, c.color as category_color, et.name as event_type_name, eq.name as equipment_name
+ FROM $table_activities a
+ LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
+ LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id
+ LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
+ WHERE a.id = %d
+ ",
+ $activity_id
+ );
+
+ $activity = $wpdb->get_row( $sql );
+
+ if ( ! $activity ) {
+ return 'Błąd: Nie znaleziono wpisu o ID ' . esc_html( $activity_id ) . '.
';
+ }
+
+ // Funkcja pomocnicza do wyświetlania wiersza
+ $render_row = function( $label, $value, $unit = '' ) {
+ if ( ! is_null( $value ) && '' !== $value && 0 != $value ) {
+ echo '';
+ echo '' . esc_html( $label ) . ' ';
+ echo '' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . ' ';
+ echo ' ';
+ }
+ };
+
+ ob_start();
+
+ // Prepare map and chart data if GPX exists
+ $gpx_data = array();
+ $has_gpx_data = false;
+ if ( ! empty( $activity->gpx_url ) ) {
+ $gpx_data = statpress_parse_gpx_data( $activity->gpx_url );
+ $has_gpx_data = ! empty( $gpx_data['points'] );
+ }
+
+ $unique_id = 'statpress-activity-' . esc_attr( $activity->id );
+
+ if ( $has_gpx_data ) {
+ wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
+ wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', array(), '1.9.4', true );
+ wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
+
+ $map_id = 'map-' . $unique_id;
+ $chart_id = 'chart-' . $unique_id;
+
+ $available_profiles = array();
+ if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
+ $available_profiles['elevation'] = 'Wysokość';}
+ if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
+ $available_profiles['speed'] = 'Prędkość';}
+ if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
+ $available_profiles['hr'] = 'Tętno';}
+ if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
+ $available_profiles['cadence'] = 'Kadencja';}
+
+ $has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
+
+ wp_register_script( 'statpress-shortcode-loader-' . $activity->id, false );
+ wp_enqueue_script( 'statpress-shortcode-loader-' . $activity->id );
+
+ $js_script = '
+ (function() {
+ document.addEventListener("DOMContentLoaded", function() {
+ const uniqueId = "' . esc_js( $unique_id ) . '";
+ const containerEl = document.getElementById(uniqueId);
+ if (!containerEl || typeof L === "undefined" || typeof Chart === "undefined") return;
+
+ const trackPoints = ' . json_encode( $gpx_data['points'] ) . ';
+ const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
+ let activeChart = null;
+
+ const mapId = "' . esc_js( $map_id ) . '";
+ const mapEl = document.getElementById(mapId);
+ if (mapEl && trackPoints.length > 0) {
+ if (mapEl._leaflet_id) mapEl._leaflet_id = null;
+ const map = L.map(mapId);
+ L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© OpenStreetMap \' }).addTo(map);
+ const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map);
+ map.fitBounds(polyline.getBounds().pad(0.1));
+ }
+
+ const chartId = "' . esc_js( $chart_id ) . '";
+ const chartEl = document.getElementById(chartId);
+ if (!chartEl) return;
+
+ const chartConfigs = {
+ elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
+ speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
+ hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
+ cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
+ };
+ const xAxisConfigs = {
+ distance: { label: "Dystans (km)", data: profiles.distance },
+ time: { label: "Czas", data: profiles.time, formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8) }
+ };
+
+ function renderChart() {
+ if (activeChart) activeChart.destroy();
+ const activeTab = containerEl.querySelector(".statpress-chart-tab.active");
+ if (!activeTab) return;
+ const chartType = activeTab.dataset.type;
+ const xAxisRadio = containerEl.querySelector(\'input[name="xaxis-\' + uniqueId + \'"]:checked\');
+ if (!xAxisRadio) return;
+ const xAxisType = xAxisRadio.value;
+
+ const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
+ const filteredY = [], filteredX = [];
+ if(yData) {
+ for(let i=0; i v, maxRotation: 0, autoSkip: true, maxTicksLimit: 7 } },
+ y: { title: { display: true, text: chartConfigs[chartType].unit } }
+ },
+ plugins: { legend: { display: false } },
+ interaction: { intersect: false, mode: "index" },
+ }
+ });
+ }
+
+ containerEl.querySelectorAll(".statpress-chart-tab").forEach(t => t.addEventListener("click", e => {
+ e.preventDefault();
+ containerEl.querySelector(".statpress-chart-tab.active").classList.remove("active");
+ e.currentTarget.classList.add("active");
+ renderChart();
+ }));
+ containerEl.querySelectorAll(\'input[name="xaxis-\' + uniqueId + \'"]\').forEach(r => r.addEventListener("change", renderChart));
+ if (containerEl.querySelector(".statpress-chart-tab")) renderChart();
+ });
+ })();';
+ wp_add_inline_script( 'statpress-shortcode-loader-' . $activity->id, $js_script );
+ }
+
+ ?>
+
+
title ); ?>
+
date ) ) ); ?>
+
+
+
+
+
+ distance, 2, ',', ' ' ), 'km' ); ?>
+ duration ); ?>
+ avg_speed, 1, ',', ' ' ), 'km/h' ); ?>
+ total_elevation_gain, 'm' ); ?>
+
+
+
+
+
+
+ category_name ); ?>
+ equipment_name ); ?>
+ strava_url ) ) : ?>
+ Strava Zobacz aktywność
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $label ) :
+ ?>
+
+
+
+
+
+ Oś X:
+ Dystans
+
+ Czas
+
+
+
+
+
+
+
+
+
+
+
+
+ '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;
+ }
+}