Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2277c6d9d | |||
| 908c997b91 | |||
| 9d3ae9a470 | |||
| 09d266204e | |||
| 8f43f5ab4d | |||
| b17463bcbc | |||
| 76de81bca4 | |||
| 842ed77f5b | |||
| 96ed86649a | |||
| fdc4cac593 | |||
| 8f67d9de0a | |||
| 3ae0bc95e0 | |||
| 92ec5e1194 | |||
| 4de5479c41 | |||
| f31630b69c | |||
| efcf06969c | |||
| 56c931f7da | |||
| 54edbd21f6 | |||
| 708a941f13 | |||
| 0b1f289478 | |||
| 82a655a6af |
@@ -14,7 +14,60 @@
|
||||
"Bash(powershell -Command \"& { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::OpenRead\\(''updates/0.20/ver_0.296.zip''\\).Entries | ForEach-Object { Write-Output $_.FullName } }\")",
|
||||
"Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../ver_0.296.zip'' -Force\")",
|
||||
"Bash(powershell -Command \"Add-Type -AssemblyName System.IO.Compression.FileSystem; [IO.Compression.ZipFile]::OpenRead\\(\\(Resolve-Path ''updates/0.20/ver_0.296.zip''\\)\\).Entries.FullName\")",
|
||||
"Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../ver_0.297.zip'' -Force\")"
|
||||
"Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../ver_0.297.zip'' -Force\")",
|
||||
"Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../../updates/0.20/ver_0.299.zip'' -Force\")",
|
||||
"Bash(powershell -Command \"Remove-Item -Recurse -Force 'c:/visual studio code/projekty/shopPRO/temp/temp_299'\":*)",
|
||||
"Bash(powershell -Command \"\\(Get-ChildItem ''c:/visual studio code/projekty/shopPRO/updates/0.20/ver_0.299.zip''\\).Length; [System.IO.Compression.ZipFile]::OpenRead\\(''c:/visual studio code/projekty/shopPRO/updates/0.20/ver_0.299.zip''\\).Entries | ForEach-Object { $_.FullName }\")",
|
||||
"Bash(powershell -Command \"Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::OpenRead\\(''c:/visual studio code/projekty/shopPRO/updates/0.20/ver_0.299.zip''\\).Entries | ForEach-Object { $_.FullName }\")",
|
||||
"Bash(unzip:*)",
|
||||
"mcp__serena__find_symbol",
|
||||
"mcp__serena__find_file",
|
||||
"mcp__serena__activate_project",
|
||||
"mcp__serena__check_onboarding_performed",
|
||||
"Bash(tail:*)",
|
||||
"WebFetch(domain:shoppro.project-dc.pl)",
|
||||
"Bash(cd:*)",
|
||||
"mcp__serena__get_symbols_overview",
|
||||
"mcp__serena__search_for_pattern",
|
||||
"mcp__serena__read_file",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && powershell.exe -ExecutionPolicy Bypass -File \"C:/visual studio code/projekty/shopPRO/test.ps1\" 2>&1)",
|
||||
"mcp__serena__replace_content",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && npx sass admin/layout/style-scss/style.scss admin/layout/style-css/style.css --source-map 2>&1)",
|
||||
"Bash(head:*)",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && rm -rf temp/temp_304 && powershell -File \"./build-update.ps1\" -FromTag v0.303 -ToTag v0.304 -ChangelogEntry \"NEW - konfigurowalne limity kwotowe metod platnosci \\(min/max kwota zamowienia\\)\" 2>&1)",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && rm -rf temp/temp_305 && powershell -File \"./build-update.ps1\" -FromTag v0.304 -ToTag v0.305 -ChangelogEntry \"FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku\" 2>&1)",
|
||||
"Bash(xxd:*)",
|
||||
"mcp__serena__list_dir",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && powershell -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.305 -ToTag v0.306 -ChangelogEntry \"FIX - ukrywanie form dostawy gdy nie ma dostepnych form platnosci\" 2>&1)",
|
||||
"Bash(powershell:*)",
|
||||
"Bash(powershell.exe:*)",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && rm -f updates/0.30/ver_0.304.zip updates/0.30/ver_0.304_manifest.json updates/0.30/ver_0.304_sql.txt updates/0.30/ver_0.304_files.txt && powershell -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.303 -ToTag v0.304 -ChangelogEntry \"NEW - konfigurowalne limity kwotowe metod platnosci \\(min/max kwota zamowienia\\)\" 2>&1)",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && rm -f updates/0.30/ver_0.305.zip updates/0.30/ver_0.305_manifest.json updates/0.30/ver_0.305_sql.txt updates/0.30/ver_0.305_files.txt && powershell -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.304 -ToTag v0.305 -ChangelogEntry \"FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku\" 2>&1)",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && rm -rf temp/temp_305 && powershell -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.304 -ToTag v0.305 -ChangelogEntry \"FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku\" 2>&1)",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && rm -rf temp/temp_305 && sleep 2 && powershell -ExecutionPolicy Bypass -Command \"& { \\\\$env:DOTNET_GCServer = 1; & './build-update.ps1' -FromTag v0.304 -ToTag v0.305 -ChangelogEntry 'FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku' }\" 2>&1)",
|
||||
"Bash(python3:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(grep ^<b>ver:*)",
|
||||
"Bash(claude mcp:*)",
|
||||
"mcp__serena__get_current_config",
|
||||
"Bash(cd \"C:\\\\visual studio code\\\\projekty\\\\shopPRO\" && npx sass admin/layout/style-scss/style.scss admin/layout/style-css/style.css --no-source-map 2>&1 || sass admin/layout/style-scss/style.scss admin/layout/style-css/style.css --no-source-map 2>&1)",
|
||||
"Bash(echo no 7z:*)",
|
||||
"Bash(cd \"C:/visual studio code/projekty/shopPRO\" && php -r \"\n\\\\$files = [\n 'admin/templates/integrations/logs.php',\n 'admin/templates/site/main-layout.php',\n 'autoload/Domain/Integrations/IntegrationsRepository.php',\n 'autoload/admin/Controllers/IntegrationsController.php',\n];\n\\\\$zipPath = 'updates/0.30/ver_0.310.zip';\nif \\(!is_dir\\('updates/0.30'\\)\\) mkdir\\('updates/0.30', 0777, true\\);\nif \\(file_exists\\(\\\\$zipPath\\)\\) unlink\\(\\\\$zipPath\\);\n\\\\$zip = new ZipArchive\\(\\);\nif \\(\\\\$zip->open\\(\\\\$zipPath, ZipArchive::CREATE\\) !== true\\) { echo 'Cannot create ZIP'; exit\\(1\\); }\nforeach \\(\\\\$files as \\\\$f\\) {\n if \\(file_exists\\(\\\\$f\\)\\) {\n \\\\$zip->addFile\\(\\\\$f, \\\\$f\\);\n echo \\\\\"Added: \\\\$f\\\\n\\\\\";\n } else {\n echo \\\\\"MISSING: \\\\$f\\\\n\\\\\";\n }\n}\n\\\\$zip->close\\(\\);\necho \\\\\"ZIP created: \\\\$zipPath \\(\\\\\".filesize\\(\\\\$zipPath\\).\\\\\" bytes\\)\\\\n\\\\\";\n\" 2>&1)",
|
||||
"Bash(where jar:*)",
|
||||
"Bash(echo php not found:*)",
|
||||
"Bash(/c/xampp/php/php:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(rm -rf \"/c/visual studio code/projekty/shopPRO/temp/temp_313\" && cd \"/c/visual studio code/projekty/shopPRO\" && powershell -File ./build-update.ps1 -FromTag v0.312 -ToTag v0.313 -ChangelogEntry \"FIX - sync płatności Apilo \\(int cast na apilo_order_id PPxxxxxx dawał 0\\) + logowanie decyzji sync do pp_log\" 2>&1)",
|
||||
"Bash(which php:*)",
|
||||
"mcp__serena__replace_symbol_body",
|
||||
"mcp__serena__insert_after_symbol",
|
||||
"Bash(php:*)",
|
||||
"Bash(rm -rf \"C:/visual studio code/projekty/shopPRO/temp/temp_314\" && cd \"C:/visual studio code/projekty/shopPRO\" && powershell -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.313 -ToTag v0.314 -ChangelogEntry \"FIX - naprawa globalnej wyszukiwarki admin \\(Content-Type, Cache-Control, POST, try/catch\\), NEW - title strony z numerem zamówienia\" 2>&1)",
|
||||
"mcp__serena__initial_instructions",
|
||||
"mcp__serena__list_memories",
|
||||
"mcp__serena__find_referencing_symbols"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
.serena/.gitignore
vendored
Normal file
1
.serena/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/cache
|
||||
118
.serena/project.yml
Normal file
118
.serena/project.yml
Normal file
@@ -0,0 +1,118 @@
|
||||
# the name by which the project can be referenced within Serena
|
||||
project_name: "shopPRO"
|
||||
|
||||
|
||||
# list of languages for which language servers are started; choose from:
|
||||
# al bash clojure cpp csharp
|
||||
# csharp_omnisharp dart elixir elm erlang
|
||||
# fortran fsharp go groovy haskell
|
||||
# java julia kotlin lua markdown
|
||||
# matlab nix pascal perl php
|
||||
# php_phpactor powershell python python_jedi r
|
||||
# rego ruby ruby_solargraph rust scala
|
||||
# swift terraform toml typescript typescript_vts
|
||||
# vue yaml zig
|
||||
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||
# Note:
|
||||
# - For C, use cpp
|
||||
# - For JavaScript, use typescript
|
||||
# - For Free Pascal/Lazarus, use pascal
|
||||
# Special requirements:
|
||||
# Some languages require additional setup/installations.
|
||||
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||
# The first language is the default language and the respective language server will be used as a fallback.
|
||||
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||
languages:
|
||||
- typescript
|
||||
- php
|
||||
|
||||
# the encoding used by text files in the project
|
||||
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||
encoding: "utf-8"
|
||||
|
||||
# whether to use project's .gitignore files to ignore files
|
||||
ignore_all_files_in_gitignore: true
|
||||
|
||||
# list of additional paths to ignore in this project.
|
||||
# Same syntax as gitignore, so you can use * and **.
|
||||
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||
ignored_paths: []
|
||||
|
||||
# whether the project is in read-only mode
|
||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
#
|
||||
# * `activate_project`: Activates a project by name.
|
||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||
# * `delete_lines`: Deletes a range of lines within a file.
|
||||
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||
# * `execute_shell_command`: Executes a shell command.
|
||||
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||
# Should only be used in settings where the system prompt cannot be set,
|
||||
# e.g. in clients you have no control over, like Claude Desktop.
|
||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||
# * `read_file`: Reads a file within the project directory.
|
||||
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||
# * `remove_project`: Removes a project from the Serena configuration.
|
||||
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||
# * `switch_modes`: Activates modes by providing a list of their names
|
||||
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
fixed_tools: []
|
||||
|
||||
# list of mode names to that are always to be included in the set of active modes
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this setting overrides the global configuration.
|
||||
# Set this to [] to disable base modes for this project.
|
||||
# Set this to a list of mode names to always include the respective modes for this project.
|
||||
base_modes:
|
||||
|
||||
# list of mode names that are to be activated by default.
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||
default_modes:
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
# override of the corresponding setting in serena_config.yml, see the documentation there.
|
||||
# If null or missing, the value from the global config is used.
|
||||
symbol_info_budget:
|
||||
@@ -18,9 +18,7 @@ test.ps1
|
||||
memory/
|
||||
|
||||
# Infrastruktura aktualizacji (meta, nie runtime)
|
||||
updates/changelog.php
|
||||
updates/versions.php
|
||||
updates/install.php
|
||||
updates/
|
||||
.updateignore
|
||||
build-update.ps1
|
||||
migrations/
|
||||
@@ -46,3 +44,7 @@ cron/temp/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.serena/
|
||||
|
||||
# Cache testów
|
||||
.phpunit.result.cache
|
||||
|
||||
6
.vscode/ftp-kr.json
vendored
6
.vscode/ftp-kr.json
vendored
@@ -14,6 +14,10 @@
|
||||
".git",
|
||||
".svn",
|
||||
"/.vscode",
|
||||
"/temp/*"
|
||||
"/temp",
|
||||
"/.serena",
|
||||
"/.claude",
|
||||
"/docs",
|
||||
"/tests"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ composer test
|
||||
|
||||
PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`.
|
||||
|
||||
Current suite: **739 tests, 2089 assertions**.
|
||||
Current suite: **758 tests, 2135 assertions**.
|
||||
|
||||
### Creating Updates
|
||||
See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs.
|
||||
|
||||
@@ -44,6 +44,15 @@ define( 'REDBEAN_MODEL_PREFIX', '' );
|
||||
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
$mdb = new medoo( [
|
||||
'database_type' => 'mysql',
|
||||
'database_name' => $database['name'],
|
||||
'server' => $database['host'],
|
||||
'username' => $database['user'],
|
||||
'password' => $database['password'],
|
||||
'charset' => 'utf8'
|
||||
] );
|
||||
|
||||
$settings = ( new \Domain\Settings\SettingsRepository( $mdb ) )->allSettings();
|
||||
|
||||
if ( file_exists( 'config.php' ) )
|
||||
@@ -79,15 +88,6 @@ if ( !$lang = \Shared\Helpers\Helpers::get_session( 'lang-' . $lang_id ) )
|
||||
\Shared\Helpers\Helpers::set_session( 'lang-' . $lang_id, $lang );
|
||||
}
|
||||
|
||||
$mdb = new medoo( [
|
||||
'database_type' => 'mysql',
|
||||
'database_name' => $database['name'],
|
||||
'server' => $database['host'],
|
||||
'username' => $database['user'],
|
||||
'password' => $database['password'],
|
||||
'charset' => 'utf8'
|
||||
] );
|
||||
|
||||
$user = \Shared\Helpers\Helpers::get_session( 'user', true );
|
||||
|
||||
\admin\App::update();
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -455,23 +455,14 @@ body {
|
||||
}
|
||||
|
||||
.site-content {
|
||||
|
||||
&.with-menu {
|
||||
width: 100%;
|
||||
|
||||
@include respond-above(xs) {
|
||||
width: calc(100% - 243px);
|
||||
|
||||
margin-left: 243px;
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-below(md) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
margin-left: 0;
|
||||
background-color: #fff;
|
||||
margin-left: 244px;
|
||||
|
||||
@include respond-above(xs) {
|
||||
width: calc(100% - 243px);
|
||||
margin-left: 243px;
|
||||
}
|
||||
|
||||
|
||||
.top-user {
|
||||
text-align: right;
|
||||
@@ -1750,33 +1741,16 @@ li.sort-collapsed.sort-hover div {
|
||||
}
|
||||
}
|
||||
|
||||
#table-products {
|
||||
.product-categories {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-wrap: wrap;
|
||||
}
|
||||
.product-categories {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-wrap: wrap;
|
||||
|
||||
.product-name {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.duplicate-product {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.duplicate-product {
|
||||
float: right;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
color: #FFF !important;
|
||||
|
||||
&.btn-create-product {
|
||||
margin-top: 5px;
|
||||
}
|
||||
&--cats {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2104,6 +2078,10 @@ textarea.form-control {
|
||||
}
|
||||
|
||||
.order-details {
|
||||
.fa-copy {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.paid-status {
|
||||
margin-top: 10px;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ $buildUrl = function(array $params = []) use ($list): string {
|
||||
}
|
||||
}
|
||||
$qs = http_build_query($query);
|
||||
return $list->basePath . ($qs ? ('?' . $qs) : '');
|
||||
return $list->basePath . $qs;
|
||||
};
|
||||
|
||||
$currentSort = $list->sort['column'] ?? '';
|
||||
@@ -92,7 +92,7 @@ $isCompactColumn = function(array $column): bool {
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="js-table-filters-wrapper table-filters-wrapper<?= $hasActiveFilters ? ' open' : ''; ?>">
|
||||
<form method="get" action="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" class="row mb15 js-table-filters-form">
|
||||
<form method="get" action="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" data-path-submit="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" class="row mb15 js-table-filters-form">
|
||||
<?php foreach ($list->filters as $filter): ?>
|
||||
<?php
|
||||
$filterKey = (string)($filter['key'] ?? '');
|
||||
@@ -162,7 +162,7 @@ $isCompactColumn = function(array $column): bool {
|
||||
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Szukaj</button>
|
||||
<a href="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-default btn-sm">Wyczyść</a>
|
||||
<a href="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-default btn-sm js-table-filters-clear">Wyczyść</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -292,7 +292,7 @@ $isCompactColumn = function(array $column): bool {
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-6 text-right">
|
||||
<form method="get" action="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" class="form-inline table-list-per-page-form">
|
||||
<form method="get" action="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" data-path-submit="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" class="form-inline table-list-per-page-form">
|
||||
<?php foreach ($list->query as $key => $value): ?>
|
||||
<?php if ($key !== 'per_page' && $key !== 'page'): ?>
|
||||
<input type="hidden" name="<?= htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); ?>" value="<?= htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); ?>" />
|
||||
@@ -300,7 +300,7 @@ $isCompactColumn = function(array $column): bool {
|
||||
<?php endforeach; ?>
|
||||
<input type="hidden" name="page" value="1" />
|
||||
Wyświetlaj
|
||||
<select name="per_page" class="form-control input-sm" onchange="this.form.submit()">
|
||||
<select name="per_page" class="form-control input-sm js-per-page-select">
|
||||
<?php foreach ($list->perPageOptions as $opt): ?>
|
||||
<option value="<?= (int)$opt; ?>"<?= ((int)$opt === $perPage) ? ' selected="selected"' : ''; ?>><?= (int)$opt; ?></option>
|
||||
<?php endforeach; ?>
|
||||
@@ -312,6 +312,40 @@ $isCompactColumn = function(array $column): bool {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
// Table state persistence — redirect ASAP to saved view
|
||||
(function() {
|
||||
var basePath = <?= json_encode($list->basePath); ?>;
|
||||
var stateKey = 'tableListQuery_' + basePath;
|
||||
var clearKey = 'tableListCleared_' + basePath;
|
||||
|
||||
var pathname = window.location.pathname.replace(/\/+$/, '/');
|
||||
var bp = basePath.replace(/\/+$/, '/');
|
||||
|
||||
var queryPart = '';
|
||||
if (pathname.length > bp.length && pathname.indexOf(bp) === 0) {
|
||||
queryPart = pathname.substring(bp.length);
|
||||
}
|
||||
if (!queryPart && window.location.search) {
|
||||
queryPart = window.location.search.substring(1);
|
||||
}
|
||||
|
||||
try {
|
||||
var justCleared = sessionStorage.getItem(clearKey) === '1';
|
||||
sessionStorage.removeItem(clearKey);
|
||||
|
||||
if (queryPart) {
|
||||
localStorage.setItem(stateKey, queryPart);
|
||||
} else if (!justCleared) {
|
||||
var saved = localStorage.getItem(stateKey);
|
||||
if (saved) {
|
||||
window.location.replace(basePath + saved);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
if (!$) {
|
||||
@@ -529,5 +563,38 @@ $isCompactColumn = function(array $column): bool {
|
||||
saveFilterState(true);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Path-based form submission (admin URL routing) ---
|
||||
$(document).off('submit.tablePathSubmit', 'form[data-path-submit]');
|
||||
$(document).on('submit.tablePathSubmit', 'form[data-path-submit]', function(e) {
|
||||
e.preventDefault();
|
||||
var basePath = $(this).attr('data-path-submit');
|
||||
var data = $(this).serializeArray();
|
||||
var parts = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (String(data[i].value) !== '') {
|
||||
parts.push(encodeURIComponent(data[i].name) + '=' + encodeURIComponent(data[i].value));
|
||||
}
|
||||
}
|
||||
window.location.href = basePath + (parts.length ? parts.join('&') : '');
|
||||
});
|
||||
|
||||
// Per-page select auto-submit
|
||||
$(document).off('change.tablePerPage', '.js-per-page-select');
|
||||
$(document).on('change.tablePerPage', '.js-per-page-select', function() {
|
||||
$(this).closest('form').trigger('submit');
|
||||
});
|
||||
|
||||
// --- Table state clear on "Wyczyść" ---
|
||||
var stateStorageKey = 'tableListQuery_' + <?= json_encode($list->basePath); ?>;
|
||||
var stateClearKey = 'tableListCleared_' + <?= json_encode($list->basePath); ?>;
|
||||
|
||||
$(document).off('click.tableClearState', '.js-table-filters-clear');
|
||||
$(document).on('click.tableClearState', '.js-table-filters-clear', function() {
|
||||
try {
|
||||
localStorage.removeItem(stateStorageKey);
|
||||
sessionStorage.setItem(stateClearKey, '1');
|
||||
} catch (e) {}
|
||||
});
|
||||
})(window.jQuery);
|
||||
</script>
|
||||
|
||||
19
admin/templates/integrations/logs.php
Normal file
19
admin/templates/integrations/logs.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?= \Shared\Tpl\Tpl::view('components/table-list', ['list' => $this->viewModel]); ?>
|
||||
|
||||
<div class="mt15">
|
||||
<a href="/admin/integrations/logs_clear/" class="btn btn-danger btn-sm"
|
||||
onclick="return confirm('Na pewno chcesz usunac wszystkie logi?');">
|
||||
<i class="fa fa-trash"></i> Wyczysc wszystkie logi
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('body').on('click', '.log-context-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var id = $(this).data('id');
|
||||
$('#log-context-' + id).toggle();
|
||||
$(this).text($('#log-context-' + id).is(':visible') ? 'Ukryj' : 'Pokaz');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -3,6 +3,7 @@ $orderId = (int)($this -> order['id'] ?? 0);
|
||||
?>
|
||||
|
||||
<div class="site-title">Szczegóły zamówienia: <?= htmlspecialchars((string)($this -> order['number'] ?? ''), ENT_QUOTES, 'UTF-8');?></div>
|
||||
<script>document.title = 'Zamówienie <?= htmlspecialchars((string)($this -> order['number'] ?? ''), ENT_QUOTES, 'UTF-8');?> - shopPro';</script>
|
||||
|
||||
<div class="od-actions mb15">
|
||||
<a href="/admin/shop_order/list/" class="btn btn-dark btn-sm">
|
||||
@@ -89,6 +90,19 @@ $orderId = (int)($this -> order['id'] ?? 0);
|
||||
<div>
|
||||
<b><?= $this -> order[ 'payment_method' ];?> </b>
|
||||
</div>
|
||||
<? if ( !empty($this -> order['apilo_order_id']) ):?>
|
||||
<br/>
|
||||
<div>
|
||||
<i class="fa fa-cloud"></i> Apilo: <b style="color: #27ae60;">tak</b>
|
||||
— ID: <b id="order-apilo-id"><?= htmlspecialchars((string)$this -> order['apilo_order_id'], ENT_QUOTES, 'UTF-8');?></b>
|
||||
<i class="fa fa-copy" onclick="copyToClipboard( 'order-apilo-id' ); return false;"></i>
|
||||
</div>
|
||||
<? else:?>
|
||||
<br/>
|
||||
<div>
|
||||
<i class="fa fa-cloud"></i> Apilo: <b style="color: #c0392b;">nie</b>
|
||||
</div>
|
||||
<? endif;?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paid-status panel">
|
||||
@@ -184,13 +198,14 @@ $orderId = (int)($this -> order['id'] ?? 0);
|
||||
<?= $product[ 'message' ] != '' ? '<strong>Wiadomość:</strong> ' . $product['message'] : '';?>
|
||||
</div>
|
||||
<div class="od-mobile-price-line">
|
||||
<?= (int)$product['quantity'];?> × <?= \Shared\Helpers\Helpers::decimal( $product['price_brutto_promo'] );?> = <?= \Shared\Helpers\Helpers::decimal( $product['price_brutto_promo'] * $product['quantity'] );?> zł
|
||||
<? $effective = ((float)$product['price_brutto_promo'] > 0 && (float)$product['price_brutto_promo'] < (float)$product['price_brutto']) ? (float)$product['price_brutto_promo'] : (float)$product['price_brutto'];?>
|
||||
<?= (int)$product['quantity'];?> × <?= \Shared\Helpers\Helpers::decimal( $effective );?> = <?= \Shared\Helpers\Helpers::decimal( $effective * $product['quantity'] );?> zł
|
||||
</div>
|
||||
</td>
|
||||
<td class="tab-center"><?= $product[ 'quantity' ];?></td>
|
||||
<td class="tab-right"><?= \Shared\Helpers\Helpers::decimal( $product[ 'price_brutto' ] );?> zł</td>
|
||||
<td class="tab-right"><?= \Shared\Helpers\Helpers::decimal( $product[ 'price_brutto_promo' ] );?> zł</td>
|
||||
<td class="tab-right"><?= \Shared\Helpers\Helpers::decimal( $product[ 'price_brutto_promo' ] * $product[ 'quantity' ] );?> zł</td>
|
||||
<td class="tab-right"><?= \Shared\Helpers\Helpers::decimal( $effective );?> zł</td>
|
||||
<td class="tab-right"><?= \Shared\Helpers\Helpers::decimal( $effective * $product[ 'quantity' ] );?> zł</td>
|
||||
</tr>
|
||||
<? endforeach; endif;?>
|
||||
</tbody>
|
||||
|
||||
@@ -8,46 +8,52 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="author" content="www.project-pro.pl - internetowe rozwiązania dla biznesu">
|
||||
<link rel='stylesheet' type="text/css" href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700'>
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/skin/default_skin/css/theme.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/magnific/magnific-popup.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/datepicker/css/bootstrap-datetimepicker.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.structure.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.theme.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/daterange/daterangepicker.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/jquery-confirm/jquery-confirm.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/easy-tabs/css/easy-responsive-tabs.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/bootstrap-4.5.2-dist/css/bootstrap.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/font-awesome-4.7.0/css/font-awesome.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/minimal.css">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/blue.css">
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery-1.11.1.min.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.min.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/js/utility/utility.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/magnific/jquery.magnific-popup.js"></script>
|
||||
<script type="text/javascript" src="/libraries/easy-tabs/js/easyResponsiveTabs.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/moment/moment.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/moment/pl.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/datepicker/js/bootstrap-datetimepicker.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/daterange/daterangepicker.js"></script>
|
||||
<script type="text/javascript" src="/libraries/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="/libraries/bootstrap-4.5.2-dist/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="/libraries/bootstrap-4.5.2-dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script type="text/javascript" src="/libraries/grid/plugins/icheck/icheck.js"></script>
|
||||
<script type="text/javascript" src="/libraries/functions.js"></script>
|
||||
<script type="text/javascript" src="/admin/js/functions.js"></script>
|
||||
<link rel="stylesheet" href="/admin/layout/style-css/style.css" />
|
||||
<link rel="stylesheet" href="/admin/layout/style-css/table-list.css" />
|
||||
<link rel="stylesheet" href="/admin/layout/style-css/order-details-mobile.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/skin/default_skin/css/theme.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/skin/default_skin/css/theme.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/magnific/magnific-popup.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/magnific/magnific-popup.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/datepicker/css/bootstrap-datetimepicker.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/datepicker/css/bootstrap-datetimepicker.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.structure.min.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.structure.min.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.theme.min.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.theme.min.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/daterange/daterangepicker.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/daterange/daterangepicker.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/jquery-confirm/jquery-confirm.min.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/jquery-confirm/jquery-confirm.min.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/easy-tabs/css/easy-responsive-tabs.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/easy-tabs/css/easy-responsive-tabs.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/bootstrap-4.5.2-dist/css/bootstrap.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/bootstrap-4.5.2-dist/css/bootstrap.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/font-awesome-4.7.0/css/font-awesome.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/font-awesome-4.7.0/css/font-awesome.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/minimal.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/grid/plugins/icheck/skins/minimal/minimal.css'); ?>">
|
||||
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/blue.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/grid/plugins/icheck/skins/minimal/blue.css'); ?>">
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery-1.11.1.min.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/jquery/jquery-1.11.1.min.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.min.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.min.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/js/utility/utility.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/js/utility/utility.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/magnific/jquery.magnific-popup.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/magnific/jquery.magnific-popup.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/easy-tabs/js/easyResponsiveTabs.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/easy-tabs/js/easyResponsiveTabs.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/moment/moment.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/moment/moment.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/moment/pl.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/moment/pl.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/datepicker/js/bootstrap-datetimepicker.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/datepicker/js/bootstrap-datetimepicker.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/daterange/daterangepicker.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/framework/vendor/plugins/daterange/daterangepicker.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/jquery-confirm/jquery-confirm.min.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/jquery-confirm/jquery-confirm.min.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/bootstrap-4.5.2-dist/js/bootstrap.min.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/bootstrap-4.5.2-dist/js/bootstrap.min.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/bootstrap-4.5.2-dist/js/bootstrap.bundle.min.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/bootstrap-4.5.2-dist/js/bootstrap.bundle.min.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/grid/plugins/icheck/icheck.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/grid/plugins/icheck/icheck.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/libraries/functions.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/libraries/functions.js'); ?>"></script>
|
||||
<script type="text/javascript" src="/admin/js/functions.js?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/admin/js/functions.js'); ?>"></script>
|
||||
<link rel="stylesheet" href="/admin/layout/style-css/style.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/admin/layout/style-css/style.css'); ?>" />
|
||||
<link rel="stylesheet" href="/admin/layout/style-css/table-list.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/admin/layout/style-css/table-list.css'); ?>" />
|
||||
<link rel="stylesheet" href="/admin/layout/style-css/order-details-mobile.css?ver=<?= filemtime($_SERVER['DOCUMENT_ROOT'] . '/admin/layout/style-css/order-details-mobile.css'); ?>" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-page">
|
||||
<div class="menu">
|
||||
<div class="logo sticky-top">
|
||||
shop<b>Pro</b>
|
||||
<span>ver. <?= \Shared\Helpers\Helpers::get_version();?></span><br>
|
||||
<? if ( $settings[ 'update' ] and \Shared\Helpers\Helpers::get_new_version() > \Shared\Helpers\Helpers::get_version() ):?>
|
||||
<a href="/admin/update/main_view/" class="label label-danger">aktualizacja</a>
|
||||
<? endif;?>
|
||||
<span>ver. <?= \Shared\Helpers\Helpers::get_version();?>
|
||||
<? if ( $settings[ 'update' ] ):?>
|
||||
<i class="fa fa-refresh check-update-btn" id="check-update-btn" title="Sprawdź aktualizacje" style="cursor:pointer;margin-left:4px;font-size:11px;opacity:0.7;"></i>
|
||||
<? endif;?>
|
||||
</span><br>
|
||||
<span id="update-badge-wrap">
|
||||
<? if ( $settings[ 'update' ] and \Shared\Helpers\Helpers::get_new_version() > \Shared\Helpers\Helpers::get_version() ):?>
|
||||
<a href="/admin/update/main_view/" class="label label-danger">aktualizacja</a>
|
||||
<? endif;?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<ul>
|
||||
@@ -147,6 +153,11 @@
|
||||
<i class="fa fa-cogs" aria-hidden="true"></i>shopPRO
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/integrations/logs/">
|
||||
<i class="fa fa-list-alt" aria-hidden="true"></i>Logi
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="preview">
|
||||
@@ -311,7 +322,7 @@
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/settings/globalSearchAjax/',
|
||||
type: 'GET',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: { q: phrase },
|
||||
success: function(response) {
|
||||
@@ -322,8 +333,12 @@
|
||||
|
||||
renderResults(response.items || []);
|
||||
},
|
||||
error: function() {
|
||||
$results.html('<div class="admin-global-search-empty">Błąd połączenia</div>').addClass('open');
|
||||
error: function(xhr) {
|
||||
var msg = 'Błąd połączenia';
|
||||
if (xhr.status === 200) {
|
||||
msg = 'Błąd parsowania odpowiedzi';
|
||||
}
|
||||
$results.html('<div class="admin-global-search-empty">' + msg + '</div>').addClass('open');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -356,6 +371,32 @@
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
$(document).off('click.checkUpdate', '#check-update-btn').on('click.checkUpdate', '#check-update-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var $btn = $(this);
|
||||
if ($btn.hasClass('fa-spin')) return;
|
||||
$btn.addClass('fa-spin').css('opacity', 1);
|
||||
$.ajax({
|
||||
url: '/admin/update/checkUpdate/',
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
$btn.removeClass('fa-spin').css('opacity', 0.7);
|
||||
var $wrap = $('#update-badge-wrap');
|
||||
if (data.has_update) {
|
||||
$wrap.html('<a href="/admin/update/main_view/" class="label label-danger">aktualizacja</a>');
|
||||
} else {
|
||||
$wrap.html('');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$btn.removeClass('fa-spin').css('opacity', 0.7);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
$(document).ready(function () {
|
||||
var user_agent = navigator.userAgent.toLowerCase();
|
||||
var click_event = user_agent.match(/(iphone|ipod|ipad)/) ? "touchend" : "click";
|
||||
|
||||
@@ -48,7 +48,7 @@ class AttributeRepository
|
||||
FROM pp_shop_attributes AS sa
|
||||
WHERE {$whereSql}
|
||||
";
|
||||
$stmtCount = $this->db->query($sqlCount, $params);
|
||||
$stmtCount = $this->db->query($sqlCount, $whereData['params']);
|
||||
$countRows = $stmtCount ? $stmtCount->fetchAll() : [];
|
||||
$total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0;
|
||||
|
||||
|
||||
30
autoload/Domain/Integrations/ApiloLogger.php
Normal file
30
autoload/Domain/Integrations/ApiloLogger.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Domain\Integrations;
|
||||
|
||||
class ApiloLogger
|
||||
{
|
||||
/**
|
||||
* @param \medoo $db
|
||||
* @param string $action np. 'send_order', 'payment_sync', 'status_sync', 'status_poll'
|
||||
* @param int|null $orderId
|
||||
* @param string $message
|
||||
* @param mixed $context dane do zapisania jako JSON (request/response)
|
||||
*/
|
||||
public static function log($db, string $action, ?int $orderId, string $message, $context = null): void
|
||||
{
|
||||
$contextJson = null;
|
||||
if ($context !== null) {
|
||||
$contextJson = is_string($context)
|
||||
? $context
|
||||
: json_encode($context, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$db->insert('pp_log', [
|
||||
'action' => $action,
|
||||
'order_id' => $orderId,
|
||||
'message' => $message,
|
||||
'context' => $contextJson,
|
||||
'date' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,63 @@ class IntegrationsRepository
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Logs ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Pobiera logi z tabeli pp_log z paginacją, sortowaniem i filtrowaniem.
|
||||
*
|
||||
* @return array{items:array, total:int}
|
||||
*/
|
||||
public function getLogs( array $filters, string $sortColumn, string $sortDir, int $page, int $perPage ): array
|
||||
{
|
||||
$where = [];
|
||||
|
||||
if ( !empty( $filters['log_action'] ) ) {
|
||||
$where['action[~]'] = '%' . $filters['log_action'] . '%';
|
||||
}
|
||||
|
||||
if ( !empty( $filters['message'] ) ) {
|
||||
$where['message[~]'] = '%' . $filters['message'] . '%';
|
||||
}
|
||||
|
||||
if ( !empty( $filters['order_id'] ) ) {
|
||||
$where['order_id'] = (int) $filters['order_id'];
|
||||
}
|
||||
|
||||
$total = $this->db->count( 'pp_log', $where );
|
||||
|
||||
$where['ORDER'] = [ $sortColumn => $sortDir ];
|
||||
$where['LIMIT'] = [ ( $page - 1 ) * $perPage, $perPage ];
|
||||
|
||||
$items = $this->db->select( 'pp_log', '*', $where );
|
||||
if ( !is_array( $items ) ) {
|
||||
$items = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => (int) $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Usuwa wpis logu po ID.
|
||||
*/
|
||||
public function deleteLog( int $id ): bool
|
||||
{
|
||||
$this->db->delete( 'pp_log', [ 'id' => $id ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Czyści wszystkie logi z tabeli pp_log.
|
||||
*/
|
||||
public function clearLogs(): bool
|
||||
{
|
||||
$this->db->delete( 'pp_log', [] );
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Product linking (Apilo) ─────────────────────────────────
|
||||
|
||||
public function linkProduct( int $productId, $externalId, $externalName ): bool
|
||||
|
||||
@@ -30,6 +30,14 @@ class OrderAdminService
|
||||
return $this->orders->orderStatuses();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{names: array<int, string>, colors: array<int, string>}
|
||||
*/
|
||||
public function statusData(): array
|
||||
{
|
||||
return $this->orders->orderStatusData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{items: array<int, array<string, mixed>>, total: int}
|
||||
*/
|
||||
@@ -385,17 +393,38 @@ class OrderAdminService
|
||||
global $mdb;
|
||||
|
||||
if ($orderId <= 0) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$mdb,
|
||||
'resend_order',
|
||||
$orderId,
|
||||
'Nieprawidlowe ID zamowienia (orderId <= 0)',
|
||||
['order_id' => $orderId]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = $this->orders->findForAdmin($orderId);
|
||||
if (empty($order) || empty($order['apilo_order_id'])) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$mdb,
|
||||
'resend_order',
|
||||
$orderId,
|
||||
'Brak zamowienia lub brak apilo_order_id',
|
||||
['order_found' => !empty($order), 'apilo_order_id' => $order['apilo_order_id'] ?? null]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
$accessToken = $integrationsRepository -> apiloGetAccessToken();
|
||||
if (!$accessToken) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$mdb,
|
||||
'resend_order',
|
||||
$orderId,
|
||||
'Nie udalo sie uzyskac tokenu Apilo (access token)',
|
||||
['apilo_order_id' => $order['apilo_order_id']]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -417,13 +446,29 @@ class OrderAdminService
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$apiloResultRaw = curl_exec($ch);
|
||||
$http_code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$apiloResult = json_decode((string)$apiloResultRaw, true);
|
||||
|
||||
if (!is_array($apiloResult) || (int)($apiloResult['updates'] ?? 0) !== 1) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$mdb,
|
||||
'resend_order',
|
||||
$orderId,
|
||||
'Błąd ponownego wysyłania zamówienia do Apilo (HTTP: ' . $http_code . ')',
|
||||
['apilo_order_id' => $order['apilo_order_id'], 'http_code' => $http_code, 'response' => $apiloResult]
|
||||
);
|
||||
curl_close($ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$mdb,
|
||||
'resend_order',
|
||||
$orderId,
|
||||
'Zamówienie ponownie wysłane do Apilo (apilo_order_id: ' . $order['apilo_order_id'] . ')',
|
||||
['apilo_order_id' => $order['apilo_order_id'], 'http_code' => $http_code, 'response' => $apiloResult]
|
||||
);
|
||||
|
||||
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'pp_shop_orders' AND COLUMN_NAME != 'id'";
|
||||
$stmt = $mdb->query($query);
|
||||
$columns = $stmt ? $stmt->fetchAll(\PDO::FETCH_COLUMN) : [];
|
||||
@@ -509,9 +554,26 @@ class OrderAdminService
|
||||
|
||||
$error = '';
|
||||
$sync_failed = false;
|
||||
$max_attempts = 50; // ~8h przy cronie co 10 min
|
||||
|
||||
// Zamówienie jeszcze nie wysłane do Apilo — czekaj na crona
|
||||
if (!(int)$order['apilo_order_id']) {
|
||||
$attempts = (int)($task['attempts'] ?? 0) + 1;
|
||||
if ($attempts >= $max_attempts) {
|
||||
// Przekroczono limit prób — porzuć task
|
||||
unset($queue[$key]);
|
||||
} else {
|
||||
$task['attempts'] = $attempts;
|
||||
$task['last_error'] = 'awaiting_apilo_order';
|
||||
$task['updated_at'] = date('Y-m-d H:i:s');
|
||||
$queue[$key] = $task;
|
||||
}
|
||||
$processed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$payment_pending = !empty($task['payment']) && (int)$order['paid'] === 1;
|
||||
if ($payment_pending && (int)$order['apilo_order_id']) {
|
||||
if ($payment_pending) {
|
||||
if (!$this->syncApiloPayment($order)) {
|
||||
$sync_failed = true;
|
||||
$error = 'payment_sync_failed';
|
||||
@@ -519,7 +581,7 @@ class OrderAdminService
|
||||
}
|
||||
|
||||
$status_pending = isset($task['status']) && $task['status'] !== null && $task['status'] !== '';
|
||||
if (!$sync_failed && $status_pending && (int)$order['apilo_order_id']) {
|
||||
if (!$sync_failed && $status_pending) {
|
||||
if (!$this->syncApiloStatus($order, (int)$task['status'])) {
|
||||
$sync_failed = true;
|
||||
$error = 'status_sync_failed';
|
||||
@@ -600,6 +662,17 @@ class OrderAdminService
|
||||
$apilo_settings = $integrationsRepository->getSettings('apilo');
|
||||
|
||||
if (!$apilo_settings['enabled'] || !$apilo_settings['access-token'] || !$apilo_settings['sync_orders']) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'payment_sync',
|
||||
(int)$order['id'],
|
||||
'Pominięto sync płatności — Apilo wyłączone lub brak tokenu/sync_orders',
|
||||
[
|
||||
'enabled' => $apilo_settings['enabled'] ?? false,
|
||||
'has_token' => !empty($apilo_settings['access-token']),
|
||||
'sync_orders' => $apilo_settings['sync_orders'] ?? false,
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -607,7 +680,24 @@ class OrderAdminService
|
||||
self::appendApiloLog("SET AS PAID\n" . print_r($order, true));
|
||||
}
|
||||
|
||||
if ($order['apilo_order_id'] && !$this->syncApiloPayment($order)) {
|
||||
if (!$order['apilo_order_id']) {
|
||||
// Zamówienie jeszcze nie wysłane do Apilo — kolejkuj sync płatności na później
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'payment_sync',
|
||||
(int)$order['id'],
|
||||
'Brak apilo_order_id — płatność zakolejkowana do sync',
|
||||
['apilo_order_id' => $order['apilo_order_id'] ?? null]
|
||||
);
|
||||
self::queueApiloSync((int)$order['id'], true, null, 'awaiting_apilo_order');
|
||||
} elseif (!$this->syncApiloPayment($order)) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'payment_sync',
|
||||
(int)$order['id'],
|
||||
'Sync płatności nieudany — zakolejkowano ponowną próbę',
|
||||
['apilo_order_id' => $order['apilo_order_id']]
|
||||
);
|
||||
self::queueApiloSync((int)$order['id'], true, null, 'payment_sync_failed');
|
||||
}
|
||||
}
|
||||
@@ -621,6 +711,18 @@ class OrderAdminService
|
||||
$apilo_settings = $integrationsRepository->getSettings('apilo');
|
||||
|
||||
if (!$apilo_settings['enabled'] || !$apilo_settings['access-token'] || !$apilo_settings['sync_orders']) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'status_sync',
|
||||
(int)$order['id'],
|
||||
'Pominięto sync statusu — Apilo wyłączone lub brak tokenu/sync_orders',
|
||||
[
|
||||
'target_status' => $status,
|
||||
'enabled' => $apilo_settings['enabled'] ?? false,
|
||||
'has_token' => !empty($apilo_settings['access-token']),
|
||||
'sync_orders' => $apilo_settings['sync_orders'] ?? false,
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -628,7 +730,24 @@ class OrderAdminService
|
||||
self::appendApiloLog("UPDATE STATUS\n" . print_r($order, true));
|
||||
}
|
||||
|
||||
if ($order['apilo_order_id'] && !$this->syncApiloStatus($order, $status)) {
|
||||
if (!$order['apilo_order_id']) {
|
||||
// Zamówienie jeszcze nie wysłane do Apilo — kolejkuj sync statusu na później
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'status_sync',
|
||||
(int)$order['id'],
|
||||
'Brak apilo_order_id — status zakolejkowany do sync',
|
||||
['apilo_order_id' => $order['apilo_order_id'] ?? null, 'target_status' => $status]
|
||||
);
|
||||
self::queueApiloSync((int)$order['id'], false, $status, 'awaiting_apilo_order');
|
||||
} elseif (!$this->syncApiloStatus($order, $status)) {
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'status_sync',
|
||||
(int)$order['id'],
|
||||
'Sync statusu nieudany — zakolejkowano ponowną próbę',
|
||||
['apilo_order_id' => $order['apilo_order_id'], 'target_status' => $status]
|
||||
);
|
||||
self::queueApiloSync((int)$order['id'], false, $status, 'status_sync_failed');
|
||||
}
|
||||
}
|
||||
@@ -640,7 +759,7 @@ class OrderAdminService
|
||||
$db = $this->orders->getDb();
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db);
|
||||
|
||||
if (!(int)$order['apilo_order_id']) {
|
||||
if (empty($order['apilo_order_id'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -677,6 +796,23 @@ class OrderAdminService
|
||||
self::appendApiloLog("PAYMENT RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r($apilo_response, true));
|
||||
}
|
||||
|
||||
$success = ($curl_error === '' && $http_code >= 200 && $http_code < 300);
|
||||
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'payment_sync',
|
||||
(int)$order['id'],
|
||||
$success
|
||||
? 'Płatność zsynchronizowana z Apilo (apilo_order_id: ' . $order['apilo_order_id'] . ')'
|
||||
: 'Błąd synchronizacji płatności (HTTP: ' . $http_code . ($curl_error ? ', cURL: ' . $curl_error : '') . ')',
|
||||
[
|
||||
'apilo_order_id' => $order['apilo_order_id'],
|
||||
'http_code' => $http_code,
|
||||
'curl_error' => $curl_error,
|
||||
'response' => json_decode((string)$apilo_response, true),
|
||||
]
|
||||
);
|
||||
|
||||
if ($curl_error !== '') return false;
|
||||
if ($http_code < 200 || $http_code >= 300) return false;
|
||||
|
||||
@@ -690,7 +826,7 @@ class OrderAdminService
|
||||
$db = $this->orders->getDb();
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db);
|
||||
|
||||
if (!(int)$order['apilo_order_id']) {
|
||||
if (empty($order['apilo_order_id'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -721,6 +857,24 @@ class OrderAdminService
|
||||
self::appendApiloLog("STATUS RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r($apilo_result, true));
|
||||
}
|
||||
|
||||
$success = ($curl_error === '' && $http_code >= 200 && $http_code < 300);
|
||||
|
||||
\Domain\Integrations\ApiloLogger::log(
|
||||
$db,
|
||||
'status_sync',
|
||||
(int)$order['id'],
|
||||
$success
|
||||
? 'Status zsynchronizowany z Apilo (apilo_order_id: ' . $order['apilo_order_id'] . ', status: ' . $status . ')'
|
||||
: 'Błąd synchronizacji statusu (HTTP: ' . $http_code . ($curl_error ? ', cURL: ' . $curl_error : '') . ')',
|
||||
[
|
||||
'apilo_order_id' => $order['apilo_order_id'],
|
||||
'status' => $status,
|
||||
'http_code' => $http_code,
|
||||
'curl_error' => $curl_error,
|
||||
'response' => json_decode((string)$apilo_result, true),
|
||||
]
|
||||
);
|
||||
|
||||
if ($curl_error !== '') return false;
|
||||
if ($http_code < 200 || $http_code >= 300) return false;
|
||||
|
||||
|
||||
@@ -245,25 +245,43 @@ class OrderRepository
|
||||
|
||||
public function orderStatuses(): array
|
||||
{
|
||||
$rows = $this->db->select('pp_shop_statuses', ['id', 'status'], [
|
||||
$data = $this->orderStatusData();
|
||||
return $data['names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca nazwy i kolory statusów w jednym zapytaniu.
|
||||
*
|
||||
* @return array{names: array<int, string>, colors: array<int, string>}
|
||||
*/
|
||||
public function orderStatusData(): array
|
||||
{
|
||||
$rows = $this->db->select('pp_shop_statuses', ['id', 'status', 'color'], [
|
||||
'ORDER' => ['o' => 'ASC'],
|
||||
]);
|
||||
|
||||
$names = [];
|
||||
$colors = [];
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return [];
|
||||
return ['names' => $names, 'colors' => $colors];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$id = (int)($row['id'] ?? 0);
|
||||
if ($id < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$id] = (string)($row['status'] ?? '');
|
||||
$names[$id] = (string)($row['status'] ?? '');
|
||||
|
||||
$color = trim((string)($row['color'] ?? ''));
|
||||
if ($color !== '' && preg_match('/^#[0-9a-fA-F]{3,6}$/', $color)) {
|
||||
$colors[$id] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
return ['names' => $names, 'colors' => $colors];
|
||||
}
|
||||
|
||||
public function nextOrderId(int $orderId): ?int
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\Integrations\IntegrationsRepository;
|
||||
use admin\ViewModels\Common\PaginatedTableViewModel;
|
||||
|
||||
class IntegrationsController
|
||||
{
|
||||
@@ -12,6 +13,114 @@ class IntegrationsController
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function logs(): string
|
||||
{
|
||||
$sortableColumns = ['id', 'action', 'order_id', 'message', 'date'];
|
||||
|
||||
$filterDefinitions = [
|
||||
[
|
||||
'key' => 'log_action',
|
||||
'label' => 'Akcja',
|
||||
'type' => 'text',
|
||||
],
|
||||
[
|
||||
'key' => 'message',
|
||||
'label' => 'Wiadomosc',
|
||||
'type' => 'text',
|
||||
],
|
||||
[
|
||||
'key' => 'order_id',
|
||||
'label' => 'ID zamowienia',
|
||||
'type' => 'text',
|
||||
],
|
||||
];
|
||||
|
||||
$listRequest = \admin\Support\TableListRequestFactory::fromRequest(
|
||||
$filterDefinitions,
|
||||
$sortableColumns,
|
||||
'id'
|
||||
);
|
||||
|
||||
$result = $this->repository->getLogs(
|
||||
$listRequest['filters'],
|
||||
$listRequest['sortColumn'],
|
||||
$listRequest['sortDir'],
|
||||
$listRequest['page'],
|
||||
$listRequest['perPage']
|
||||
);
|
||||
|
||||
$rows = [];
|
||||
$lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1;
|
||||
|
||||
foreach ( $result['items'] as $item ) {
|
||||
$id = (int)($item['id'] ?? 0);
|
||||
$context = trim( (string)($item['context'] ?? '') );
|
||||
$contextHtml = '';
|
||||
if ( $context !== '' ) {
|
||||
$contextHtml = '<button class="btn btn-xs btn-default log-context-btn" data-id="' . $id . '">Pokaz</button>'
|
||||
. '<pre class="log-context-pre" id="log-context-' . $id . '" style="display:none;max-height:300px;overflow:auto;margin-top:5px;font-size:11px;white-space:pre-wrap;">'
|
||||
. htmlspecialchars( $context, ENT_QUOTES, 'UTF-8' )
|
||||
. '</pre>';
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'lp' => $lp++ . '.',
|
||||
'action' => htmlspecialchars( (string)($item['action'] ?? ''), ENT_QUOTES, 'UTF-8' ),
|
||||
'order_id' => $item['order_id'] ? (int)$item['order_id'] : '-',
|
||||
'message' => htmlspecialchars( (string)($item['message'] ?? ''), ENT_QUOTES, 'UTF-8' ),
|
||||
'context' => $contextHtml,
|
||||
'date' => !empty( $item['date'] ) ? date( 'Y-m-d H:i:s', strtotime( (string)$item['date'] ) ) : '-',
|
||||
];
|
||||
}
|
||||
|
||||
$total = (int)$result['total'];
|
||||
$totalPages = max( 1, (int)ceil( $total / $listRequest['perPage'] ) );
|
||||
|
||||
$viewModel = new PaginatedTableViewModel(
|
||||
[
|
||||
['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false],
|
||||
['key' => 'date', 'sort_key' => 'date', 'label' => 'Data', 'class' => 'text-center', 'sortable' => true],
|
||||
['key' => 'action', 'sort_key' => 'action', 'label' => 'Akcja', 'sortable' => true],
|
||||
['key' => 'order_id', 'sort_key' => 'order_id', 'label' => 'Zamowienie', 'class' => 'text-center', 'sortable' => true],
|
||||
['key' => 'message', 'sort_key' => 'message', 'label' => 'Wiadomosc', 'sortable' => true],
|
||||
['key' => 'context', 'label' => 'Kontekst', 'sortable' => false, 'raw' => true],
|
||||
],
|
||||
$rows,
|
||||
$listRequest['viewFilters'],
|
||||
[
|
||||
'column' => $listRequest['sortColumn'],
|
||||
'dir' => $listRequest['sortDir'],
|
||||
],
|
||||
[
|
||||
'page' => $listRequest['page'],
|
||||
'per_page' => $listRequest['perPage'],
|
||||
'total' => $total,
|
||||
'total_pages' => $totalPages,
|
||||
],
|
||||
array_merge( $listRequest['queryFilters'], [
|
||||
'sort' => $listRequest['sortColumn'],
|
||||
'dir' => $listRequest['sortDir'],
|
||||
'per_page' => $listRequest['perPage'],
|
||||
] ),
|
||||
$listRequest['perPageOptions'],
|
||||
$sortableColumns,
|
||||
'/admin/integrations/logs/',
|
||||
'Brak wpisow w logach.'
|
||||
);
|
||||
|
||||
return \Shared\Tpl\Tpl::view( 'integrations/logs', [
|
||||
'viewModel' => $viewModel,
|
||||
] );
|
||||
}
|
||||
|
||||
public function logs_clear(): void
|
||||
{
|
||||
$this->repository->clearLogs();
|
||||
\Shared\Helpers\Helpers::alert( 'Logi zostaly wyczyszczone.' );
|
||||
header( 'Location: /admin/integrations/logs/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
public function apilo_settings(): string
|
||||
{
|
||||
return \Shared\Tpl\Tpl::view( 'integrations/apilo-settings', [
|
||||
|
||||
@@ -73,26 +73,45 @@ class SettingsController
|
||||
*/
|
||||
public function globalSearchAjax(): void
|
||||
{
|
||||
global $mdb;
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
|
||||
$phrase = trim((string)\Shared\Helpers\Helpers::get('q'));
|
||||
if ($phrase === '' || mb_strlen($phrase) < 2) {
|
||||
try {
|
||||
$this->executeGlobalSearch();
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'status' => 'error',
|
||||
'items' => [],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
private function executeGlobalSearch(): void
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$phrase = isset($_REQUEST['q']) ? trim((string)$_REQUEST['q']) : '';
|
||||
if ($phrase === '' || mb_strlen($phrase) < 2) {
|
||||
echo json_encode(['status' => 'ok', 'items' => []]);
|
||||
return;
|
||||
}
|
||||
|
||||
$phrase = mb_substr($phrase, 0, 120);
|
||||
$phraseNormalized = preg_replace('/\s+/', ' ', $phrase);
|
||||
$phraseNormalized = trim((string)$phraseNormalized);
|
||||
$phraseNormalized = trim((string)preg_replace('/\s+/', ' ', $phrase));
|
||||
$like = '%' . $phrase . '%';
|
||||
$likeNormalized = '%' . $phraseNormalized . '%';
|
||||
|
||||
$items = [];
|
||||
$defaultLang = (string)$this->languagesRepository->defaultLanguage();
|
||||
|
||||
$defaultLang = '1';
|
||||
try {
|
||||
$defaultLang = (string)$this->languagesRepository->defaultLanguage();
|
||||
} catch (\Throwable $e) {
|
||||
// fallback to '1'
|
||||
}
|
||||
|
||||
// --- Produkty ---
|
||||
try {
|
||||
$productStmt = $mdb->query(
|
||||
'SELECT '
|
||||
@@ -115,7 +134,10 @@ class SettingsController
|
||||
$productStmt = false;
|
||||
}
|
||||
|
||||
$productRows = $productStmt ? $productStmt->fetchAll() : [];
|
||||
$productRows = ($productStmt && method_exists($productStmt, 'fetchAll'))
|
||||
? $productStmt->fetchAll(\PDO::FETCH_ASSOC)
|
||||
: [];
|
||||
|
||||
if (is_array($productRows)) {
|
||||
foreach ($productRows as $row) {
|
||||
$productId = (int)($row['id'] ?? 0);
|
||||
@@ -147,6 +169,7 @@ class SettingsController
|
||||
}
|
||||
}
|
||||
|
||||
// --- Zamowienia ---
|
||||
try {
|
||||
$orderStmt = $mdb->query(
|
||||
'SELECT '
|
||||
@@ -178,7 +201,10 @@ class SettingsController
|
||||
$orderStmt = false;
|
||||
}
|
||||
|
||||
$orderRows = $orderStmt ? $orderStmt->fetchAll() : [];
|
||||
$orderRows = ($orderStmt && method_exists($orderStmt, 'fetchAll'))
|
||||
? $orderStmt->fetchAll(\PDO::FETCH_ASSOC)
|
||||
: [];
|
||||
|
||||
if (is_array($orderRows)) {
|
||||
foreach ($orderRows as $row) {
|
||||
$orderId = (int)($row['id'] ?? 0);
|
||||
@@ -214,11 +240,12 @@ class SettingsController
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'items' => array_slice($items, 0, 20),
|
||||
]);
|
||||
exit;
|
||||
$json = json_encode(['status' => 'ok', 'items' => array_slice($items, 0, 20)]);
|
||||
if ($json === false) {
|
||||
echo json_encode(['status' => 'ok', 'items' => []], JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
echo $json;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,9 @@ class ShopOrderController
|
||||
$listRequest['perPage']
|
||||
);
|
||||
|
||||
$statusesMap = $this->service->statuses();
|
||||
$statusData = $this->service->statusData();
|
||||
$statusesMap = $statusData['names'];
|
||||
$statusColorsMap = $statusData['colors'];
|
||||
$rows = [];
|
||||
$lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1;
|
||||
|
||||
@@ -77,7 +79,15 @@ class ShopOrderController
|
||||
$orderId = (int)($item['id'] ?? 0);
|
||||
$orderNumber = (string)($item['number'] ?? '');
|
||||
$statusId = (int)($item['status'] ?? 0);
|
||||
$statusLabel = (string)($statusesMap[$statusId] ?? ('Status #' . $statusId));
|
||||
$statusLabel = htmlspecialchars((string)($statusesMap[$statusId] ?? ('Status #' . $statusId)), ENT_QUOTES, 'UTF-8');
|
||||
$statusColor = isset($statusColorsMap[$statusId]) ? $statusColorsMap[$statusId] : '';
|
||||
|
||||
if ($statusColor !== '') {
|
||||
$textColor = $this->contrastTextColor($statusColor);
|
||||
$statusHtml = '<span class="label" style="background-color:' . htmlspecialchars($statusColor, ENT_QUOTES, 'UTF-8') . ';color:' . $textColor . '">' . $statusLabel . '</span>';
|
||||
} else {
|
||||
$statusHtml = $statusLabel;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'lp' => $lp++ . '.',
|
||||
@@ -86,13 +96,13 @@ class ShopOrderController
|
||||
'paid' => ((int)($item['paid'] ?? 0) === 1)
|
||||
? '<i class="fa fa-check text-success"></i>'
|
||||
: '<i class="fa fa-times text-dark"></i>',
|
||||
'status' => htmlspecialchars($statusLabel, ENT_QUOTES, 'UTF-8'),
|
||||
'status' => $statusHtml,
|
||||
'summary' => number_format((float)($item['summary'] ?? 0), 2, '.', ' ') . ' zł',
|
||||
'client' => htmlspecialchars((string)($item['client'] ?? ''), ENT_QUOTES, 'UTF-8') . ' | zamówienia: <strong>' . (int)($item['total_orders'] ?? 0) . '</strong>',
|
||||
'address' => (string)($item['address'] ?? ''),
|
||||
'order_email' => (string)($item['order_email'] ?? ''),
|
||||
'client_phone' => (string)($item['client_phone'] ?? ''),
|
||||
'transport' => (string)($item['transport'] ?? ''),
|
||||
'transport' => $this->sanitizeInlineHtml((string)($item['transport'] ?? '')),
|
||||
'payment_method' => (string)($item['payment_method'] ?? ''),
|
||||
'_actions' => [
|
||||
[
|
||||
@@ -127,7 +137,7 @@ class ShopOrderController
|
||||
['key' => 'address', 'label' => 'Adres', 'sortable' => false],
|
||||
['key' => 'order_email', 'sort_key' => 'order_email', 'label' => 'Email', 'sortable' => true],
|
||||
['key' => 'client_phone', 'sort_key' => 'client_phone', 'label' => 'Telefon', 'sortable' => true],
|
||||
['key' => 'transport', 'sort_key' => 'transport', 'label' => 'Dostawa', 'sortable' => true],
|
||||
['key' => 'transport', 'sort_key' => 'transport', 'label' => 'Dostawa', 'sortable' => true, 'raw' => true],
|
||||
['key' => 'payment_method', 'sort_key' => 'payment_method', 'label' => 'Płatność', 'sortable' => true],
|
||||
],
|
||||
$rows,
|
||||
@@ -361,4 +371,26 @@ class ShopOrderController
|
||||
|
||||
return date('Y-m-d H:i', $ts);
|
||||
}
|
||||
}
|
||||
|
||||
private function contrastTextColor(string $hex): string
|
||||
{
|
||||
$hex = ltrim($hex, '#');
|
||||
if (strlen($hex) === 3) {
|
||||
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
|
||||
}
|
||||
if (strlen($hex) !== 6) {
|
||||
return '#fff';
|
||||
}
|
||||
$r = hexdec(substr($hex, 0, 2));
|
||||
$g = hexdec(substr($hex, 2, 2));
|
||||
$b = hexdec(substr($hex, 4, 2));
|
||||
$luminance = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
|
||||
return $luminance > 0.5 ? '#000' : '#fff';
|
||||
}
|
||||
|
||||
private function sanitizeInlineHtml(string $html): string
|
||||
{
|
||||
$html = strip_tags($html, '<b><strong><i><em>');
|
||||
return preg_replace('/<(b|strong|i|em)\s[^>]*>/i', '<$1>', $html);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class ShopProductController
|
||||
. '<a href="/admin/shop_product/product_edit/id=' . $id . '">' . $name . '</a> '
|
||||
. '<a href="#" class="text-muted duplicate-product" product-id="' . $id . '">duplikuj</a>'
|
||||
. '</div>'
|
||||
. '<small class="text-muted product-categories">' . $categories . '</small>'
|
||||
. '<small class="text-muted product-categories product-categories--cats" title="' . $categories . '">' . $categories . '</small>'
|
||||
. '<small class="text-muted product-categories">SKU: ' . $sku . ', EAN: ' . $ean . '</small>';
|
||||
|
||||
$priceHtml = '<input type="text" class="product-price form-control text-right" product-id="' . $id . '" value="' . htmlspecialchars( (string) $product['price_brutto'], ENT_QUOTES, 'UTF-8' ) . '" style="width: 75px;">';
|
||||
@@ -688,7 +688,7 @@ class ShopProductController
|
||||
foreach ( $products as $key => $val ) {
|
||||
if ( (int) $key !== $productId ) {
|
||||
$selected = ( is_array( $product['products_related'] ?? null ) && in_array( $key, $product['products_related'] ) ) ? ' selected' : '';
|
||||
$html .= '<option value="' . (int) $key . '"' . $selected . '>' . $this->escapeHtml( $val ) . '</option>';
|
||||
$html .= '<option value="' . (int) $key . '"' . $selected . '>' . $this->escapeHtml( (string) $val ) . '</option>';
|
||||
}
|
||||
}
|
||||
$html .= '</select></div></div>';
|
||||
|
||||
@@ -49,4 +49,17 @@ class UpdateController
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
public function checkUpdate(): void
|
||||
{
|
||||
\Shared\Helpers\Helpers::set_session( 'new-version', null );
|
||||
$newVer = \Shared\Helpers\Helpers::get_new_version();
|
||||
$curVer = \Shared\Helpers\Helpers::get_version();
|
||||
|
||||
echo json_encode( [
|
||||
'has_update' => $newVer > $curVer,
|
||||
'new_ver' => $newVer,
|
||||
] );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
$database['host'] = 'localhost';
|
||||
$database['remote_host'] = 'host117523.hostido.net.pl';
|
||||
$database['user'] = 'host117523_shoppro';
|
||||
$database['password'] = 'mhA9WCEXEnRfTtbN33hL';
|
||||
$database['name'] = 'host117523_shoppro';
|
||||
@@ -12,7 +13,7 @@ $config['redis']['host'] = '127.0.0.1';
|
||||
$config['redis']['port'] = 20470;
|
||||
$config['redis']['password'] = 'Gi7FzWtkry19hZ1BqT1LKEWfwokQpigh';
|
||||
|
||||
$config['debug']['apilo'] = false;
|
||||
$config['debug']['apilo'] = true;
|
||||
|
||||
$config['trustmate']['enabled'] = true;
|
||||
$config['trustmate']['uid'] = '34eb36ba-c715-4cdc-8707-22376c9f14c7';
|
||||
|
||||
56
cron.php
56
cron.php
@@ -201,6 +201,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
{
|
||||
$products = $mdb -> select( 'pp_shop_order_products', '*', [ 'order_id' => $order['id'] ] );
|
||||
$products_array = [];
|
||||
$order_message = '';
|
||||
foreach ( $products as $product )
|
||||
{
|
||||
$productRepo = new \Domain\Product\ProductRepository( $mdb );
|
||||
@@ -211,8 +212,8 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
'ean' => $productRepo->getEanWithFallback( (int)$product['product_id'], true ),
|
||||
'sku' => $sku ? $sku : md5( $product['product_id'] ),
|
||||
'originalName' => $product['name'],
|
||||
'originalPriceWithTax' => $product['price_brutto_promo'] ? str_replace( ',', '.', $product['price_brutto_promo'] ) : str_replace( ',', '.', $product['price_brutto'] ),
|
||||
'originalPriceWithoutTax' => $product['price_brutto_promo'] ? str_replace( ',', '.', round( $product['price_brutto_promo'] / ( 1 + $product['vat']/100 ), 2 ) ) : str_replace( ',', '.', round( $product['price_brutto'] / ( 1 + $product['vat']/100 ), 2 ) ),
|
||||
'originalPriceWithTax' => (float)$product['price_brutto_promo'] > 0 ? str_replace( ',', '.', $product['price_brutto_promo'] ) : str_replace( ',', '.', $product['price_brutto'] ),
|
||||
'originalPriceWithoutTax' => (float)$product['price_brutto_promo'] > 0 ? str_replace( ',', '.', round( $product['price_brutto_promo'] / ( 1 + $product['vat']/100 ), 2 ) ) : str_replace( ',', '.', round( $product['price_brutto'] / ( 1 + $product['vat']/100 ), 2 ) ),
|
||||
'quantity' => $product['quantity'],
|
||||
'tax' => number_format( $product['vat'], 2, '.', '' ),
|
||||
'status' => 1,
|
||||
@@ -255,6 +256,25 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
'media' => null
|
||||
];
|
||||
|
||||
// Walidacja: sprawdź czy zamówienie ma produkty z cenami > 0
|
||||
$has_priced_products = false;
|
||||
foreach ( $products_array as $pa )
|
||||
{
|
||||
if ( $pa['type'] == 1 && (float)$pa['originalPriceWithTax'] > 0 )
|
||||
{
|
||||
$has_priced_products = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !$has_priced_products )
|
||||
{
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Pominięto zamówienie - wszystkie produkty mają cenę 0.00', [ 'products' => $products_array ] );
|
||||
\Shared\Helpers\Helpers::send_email( 'biuro@project-pro.pl', 'Apilo: zamówienie #' . $order['id'] . ' ma zerowe ceny produktów', 'Zamówienie #' . $order['id'] . ' nie zostało wysłane do Apilo, ponieważ wszystkie produkty mają cenę 0.00 PLN. Sprawdź zamówienie w panelu sklepu.' );
|
||||
$mdb -> update( 'pp_shop_orders', [ 'apilo_order_id' => -2 ], [ 'id' => $order['id'] ] );
|
||||
echo '<p>Pominięto zamówienie #' . $order['id'] . ' - zerowe ceny produktów</p>';
|
||||
continue;
|
||||
}
|
||||
|
||||
$access_token = $integrationsRepository -> apiloGetAccessToken();
|
||||
|
||||
$order_date = new DateTime( $order['date_order'] );
|
||||
@@ -406,8 +426,11 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
$response = curl_exec( $ch );
|
||||
if (curl_errno( $ch ) ) {
|
||||
echo 'Błąd cURL: ' . curl_error( $ch );
|
||||
$curl_error_send = curl_error( $ch );
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Błąd cURL przy wysyłaniu zamówienia: ' . $curl_error_send, [ 'curl_error' => $curl_error_send ] );
|
||||
echo 'Błąd cURL: ' . $curl_error_send;
|
||||
}
|
||||
$http_code_send = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
curl_close( $ch );
|
||||
|
||||
$response = json_decode( $response, true );
|
||||
@@ -423,6 +446,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
{
|
||||
$apilo_order_id = str_replace( 'Order id: ', '', $response['description'] );
|
||||
$mdb -> update( 'pp_shop_orders', [ 'apilo_order_id' => $apilo_order_id ], [ 'id' => $order['id'] ] );
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Zamówienie już istnieje w Apilo (apilo_order_id: ' . $apilo_order_id . ')', [ 'http_code' => $http_code_send, 'response' => $response ] );
|
||||
echo '<p>Zaktualizowałem id zamówienia na podstawie zamówienia apilo.com</p>';
|
||||
}
|
||||
elseif ( $response['message'] == 'Validation error' )
|
||||
@@ -462,6 +486,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
{
|
||||
$apilo_order_id = $get_response_data['list'][0]['id'];
|
||||
$mdb -> update( 'pp_shop_orders', [ 'apilo_order_id' => $apilo_order_id ], [ 'id' => $order['id'] ] );
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Duplikat idExternal - pobrano apilo_order_id: ' . $apilo_order_id, [ 'http_code' => $http_code_send, 'response' => $response, 'get_response' => $get_response_data ] );
|
||||
echo '<p>Zamówienie już istnieje w Apilo. Zaktualizowano ID zamówienia: ' . $apilo_order_id . '</p>';
|
||||
}
|
||||
else
|
||||
@@ -471,6 +496,8 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
echo print_r( $postData, true );
|
||||
echo '</pre>';
|
||||
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Błąd: duplikat idExternal, ale nie znaleziono zamówienia w Apilo', [ 'http_code' => $http_code_send, 'response' => $response, 'get_response' => $get_response_data ] );
|
||||
|
||||
$email_data = print_r( $response, true );
|
||||
$email_data .= print_r( $postData, true );
|
||||
\Shared\Helpers\Helpers::send_email( 'biuro@project-pro.pl', 'Błąd wysyłania zamówienia do apilo.com - nie znaleziono zamówienia', $email_data );
|
||||
@@ -483,17 +510,37 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
echo print_r( $postData, true );
|
||||
echo '</pre>';
|
||||
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Błąd walidacji wysyłania zamówienia do Apilo', [ 'http_code' => $http_code_send, 'response' => $response ] );
|
||||
|
||||
$email_data = print_r( $response, true );
|
||||
$email_data .= print_r( $postData, true );
|
||||
\Shared\Helpers\Helpers::send_email( 'biuro@project-pro.pl', 'Błąd wysyłania zamówienia do apilo.com', $email_data );
|
||||
}
|
||||
}
|
||||
elseif ( $http_code_send >= 400 || !isset( $response['id'] ) )
|
||||
{
|
||||
// Błąd serwera lub brak ID w odpowiedzi — logujemy i pomijamy, NIE ustawiamy apilo_order_id
|
||||
// żeby zamówienie nie wpadło w nieskończoną pętlę, ustawiamy apilo_order_id na -1 (błąd)
|
||||
$mdb -> update( 'pp_shop_orders', [ 'apilo_order_id' => -1 ], [ 'id' => $order['id'] ] );
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Błąd wysyłania zamówienia do Apilo (HTTP ' . $http_code_send . ')', [ 'http_code' => $http_code_send, 'response' => $response ] );
|
||||
|
||||
$email_data = 'HTTP Code: ' . $http_code_send . "\n\n";
|
||||
$email_data .= print_r( $response, true );
|
||||
$email_data .= print_r( $postData, true );
|
||||
\Shared\Helpers\Helpers::send_email( 'biuro@project-pro.pl', 'Błąd wysyłania zamówienia #' . $order['id'] . ' do apilo.com (HTTP ' . $http_code_send . ')', $email_data );
|
||||
|
||||
echo '<p>Błąd wysyłania zamówienia do apilo.com: ID: ' . $order['id'] . ' (HTTP ' . $http_code_send . ')</p>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_shop_orders', [ 'apilo_order_id' => $response['id'] ], [ 'id' => $order['id'] ] );
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Zamówienie wysłane do Apilo (apilo_order_id: ' . $response['id'] . ')', [ 'http_code' => $http_code_send, 'response' => $response ] );
|
||||
echo '<p>Wysłałem zamówienie do apilo.com: ID: ' . $order['id'] . ' - ' . $response['id'] . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Po wysłaniu zamówień: przetwórz kolejkę sync (płatności/statusy oczekujące na apilo_order_id)
|
||||
$orderAdminService->processApiloSyncQueue( 10 );
|
||||
}
|
||||
|
||||
// sprawdzanie statusów zamówień w apilo.com jeżeli zamówienie nie jest zrealizowane, anulowane lub nieodebrane
|
||||
@@ -515,6 +562,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
] );
|
||||
|
||||
$response = curl_exec( $ch );
|
||||
$http_code_poll = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
$responseData = json_decode( $response, true );
|
||||
|
||||
if ( $responseData['id'] and $responseData['status'] )
|
||||
@@ -524,6 +572,8 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
if ( $shop_status_id )
|
||||
$orderAdminService->changeStatus( (int)$order['id'], $shop_status_id, false );
|
||||
|
||||
\Domain\Integrations\ApiloLogger::log( $mdb, 'status_poll', (int)$order['id'], 'Status pobrany z Apilo (apilo_status: ' . $responseData['status'] . ', shop_status: ' . ($shop_status_id ?: 'brak mapowania') . ')', [ 'apilo_order_id' => $order['apilo_order_id'], 'http_code' => $http_code_poll, 'response' => $responseData ] );
|
||||
|
||||
$orderRepo->updateApiloStatusDate( (int)$order['id'], date( 'Y-m-d H:i:s' ) );
|
||||
echo '<p>Zaktualizowałem status zamówienia <b>' . $order['number'] . '</b></p>';
|
||||
}
|
||||
|
||||
@@ -4,6 +4,89 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.315 (2026-02-23) - Fix listowania atrybutów w admin
|
||||
|
||||
- **FIX**: `AttributeRepository::listForAdmin()` — zapytanie COUNT dostawało parametr `:default_lang_id` którego nie miało w SQL, powodując `PDOException: SQLSTATE[HY093]: Invalid parameter number`. Parametr potrzebny tylko w głównym SELECT, nie w COUNT
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.314 (2026-02-23) - Fix wyszukiwarki admin + title zamówienia
|
||||
|
||||
- **FIX**: Globalna wyszukiwarka w panelu admina przestała zwracać wyniki — dodano `Content-Type: application/json` i `Cache-Control: no-store` (zapobiega cache'owaniu przez proxy/CDN), zmiana AJAX z GET na POST, `fetchAll(PDO::FETCH_ASSOC)`, top-level try/catch z gwarantowaną odpowiedzią JSON
|
||||
- **NEW**: `document.title` w widoku szczegółów zamówienia pokazuje numer zamówienia (np. "Zamówienie ZAM/123 - shopPro")
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.313 (2026-02-23) - Fix sync płatności Apilo + logowanie
|
||||
|
||||
- **FIX**: `syncApiloPayment()` i `syncApiloStatus()` — `(int)` cast na `apilo_order_id` (format `"PPxxxxxx"`) dawał `0`, przez co metody pomijały sync z API Apilo. Zmiana na `empty()`
|
||||
- **NEW**: Logowanie w `syncApiloPaymentIfNeeded()` i `syncApiloStatusIfNeeded()` — każda ścieżka decyzyjna (Apilo wyłączone, brak tokenu, brak `apilo_order_id`, sync nieudany) zapisuje wpis do `pp_log` z kontekstem
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.312 (2026-02-23) - Fix krytycznych bugów integracji Apilo
|
||||
|
||||
- **FIX**: `curl_getinfo()` wywoływane po `curl_close()` — HTTP code zawsze wynosił 0, uniemożliwiając prawidłową obsługę odpowiedzi Apilo
|
||||
- **FIX**: Nieskończona pętla wysyłania zamówienia — gdy Apilo zwracało błąd serwera, zamówienie nie dostawało `apilo_order_id` i było ponownie wybierane w każdym cyklu crona. Teraz błędne zamówienia oznaczane `apilo_order_id = -1` z powiadomieniem email
|
||||
- **FIX**: Ceny produktów 0.00 PLN w Apilo — string `"0.00"` z MySQL jest truthy w PHP, więc ternary wybierał `price_brutto_promo` (0.00) zamiast `price_brutto`. Zmiana na `(float)... > 0`
|
||||
- **FIX**: Walidacja cen przed wysyłką — zamówienia z zerowymi cenami produktów nie są wysyłane do Apilo (`apilo_order_id = -2`) z powiadomieniem email
|
||||
- **FIX**: Niezainicjalizowana zmienna `$order_message` powodująca PHP warning
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.311 (2026-02-23) - Fix race condition Apilo + persistence filtrów + poprawki cen
|
||||
|
||||
- **FIX**: Race condition — callback płatności przed wysłaniem zamówienia do Apilo nie synchronizował płatności (task trafiał w pustkę). Teraz `syncApiloPaymentIfNeeded` i `syncApiloStatusIfNeeded` kolejkują sync do retry gdy `apilo_order_id` jeszcze nie istnieje
|
||||
- **FIX**: `processApiloSyncQueue` — zamówienia bez `apilo_order_id` były usuwane z kolejki bez synchronizacji. Teraz czekają (max 50 prób ~8h) aż cron wyśle zamówienie do Apilo
|
||||
- **FIX**: Drugie wywołanie `processApiloSyncQueue` w cronie po wysyłce zamówień — sync płatności/statusów w tym samym cyklu
|
||||
- **FIX**: Ceny w szczegółach zamówienia (admin + frontend) — gdy `price_brutto_promo` = 0 lub >= ceny regularnej, wyświetla cenę regularną zamiast 0 zł
|
||||
- **NEW**: Persistence filtrów tabel w panelu admin — localStorage zapamiętuje ostatni widok (filtry, sortowanie, paginacja) i przywraca go przy powrocie do listy. Przycisk "Wyczyść" resetuje zapisany stan
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.310 (2026-02-23) - Logi integracji w panelu admin
|
||||
|
||||
- **NEW**: Zakładka "Logi" w sekcji Integracje — podgląd tabeli `pp_log` z paginacją, sortowaniem, filtrami (akcja, wiadomość, ID zamówienia) i rozwijalnym kontekstem JSON
|
||||
- **NEW**: `IntegrationsRepository::getLogs()`, `deleteLog()`, `clearLogs()` — metody do obsługi logów
|
||||
- **NEW**: `IntegrationsController::logs()`, `logs_clear()` — akcje kontrolera
|
||||
- **NEW**: Przycisk "Wyczyść wszystkie logi" z potwierdzeniem
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.309 (2026-02-23) - ApiloLogger + cache-busting CSS/JS + poprawki UI
|
||||
|
||||
- **NEW**: `ApiloLogger` — logowanie operacji Apilo do tabeli `pp_log` z kontekstem JSON (send_order, resend_order, payment_sync, status_sync, status_poll)
|
||||
- **NEW**: Migracja `pp_log` — kolumny `action`, `order_id`, `context` + indeksy
|
||||
- **NEW**: Cache-busting dla CSS i JS w admin panelu — `?ver=filemtime()` przy wszystkich lokalnych zasobach w `main-layout.php`
|
||||
- **FIX**: Przeniesienie inicjalizacji `$mdb` przed `SettingsRepository` w `admin/index.php`
|
||||
- **FIX**: Rzutowanie na `(string)` w `ShopProductController::escapeHtml()` — zapobiega warningom
|
||||
- **ZMIANA**: Skrocone kategorie produktow na liscie — `text-overflow: ellipsis` z `title` tooltip
|
||||
- **ZMIANA**: `copyToClipboard()` — uzywa `navigator.clipboard` API z fallbackiem na `<textarea>`
|
||||
- **ZMIANA**: Uproszczenie CSS `.site-content` — usuniety zbedny `.with-menu`, menu widoczne zawsze na desktop
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.308 (2026-02-22) - Kolory statusow zamowien + poprawki bezpieczenstwa
|
||||
|
||||
- **NEW**: Kolorowe badge statusow na liscie zamowien w admin panelu — kolory pobierane z `pp_shop_statuses.color`, kontrast tekstu obliczany automatycznie
|
||||
- **FIX**: Walidacja formatu hex koloru z bazy (`/^#[0-9a-fA-F]{3,6}$/`) — odrzucanie nieprawidlowych wartosci
|
||||
- **FIX**: Sanityzacja HTML w kolumnie "Dostawa" — `strip_tags()` + regex usuwajacy atrybuty z dozwolonych tagow (zapobieganie XSS via `onclick` itp.)
|
||||
- **OPTYMALIZACJA**: Polaczenie dwoch zapytan SQL do `pp_shop_statuses` (nazwy + kolory) w jedno `orderStatusData()`
|
||||
- **ZMIANA**: Path-based form submit w `table-list.php` — formularze filtrow i per-page uzywaja JS interceptora z `data-path-submit` zamiast natywnego GET, kompatybilne z admin URL routing
|
||||
- **NEW**: 11 nowych testow jednostkowych (750 total, 2114 assertions)
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.307 (2026-02-22) - Przycisk sprawdzania aktualizacji + auto-changelog
|
||||
|
||||
- **NEW**: Przycisk "Sprawdz aktualizacje" w panelu admina — ikona odswiezenia obok numeru wersji, klik odpytuje serwer AJAX-em i pokazuje/ukrywa badge "aktualizacja" bez przeladowania strony
|
||||
- **NEW**: `UpdateController::checkUpdate()` — nowa akcja AJAX czyszczaca sesje `new-version` i sprawdzajaca wersje z serwera
|
||||
- **NEW**: `updates/changelog.php` — auto-generowany z manifestow JSON (`_manifest.json`) + `changelog-legacy.json`, zamiast recznej edycji HTML
|
||||
- **NEW**: `updates/changelog-legacy.json` — migracja 295 wpisow changelogu (wersje 0.300-0.001) do formatu JSON
|
||||
- **ZMIANA**: Workflow KONIEC PRACY — usuniety krok recznej edycji `changelog.php` (teraz automatyczny)
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.306 (2026-02-22) - Ukrywanie form dostawy bez dostepnych platnosci
|
||||
|
||||
- **FIX**: Formy dostawy, dla ktorych nie ma zadnej dostepnej formy platnosci (np. wszystkie odfiltrowane przez `min_order_amount`/`max_order_amount`), sa teraz ukrywane z listy w koszyku
|
||||
|
||||
156
docs/CRON_QUEUE_PLAN.md
Normal file
156
docs/CRON_QUEUE_PLAN.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Plan: System kolejki zadań cron oparty o bazę danych
|
||||
|
||||
## Kontekst
|
||||
|
||||
Obecny system cron ma dwa problemy:
|
||||
1. **Kolejka plikowa (JSON)** — sync płatności/statusów Apilo trzymany w `/temp/apilo-sync-queue.json` — kruchy, brak transakcji, ryzyko utraty danych
|
||||
2. **Monolityczny cron.php** (~550 linii) — brak priorytetów, brak retry z backoff, brak centralnego zarządzania
|
||||
|
||||
Cel: Zastąpienie całego systemu cron tabelą `pp_cron_jobs` z priorytetami, retry/backoff i harmonogramem `pp_cron_schedules`.
|
||||
|
||||
## Nowe pliki
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| `autoload/Domain/CronJob/CronJobType.php` | Stałe typów zadań i priorytetów |
|
||||
| `autoload/Domain/CronJob/CronJobRepository.php` | CRUD na `pp_cron_jobs` + `pp_cron_schedules` |
|
||||
| `autoload/Domain/CronJob/CronJobProcessor.php` | Orkiestracja: pobierz zadanie → wywołaj handler → obsłuż wynik |
|
||||
| `tests/Unit/Domain/CronJob/CronJobTypeTest.php` | Testy stałych |
|
||||
| `tests/Unit/Domain/CronJob/CronJobRepositoryTest.php` | Testy repozytorium |
|
||||
| `tests/Unit/Domain/CronJob/CronJobProcessorTest.php` | Testy procesora |
|
||||
| `migrations/0.315.sql` | CREATE TABLE + INSERT harmonogramów |
|
||||
|
||||
## Modyfikowane pliki
|
||||
|
||||
| Plik | Zmiana |
|
||||
|------|--------|
|
||||
| `cron.php` | Zastąpienie ~550 linii orchestratorem (~100 linii) z rejestracją handlerów |
|
||||
| `cron/cron-xml.php` | Usunięcie — logika przeniesiona do handlera `google_xml_feed` |
|
||||
| `cron-turstmate.php` | Usunięcie — logika przeniesiona do handlera `trustmate_invitation` |
|
||||
| `autoload/Domain/Order/OrderAdminService.php` | `queueApiloSync()` → enqueue do DB; usunięcie metod plikowych; `syncApiloPayment()`/`syncApiloStatus()` → public |
|
||||
| `tests/Unit/Domain/Order/OrderAdminServiceTest.php` | Refaktor testów kolejki: mock `CronJobRepository` zamiast pliku JSON |
|
||||
| `docs/DATABASE_STRUCTURE.md` | Dodanie tabel `pp_cron_jobs`, `pp_cron_schedules` |
|
||||
| `docs/CHANGELOG.md` | Wpis o nowym systemie |
|
||||
|
||||
## Schemat DB (`migrations/0.315.sql`)
|
||||
|
||||
### `pp_cron_jobs`
|
||||
```sql
|
||||
CREATE TABLE pp_cron_jobs (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
job_type VARCHAR(50) NOT NULL,
|
||||
status ENUM('pending','processing','completed','failed','cancelled') NOT NULL DEFAULT 'pending',
|
||||
priority TINYINT UNSIGNED NOT NULL DEFAULT 100, -- niższy = ważniejszy
|
||||
payload TEXT NULL, -- JSON z danymi zadania
|
||||
result TEXT NULL, -- JSON z wynikiem
|
||||
attempts SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
max_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 10,
|
||||
last_error VARCHAR(500) NULL,
|
||||
scheduled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
started_at DATETIME NULL,
|
||||
completed_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_status_priority_scheduled (status, priority, scheduled_at),
|
||||
INDEX idx_job_type (job_type),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
```
|
||||
|
||||
### `pp_cron_schedules`
|
||||
```sql
|
||||
CREATE TABLE pp_cron_schedules (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
job_type VARCHAR(50) NOT NULL UNIQUE,
|
||||
interval_seconds INT UNSIGNED NOT NULL,
|
||||
priority TINYINT UNSIGNED NOT NULL DEFAULT 100,
|
||||
max_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 3,
|
||||
payload TEXT NULL,
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
last_run_at DATETIME NULL,
|
||||
next_run_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_enabled_next_run (enabled, next_run_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
```
|
||||
|
||||
## Typy zadań i priorytety
|
||||
|
||||
| Typ | Priorytet | Harmonogram |
|
||||
|-----|-----------|-------------|
|
||||
| `apilo_token_keepalive` | 10 (krytyczny) | co 4 min |
|
||||
| `apilo_send_order` | 50 (wysoki) | co 1 min |
|
||||
| `apilo_sync_payment` | 50 (wysoki) | event-driven (enqueue przy zmianie) |
|
||||
| `apilo_sync_status` | 50 (wysoki) | event-driven |
|
||||
| `apilo_product_sync` | 100 (normalny) | co 10 min |
|
||||
| `apilo_pricelist_sync` | 100 (normalny) | co 1h |
|
||||
| `apilo_status_poll` | 100 (normalny) | co 10 min |
|
||||
| `price_history` | 100 (normalny) | co 24h |
|
||||
| `order_analysis` | 100 (normalny) | co 10 min |
|
||||
| `trustmate_invitation` | 200 (niski) | co 10 min |
|
||||
| `google_xml_feed` | 200 (niski) | co 1h |
|
||||
|
||||
## Architektura klas
|
||||
|
||||
### CronJobRepository — metody kluczowe
|
||||
- `enqueue($jobType, $payload, $priority, $maxAttempts, $scheduledAt)` — dodaj do kolejki
|
||||
- `fetchNext($limit)` — atomowe pobranie pending jobs (UPDATE WHERE status='pending')
|
||||
- `markCompleted($jobId, $result)` / `markFailed($jobId, $error, $backoffSeconds)`
|
||||
- `hasPendingJob($jobType, $payloadMatch)` — zapobiega duplikatom
|
||||
- `cleanup($olderThanDays)` — GC starych wpisów
|
||||
- `recoverStuck($olderThanMinutes)` — reset stuck "processing" jobs
|
||||
- `getDueSchedules()` / `touchSchedule($id)` — harmonogram
|
||||
|
||||
### CronJobProcessor — orkiestracja
|
||||
- `registerHandler($jobType, callable)` — rejestracja handlera
|
||||
- `createScheduledJobs()` — tworzy jobs z harmonogramów których `next_run_at <= NOW`
|
||||
- `processQueue($limit)` — pobierz + wywołaj handler + markCompleted/markFailed
|
||||
- `run($limit)` — główna metoda: schedules + process
|
||||
|
||||
### Exponential backoff
|
||||
```
|
||||
Próba 1: 60s, Próba 2: 120s, Próba 3: 240s, ... max 3600s (1h)
|
||||
```
|
||||
|
||||
### Zależność "order not yet in Apilo"
|
||||
Handler `apilo_sync_payment`/`apilo_sync_status` sprawdza `apilo_order_id`. Jeśli brak → zwraca false → `markFailed()` z backoffem → zadanie wraca do kolejki. Max 50 prób.
|
||||
|
||||
## Nowy cron.php (schemat)
|
||||
|
||||
```php
|
||||
$cronRepo = new \Domain\CronJob\CronJobRepository($mdb);
|
||||
$processor = new \Domain\CronJob\CronJobProcessor($mdb, $cronRepo);
|
||||
|
||||
// Rejestracja handlerów (każdy to callable)
|
||||
$processor->registerHandler('apilo_token_keepalive', function($payload) use ($integrationsRepo) { ... });
|
||||
$processor->registerHandler('apilo_send_order', function($payload) use ($orderService, ...) { ... });
|
||||
// ... inne handlery
|
||||
|
||||
$result = $processor->run(20);
|
||||
```
|
||||
|
||||
## Zmiany w OrderAdminService
|
||||
|
||||
1. `queueApiloSync()` → `CronJobRepository::enqueue()` zamiast zapisu do pliku JSON
|
||||
2. Usunięcie: `loadApiloSyncQueue()`, `saveApiloSyncQueue()`, `apiloSyncQueuePath()`, stała `APILO_SYNC_QUEUE_FILE`
|
||||
3. `syncApiloPayment()`, `syncApiloStatus()` → zmiana z `private` na `public`
|
||||
4. Jednorazowa migracja: odczyt JSON → insert do DB → usunięcie pliku
|
||||
|
||||
## Kolejność implementacji
|
||||
|
||||
1. Migracja SQL
|
||||
2. `CronJobType.php`
|
||||
3. `CronJobRepository.php` + testy
|
||||
4. `CronJobProcessor.php` + testy
|
||||
5. Modyfikacja `OrderAdminService` (queue → DB, public methods)
|
||||
6. Jednorazowa migracja pliku JSON → DB
|
||||
7. Nowy `cron.php` z handlerami (ekstrakcja logiki z bloków proceduralnych)
|
||||
8. Aktualizacja testów OrderAdminService
|
||||
9. Dokumentacja (DATABASE_STRUCTURE.md, CHANGELOG.md)
|
||||
|
||||
## Weryfikacja
|
||||
|
||||
1. Uruchomienie pełnego zestawu testów: `./test.ps1`
|
||||
2. Sprawdzenie czy nowe testy CronJob* przechodzą
|
||||
3. Sprawdzenie czy istniejące testy OrderAdminService przechodzą po refaktorze
|
||||
4. Weryfikacja migracji SQL na pustej bazie
|
||||
@@ -23,7 +23,7 @@ composer test # standard
|
||||
## Aktualny stan
|
||||
|
||||
```text
|
||||
OK (739 tests, 2089 assertions)
|
||||
OK (758 tests, 2135 assertions)
|
||||
```
|
||||
|
||||
Zweryfikowano: 2026-02-22 (ver. 0.304)
|
||||
|
||||
0
docs/TODO.md
Normal file
0
docs/TODO.md
Normal file
@@ -1,10 +1,20 @@
|
||||
window.copyToClipboard = function( element )
|
||||
{
|
||||
var $temp = $( "<input>" );
|
||||
$( "#" + element ).after( $temp );
|
||||
$temp.val( $( '#' + element ).text() ).select();
|
||||
document.execCommand( "copy" );
|
||||
$temp.remove();
|
||||
var text = $( '#' + element ).text().trim();
|
||||
|
||||
if ( navigator.clipboard && navigator.clipboard.writeText )
|
||||
{
|
||||
navigator.clipboard.writeText( text );
|
||||
}
|
||||
else
|
||||
{
|
||||
var $temp = $( "<textarea>" );
|
||||
$temp.css({ position: 'fixed', left: '-9999px', top: '-9999px' });
|
||||
$( "body" ).append( $temp );
|
||||
$temp.val( text ).select();
|
||||
document.execCommand( "copy" );
|
||||
$temp.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function checkForm(id)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL;
|
||||
ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL;
|
||||
ALTER TABLE pp_shop_payment_methods ADD COLUMN max_order_amount DECIMAL(10,2) DEFAULT NULL;
|
||||
|
||||
5
migrations/0.309.sql
Normal file
5
migrations/0.309.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE pp_log ADD COLUMN `action` VARCHAR(100) NULL DEFAULT NULL AFTER `id`;
|
||||
ALTER TABLE pp_log ADD COLUMN `order_id` INT NULL DEFAULT NULL AFTER `action`;
|
||||
ALTER TABLE pp_log ADD COLUMN `context` TEXT NULL DEFAULT NULL AFTER `message`;
|
||||
ALTER TABLE pp_log ADD INDEX `idx_action` (`action`);
|
||||
ALTER TABLE pp_log ADD INDEX `idx_order_id` (`order_id`);
|
||||
@@ -179,7 +179,7 @@
|
||||
'id': <?= (int)$product['product_id'];?>,
|
||||
'name': '<?= $product['name'];?>',
|
||||
'quantity': <?= $product['quantity'];?>,
|
||||
'price': <?= $product['price_brutto_promo'];?>
|
||||
'price': <?= ((float)$product['price_brutto_promo'] > 0 && (float)$product['price_brutto_promo'] < (float)$product['price_brutto']) ? (float)$product['price_brutto_promo'] : (float)$product['price_brutto'];?>
|
||||
}<? if ( $product != end( $this -> order['products'] ) ) echo ',';?>
|
||||
<? endforeach;?>
|
||||
]
|
||||
|
||||
@@ -298,4 +298,69 @@ class IntegrationsRepositoryTest extends TestCase
|
||||
$this->assertSame('1', (string)$result[0]['id']);
|
||||
$this->assertSame('Przelew', (string)$result[0]['name']);
|
||||
}
|
||||
|
||||
// ── Logs ────────────────────────────────────────────────────
|
||||
|
||||
public function testGetLogsReturnsItemsAndTotal(): void
|
||||
{
|
||||
$this->mockDb->expects($this->once())
|
||||
->method('count')
|
||||
->with('pp_log', $this->anything())
|
||||
->willReturn(2);
|
||||
|
||||
$this->mockDb->expects($this->once())
|
||||
->method('select')
|
||||
->with('pp_log', '*', $this->anything())
|
||||
->willReturn([
|
||||
['id' => 1, 'action' => 'send_order', 'message' => 'OK', 'date' => '2026-01-01 12:00:00'],
|
||||
['id' => 2, 'action' => 'status_sync', 'message' => 'Synced', 'date' => '2026-01-02 12:00:00'],
|
||||
]);
|
||||
|
||||
$result = $this->repository->getLogs([], 'id', 'DESC', 1, 15);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertArrayHasKey('items', $result);
|
||||
$this->assertArrayHasKey('total', $result);
|
||||
$this->assertCount(2, $result['items']);
|
||||
$this->assertSame(2, $result['total']);
|
||||
}
|
||||
|
||||
public function testGetLogsReturnsEmptyWhenNoResults(): void
|
||||
{
|
||||
$this->mockDb->method('count')->willReturn(0);
|
||||
$this->mockDb->method('select')->willReturn([]);
|
||||
|
||||
$result = $this->repository->getLogs([], 'id', 'DESC', 1, 15);
|
||||
|
||||
$this->assertSame(0, $result['total']);
|
||||
$this->assertEmpty($result['items']);
|
||||
}
|
||||
|
||||
public function testGetLogsHandlesNullFromSelect(): void
|
||||
{
|
||||
$this->mockDb->method('count')->willReturn(0);
|
||||
$this->mockDb->method('select')->willReturn(null);
|
||||
|
||||
$result = $this->repository->getLogs([], 'id', 'DESC', 1, 15);
|
||||
|
||||
$this->assertSame([], $result['items']);
|
||||
}
|
||||
|
||||
public function testDeleteLogCallsDelete(): void
|
||||
{
|
||||
$this->mockDb->expects($this->once())
|
||||
->method('delete')
|
||||
->with('pp_log', ['id' => 42]);
|
||||
|
||||
$this->assertTrue($this->repository->deleteLog(42));
|
||||
}
|
||||
|
||||
public function testClearLogsDeletesAll(): void
|
||||
{
|
||||
$this->mockDb->expects($this->once())
|
||||
->method('delete')
|
||||
->with('pp_log', []);
|
||||
|
||||
$this->assertTrue($this->repository->clearLogs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,4 +227,110 @@ class OrderAdminServiceTest extends TestCase
|
||||
$service = $this->createService(null, null, $settingsRepo);
|
||||
$this->assertSame(150.0, $service->getFreeDeliveryThreshold());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// processApiloSyncQueue — awaiting apilo_order_id
|
||||
// =========================================================================
|
||||
|
||||
private function getQueuePath(): string
|
||||
{
|
||||
// Musi odpowiadać ścieżce w OrderAdminService::apiloSyncQueuePath()
|
||||
// dirname(autoload/Domain/Order/, 2) = autoload/
|
||||
return dirname(__DIR__, 4) . '/autoload/temp/apilo-sync-queue.json';
|
||||
}
|
||||
|
||||
private function writeQueue(array $queue): void
|
||||
{
|
||||
$path = $this->getQueuePath();
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
file_put_contents($path, json_encode($queue, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private function readQueue(): array
|
||||
{
|
||||
$path = $this->getQueuePath();
|
||||
if (!file_exists($path)) return [];
|
||||
$content = file_get_contents($path);
|
||||
return $content ? json_decode($content, true) : [];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$path = $this->getQueuePath();
|
||||
if (file_exists($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testProcessApiloSyncQueueKeepsTaskWhenApiloOrderIdIsNull(): void
|
||||
{
|
||||
// Zamówienie bez apilo_order_id — task powinien zostać w kolejce
|
||||
$this->writeQueue([
|
||||
'42' => [
|
||||
'order_id' => 42,
|
||||
'payment' => 1,
|
||||
'status' => null,
|
||||
'attempts' => 0,
|
||||
'last_error' => 'awaiting_apilo_order',
|
||||
'updated_at' => '2026-01-01 00:00:00',
|
||||
],
|
||||
]);
|
||||
|
||||
$orderRepo = $this->createMock(OrderRepository::class);
|
||||
$orderRepo->method('findRawById')
|
||||
->with(42)
|
||||
->willReturn([
|
||||
'id' => 42,
|
||||
'apilo_order_id' => null,
|
||||
'paid' => 1,
|
||||
'summary' => '100.00',
|
||||
]);
|
||||
|
||||
$service = new OrderAdminService($orderRepo);
|
||||
$processed = $service->processApiloSyncQueue(10);
|
||||
|
||||
$this->assertSame(1, $processed);
|
||||
|
||||
$queue = $this->readQueue();
|
||||
$this->assertArrayHasKey('42', $queue);
|
||||
$this->assertSame('awaiting_apilo_order', $queue['42']['last_error']);
|
||||
$this->assertSame(1, $queue['42']['attempts']);
|
||||
}
|
||||
|
||||
public function testProcessApiloSyncQueueRemovesTaskAfterMaxAttempts(): void
|
||||
{
|
||||
// Task z 49 próbami — limit to 50, więc powinien zostać usunięty
|
||||
$this->writeQueue([
|
||||
'42' => [
|
||||
'order_id' => 42,
|
||||
'payment' => 1,
|
||||
'status' => null,
|
||||
'attempts' => 49,
|
||||
'last_error' => 'awaiting_apilo_order',
|
||||
'updated_at' => '2026-01-01 00:00:00',
|
||||
],
|
||||
]);
|
||||
|
||||
$orderRepo = $this->createMock(OrderRepository::class);
|
||||
$orderRepo->method('findRawById')
|
||||
->with(42)
|
||||
->willReturn([
|
||||
'id' => 42,
|
||||
'apilo_order_id' => null,
|
||||
'paid' => 1,
|
||||
'summary' => '100.00',
|
||||
]);
|
||||
|
||||
$service = new OrderAdminService($orderRepo);
|
||||
$processed = $service->processApiloSyncQueue(10);
|
||||
|
||||
$this->assertSame(1, $processed);
|
||||
|
||||
$queue = $this->readQueue();
|
||||
$this->assertArrayNotHasKey('42', $queue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,66 @@ class OrderRepositoryTest extends TestCase
|
||||
$this->assertSame('W realizacji', $statuses[4]);
|
||||
}
|
||||
|
||||
public function testOrderStatusDataReturnsBothNamesAndColors(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('select')
|
||||
->willReturnCallback(function ($table, $columns, $where) {
|
||||
if ($table === 'pp_shop_statuses') {
|
||||
return [
|
||||
['id' => 0, 'status' => 'Nowe', 'color' => '#ff0000'],
|
||||
['id' => 4, 'status' => 'W realizacji', 'color' => '#00ff00'],
|
||||
['id' => 5, 'status' => 'Wysłane', 'color' => ''],
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
$repository = new OrderRepository($mockDb);
|
||||
$data = $repository->orderStatusData();
|
||||
|
||||
$this->assertArrayHasKey('names', $data);
|
||||
$this->assertArrayHasKey('colors', $data);
|
||||
$this->assertSame('Nowe', $data['names'][0]);
|
||||
$this->assertSame('W realizacji', $data['names'][4]);
|
||||
$this->assertSame('Wysłane', $data['names'][5]);
|
||||
$this->assertSame('#ff0000', $data['colors'][0]);
|
||||
$this->assertSame('#00ff00', $data['colors'][4]);
|
||||
$this->assertArrayNotHasKey(5, $data['colors']);
|
||||
}
|
||||
|
||||
public function testOrderStatusDataFiltersInvalidHexColors(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('select')
|
||||
->willReturn([
|
||||
['id' => 1, 'status' => 'OK', 'color' => '#abc'],
|
||||
['id' => 2, 'status' => 'Bad', 'color' => 'red'],
|
||||
['id' => 3, 'status' => 'XSS', 'color' => '#000" onclick="alert(1)'],
|
||||
['id' => 4, 'status' => 'Valid', 'color' => '#AABBCC'],
|
||||
]);
|
||||
|
||||
$repository = new OrderRepository($mockDb);
|
||||
$data = $repository->orderStatusData();
|
||||
|
||||
$this->assertSame('#abc', $data['colors'][1]);
|
||||
$this->assertArrayNotHasKey(2, $data['colors']);
|
||||
$this->assertArrayNotHasKey(3, $data['colors']);
|
||||
$this->assertSame('#AABBCC', $data['colors'][4]);
|
||||
}
|
||||
|
||||
public function testOrderStatusDataReturnsEmptyOnDbFailure(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('select')->willReturn(false);
|
||||
|
||||
$repository = new OrderRepository($mockDb);
|
||||
$data = $repository->orderStatusData();
|
||||
|
||||
$this->assertSame([], $data['names']);
|
||||
$this->assertSame([], $data['colors']);
|
||||
}
|
||||
|
||||
public function testNextAndPrevOrderIdReturnNullForInvalidInput(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
@@ -35,6 +35,33 @@ class IntegrationsControllerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testHasLogsMethods(): void
|
||||
{
|
||||
$methods = [
|
||||
'logs',
|
||||
'logs_clear',
|
||||
];
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$this->assertTrue(
|
||||
method_exists($this->controller, $method),
|
||||
"Method $method does not exist"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testLogsReturnsString(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$this->assertEquals('string', (string) $reflection->getMethod('logs')->getReturnType());
|
||||
}
|
||||
|
||||
public function testLogsClearReturnsVoid(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$this->assertEquals('void', (string) $reflection->getMethod('logs_clear')->getReturnType());
|
||||
}
|
||||
|
||||
public function testHasAllApiloSettingsMethods(): void
|
||||
{
|
||||
$methods = [
|
||||
|
||||
@@ -85,4 +85,72 @@ class ShopOrderControllerTest extends TestCase
|
||||
$this->assertEquals('Domain\\Product\\ProductRepository', $params[1]->getType()->getName());
|
||||
$this->assertTrue($params[1]->isOptional());
|
||||
}
|
||||
|
||||
// --- contrastTextColor tests (via reflection) ---
|
||||
|
||||
public function testContrastTextColorReturnsBlackForLightColor(): void
|
||||
{
|
||||
$result = $this->invokePrivate('contrastTextColor', ['#ffffff']);
|
||||
$this->assertSame('#000', $result);
|
||||
}
|
||||
|
||||
public function testContrastTextColorReturnsWhiteForDarkColor(): void
|
||||
{
|
||||
$result = $this->invokePrivate('contrastTextColor', ['#000000']);
|
||||
$this->assertSame('#fff', $result);
|
||||
}
|
||||
|
||||
public function testContrastTextColorHandlesShortHex(): void
|
||||
{
|
||||
$result = $this->invokePrivate('contrastTextColor', ['#fff']);
|
||||
$this->assertSame('#000', $result);
|
||||
|
||||
$result = $this->invokePrivate('contrastTextColor', ['#000']);
|
||||
$this->assertSame('#fff', $result);
|
||||
}
|
||||
|
||||
public function testContrastTextColorDefaultsToWhiteForInvalidHex(): void
|
||||
{
|
||||
$result = $this->invokePrivate('contrastTextColor', ['invalid']);
|
||||
$this->assertSame('#fff', $result);
|
||||
|
||||
$result = $this->invokePrivate('contrastTextColor', ['#zz']);
|
||||
$this->assertSame('#fff', $result);
|
||||
}
|
||||
|
||||
// --- sanitizeInlineHtml tests (via reflection) ---
|
||||
|
||||
public function testSanitizeInlineHtmlStripsDisallowedTags(): void
|
||||
{
|
||||
$result = $this->invokePrivate('sanitizeInlineHtml', ['<b>Bold</b> <script>alert(1)</script> <em>Italic</em>']);
|
||||
$this->assertSame('<b>Bold</b> alert(1) <em>Italic</em>', $result);
|
||||
}
|
||||
|
||||
public function testSanitizeInlineHtmlStripsAttributesFromAllowedTags(): void
|
||||
{
|
||||
$result = $this->invokePrivate('sanitizeInlineHtml', ['<b onclick="alert(1)">Bold</b>']);
|
||||
$this->assertSame('<b>Bold</b>', $result);
|
||||
|
||||
$result = $this->invokePrivate('sanitizeInlineHtml', ['<strong style="color:red" class="x">text</strong>']);
|
||||
$this->assertSame('<strong>text</strong>', $result);
|
||||
}
|
||||
|
||||
public function testSanitizeInlineHtmlPreservesCleanTags(): void
|
||||
{
|
||||
$result = $this->invokePrivate('sanitizeInlineHtml', ['<b>Bold</b> <i>Italic</i> <strong>Strong</strong> <em>Em</em>']);
|
||||
$this->assertSame('<b>Bold</b> <i>Italic</i> <strong>Strong</strong> <em>Em</em>', $result);
|
||||
}
|
||||
|
||||
public function testSanitizeInlineHtmlHandlesPlainText(): void
|
||||
{
|
||||
$result = $this->invokePrivate('sanitizeInlineHtml', ['Kurier DPD']);
|
||||
$this->assertSame('Kurier DPD', $result);
|
||||
}
|
||||
|
||||
private function invokePrivate(string $method, array $args)
|
||||
{
|
||||
$reflection = new \ReflectionMethod($this->controller, $method);
|
||||
$reflection->setAccessible(true);
|
||||
return $reflection->invokeArgs($this->controller, $args);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,23 +1,26 @@
|
||||
{
|
||||
"version": "0.304",
|
||||
"date": "2026-02-22",
|
||||
"checksum_zip": "sha256:98199d033813aadab54cbd6cb495218df5df8f7bc5467ddfb132e7e139aff893",
|
||||
"files": {
|
||||
"added": [
|
||||
"updates/0.30/ver_0.303.zip",
|
||||
"updates/0.30/ver_0.303_manifest.json"
|
||||
],
|
||||
"modified": [
|
||||
"autoload/Domain/PaymentMethod/PaymentMethodRepository.php",
|
||||
"autoload/admin/Controllers/ShopPaymentMethodController.php",
|
||||
"templates/shop-basket/basket-payments-methods.php"
|
||||
],
|
||||
"deleted": []
|
||||
},
|
||||
"directories_deleted": [],
|
||||
"sql": [
|
||||
"ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL;",
|
||||
"ALTER TABLE pp_shop_payment_methods ADD COLUMN max_order_amount DECIMAL(10,2) DEFAULT NULL;"
|
||||
],
|
||||
"changelog": "NEW - konfigurowalne limity kwotowe metod platnosci (min/max kwota zamowienia)"
|
||||
"changelog": "NEW - konfigurowalne limity kwotowe metod platnosci (min/max kwota zamowienia)",
|
||||
"version": "0.304",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"autoload/Domain/PaymentMethod/PaymentMethodRepository.php",
|
||||
"autoload/admin/Controllers/ShopPaymentMethodController.php",
|
||||
"templates/shop-basket/basket-payments-methods.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:86ce0ba4f6e46c380b1cfcefe2d5b206aa9bf26f25e62661d1fac3144fa35f07",
|
||||
"sql": [
|
||||
"ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL;",
|
||||
"ALTER TABLE pp_shop_payment_methods ADD COLUMN max_order_amount DECIMAL(10,2) DEFAULT NULL;"
|
||||
],
|
||||
"date": "2026-02-22",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -1,32 +1,24 @@
|
||||
{
|
||||
"changelog": "FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku",
|
||||
"version": "0.305",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/layout/style-css/style.css",
|
||||
"admin/layout/style-css/style.css.map",
|
||||
"admin/layout/style-scss/style.scss",
|
||||
"admin/templates/shop-product/product-combination.php",
|
||||
"admin/templates/site/main-layout.php",
|
||||
"autoload/Domain/Basket/BasketCalculator.php",
|
||||
"autoload/Shared/Tpl/Tpl.php",
|
||||
"autoload/front/Controllers/ShopBasketController.php",
|
||||
"autoload/front/Controllers/ShopProductController.php",
|
||||
"templates/shop-basket/basket-transport-methods.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:533907ff12252be3bf32f264f61bfa0baa7aefde38514a9f791166cc776171c9",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-22",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
{
|
||||
"version": "0.305",
|
||||
"date": "2026-02-22",
|
||||
"checksum_zip": "sha256:05c06817cc7fdbf362e84be5a547010b9cf9eedba423d8d23fc5de2308bec084",
|
||||
"files": {
|
||||
"added": [],
|
||||
"modified": [
|
||||
"admin/layout/style-css/style.css",
|
||||
"admin/layout/style-css/style.css.map",
|
||||
"admin/layout/style-scss/style.scss",
|
||||
"admin/templates/shop-product/product-combination.php",
|
||||
"admin/templates/site/main-layout.php",
|
||||
"autoload/Domain/Basket/BasketCalculator.php",
|
||||
"autoload/Shared/Tpl/Tpl.php",
|
||||
"autoload/front/Controllers/ShopBasketController.php",
|
||||
"autoload/front/Controllers/ShopProductController.php",
|
||||
"templates/shop-basket/basket-transport-methods.php"
|
||||
],
|
||||
"deleted": []
|
||||
},
|
||||
"directories_deleted": [],
|
||||
"sql": [],
|
||||
"changelog": "FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku"
|
||||
}
|
||||
|
||||
BIN
updates/0.30/ver_0.306.zip
Normal file
BIN
updates/0.30/ver_0.306.zip
Normal file
Binary file not shown.
24
updates/0.30/ver_0.306_manifest.json
Normal file
24
updates/0.30/ver_0.306_manifest.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"changelog": "FIX - ukrywanie form dostawy gdy nie ma dostepnych form platnosci",
|
||||
"version": "0.306",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"autoload/Domain/Transport/TransportRepository.php",
|
||||
"autoload/Domain/Update/UpdateRepository.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:f0fefc2d28c00257f1015fb51bcd9b8b43c04fee4c947f6df1ca04761c61326f",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-22",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.307.zip
Normal file
BIN
updates/0.30/ver_0.307.zip
Normal file
Binary file not shown.
24
updates/0.30/ver_0.307_manifest.json
Normal file
24
updates/0.30/ver_0.307_manifest.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"changelog": "NEW - przycisk Sprawdz aktualizacje w panelu admina, NEW - auto-generowany changelog z manifestow",
|
||||
"version": "0.307",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/templates/site/main-layout.php",
|
||||
"autoload/admin/Controllers/UpdateController.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:175c64537f0238a03903758c2d8ab154277b3889d35551ede17d158e3f62de7d",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-22",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.308.zip
Normal file
BIN
updates/0.30/ver_0.308.zip
Normal file
Binary file not shown.
26
updates/0.30/ver_0.308_manifest.json
Normal file
26
updates/0.30/ver_0.308_manifest.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"changelog": "NEW - kolorowe badge statusow zamowien, walidacja hex, sanityzacja HTML transport, optymalizacja SQL",
|
||||
"version": "0.308",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/templates/components/table-list.php",
|
||||
"autoload/Domain/Order/OrderAdminService.php",
|
||||
"autoload/Domain/Order/OrderRepository.php",
|
||||
"autoload/admin/Controllers/ShopOrderController.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:01bd6e18f737db848913f334a7af78aef729d37b741803f3cbacb7e379969d0e",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-22",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.309.zip
Normal file
BIN
updates/0.30/ver_0.309.zip
Normal file
Binary file not shown.
35
updates/0.30/ver_0.309_manifest.json
Normal file
35
updates/0.30/ver_0.309_manifest.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"changelog": "NEW - ApiloLogger (logowanie operacji Apilo do pp_log), cache-busting CSS/JS w admin panelu, poprawki UI listy produktow, clipboard API",
|
||||
"version": "0.309",
|
||||
"files": {
|
||||
"added": [
|
||||
"autoload/Domain/Integrations/ApiloLogger.php"
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/index.php",
|
||||
"admin/layout/style-css/style.css",
|
||||
"admin/layout/style-css/style.css.map",
|
||||
"admin/layout/style-scss/style.scss",
|
||||
"admin/templates/site/main-layout.php",
|
||||
"autoload/Domain/Order/OrderAdminService.php",
|
||||
"autoload/admin/Controllers/ShopProductController.php",
|
||||
"cron.php",
|
||||
"libraries/functions.js"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:87a3db1a6038da742d21d92b65c21156493af52543b3810694ea91e000acf920",
|
||||
"sql": [
|
||||
"ALTER TABLE pp_log ADD COLUMN `action` VARCHAR(100) NULL DEFAULT NULL AFTER `id`;",
|
||||
"ALTER TABLE pp_log ADD COLUMN `order_id` INT NULL DEFAULT NULL AFTER `action`;",
|
||||
"ALTER TABLE pp_log ADD COLUMN `context` TEXT NULL DEFAULT NULL AFTER `message`;",
|
||||
"ALTER TABLE pp_log ADD INDEX `idx_action` (`action`);",
|
||||
"ALTER TABLE pp_log ADD INDEX `idx_order_id` (`order_id`);"
|
||||
],
|
||||
"date": "2026-02-23",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.310.zip
Normal file
BIN
updates/0.30/ver_0.310.zip
Normal file
Binary file not shown.
25
updates/0.30/ver_0.310_manifest.json
Normal file
25
updates/0.30/ver_0.310_manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"changelog": "NEW - Zakladka Logi w sekcji Integracje (podglad pp_log z paginacja, sortowaniem, filtrami)",
|
||||
"version": "0.310",
|
||||
"files": {
|
||||
"added": [
|
||||
"admin/templates/integrations/logs.php"
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/templates/site/main-layout.php",
|
||||
"autoload/Domain/Integrations/IntegrationsRepository.php",
|
||||
"autoload/admin/Controllers/IntegrationsController.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:e3b14e239230548aba203a83f01c91b00651e5114e92e162f6da7389c6a92975",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-23",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.311.zip
Normal file
BIN
updates/0.30/ver_0.311.zip
Normal file
Binary file not shown.
27
updates/0.30/ver_0.311_manifest.json
Normal file
27
updates/0.30/ver_0.311_manifest.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"changelog": "FIX - race condition callback płatności Apilo, persistence filtrów tabel admin, poprawki cen zamówień",
|
||||
"version": "0.311",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/templates/components/table-list.php",
|
||||
"admin/templates/shop-order/order-details.php",
|
||||
"autoload/Domain/Order/OrderAdminService.php",
|
||||
"cron.php",
|
||||
"templates/shop-order/order-details.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:542f599e844d48bba1378fbe91c06ef00d5487aca56af118ad9c9ea27343ebdb",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-23",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.312.zip
Normal file
BIN
updates/0.30/ver_0.312.zip
Normal file
Binary file not shown.
23
updates/0.30/ver_0.312_manifest.json
Normal file
23
updates/0.30/ver_0.312_manifest.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"changelog": "FIX - krytyczne bugi integracji Apilo: curl_getinfo po curl_close, nieskończona pętla wysyłki, ceny 0.00 PLN, walidacja cen",
|
||||
"version": "0.312",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"cron.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:07f9efd02a6a83327ab8dd9403e0c072a5f38b680d6e3f6c67a96d2af8b8fc85",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-23",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.313.zip
Normal file
BIN
updates/0.30/ver_0.313.zip
Normal file
Binary file not shown.
24
updates/0.30/ver_0.313_manifest.json
Normal file
24
updates/0.30/ver_0.313_manifest.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"changelog": "FIX - sync płatności Apilo (int cast na apilo_order_id PPxxxxxx dawał 0) + logowanie decyzji sync do pp_log",
|
||||
"version": "0.313",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/templates/shop-order/order-details.php",
|
||||
"autoload/Domain/Order/OrderAdminService.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:f344da1f3270abfc63653f8912ec1abbc006154db784cfee5a565fc0daaa75f8",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-23",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.314.zip
Normal file
BIN
updates/0.30/ver_0.314.zip
Normal file
Binary file not shown.
25
updates/0.30/ver_0.314_manifest.json
Normal file
25
updates/0.30/ver_0.314_manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"changelog": "FIX - naprawa globalnej wyszukiwarki admin (Content-Type, Cache-Control, POST, try/catch), NEW - title strony z numerem zamówienia",
|
||||
"version": "0.314",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"admin/templates/shop-order/order-details.php",
|
||||
"admin/templates/site/main-layout.php",
|
||||
"autoload/admin/Controllers/SettingsController.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:5ef21d158850db4036e3ee3ed4e9d2938d0451cd9b8602d26fd53163085a391f",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-23",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
1477
updates/changelog-legacy.json
Normal file
1477
updates/changelog-legacy.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,746 +1,96 @@
|
||||
<b>ver. 0.306 - 22.02.2026</b><br />
|
||||
FIX - ukrywanie form dostawy gdy nie ma dostepnych form platnosci (filtrowanie po min/max kwoty zamowienia)
|
||||
<hr>
|
||||
<b>ver. 0.305 - 22.02.2026</b><br />
|
||||
FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku
|
||||
<hr>
|
||||
<b>ver. 0.304 - 22.02.2026</b><br />
|
||||
NEW - konfigurowalne limity kwotowe metod platnosci (min/max kwota zamowienia)
|
||||
<hr>
|
||||
<b>ver. 0.305 - 22.02.2026</b><br />
|
||||
FIX - naprawa kolejnosci atrybutow permutacji (sortowanie po ID atrybutu), NEW - pasek postepu darmowej dostawy w koszyku, FIX - icheck CSS przeniesiony do globalnego layoutu admina
|
||||
<hr>
|
||||
<b>ver. 0.304 - 22.02.2026</b><br />
|
||||
NEW - konfigurowalne limity kwotowe metod platnosci (min/max kwota zamowienia), zastapienie hardcoded warunku PayPo
|
||||
<hr>
|
||||
<b>ver. 0.303 - 22.02.2026</b><br />
|
||||
FIX - naprawiono wyswietlanie atrybutow produktu na froncie (kolizja kolejnosci), NEW - przycisk Podglad w edycji produktu
|
||||
<hr>
|
||||
<b>ver. 0.302 - 22.02.2026</b><br />
|
||||
NEW - REST API wariantów produktów (CRUD), słownik atrybutów, filtrowanie po atrybutach, wzbogacone atrybuty z tłumaczeniami
|
||||
<hr>
|
||||
<b>ver. 0.301 - 22.02.2026</b><br />
|
||||
NEW - Ukrywalne filtry tabel, mobilna wersja szczegółów zamówienia
|
||||
<hr>
|
||||
<b>ver. 0.300 - 21.02.2026</b><br />
|
||||
- NEW - System aktualizacji oparty na manifestach JSON (checksum SHA256, backup plikĂłw, automatyczny build)
|
||||
- NEW - Panel logu aktualizacji w panelu admina
|
||||
<hr>
|
||||
<b>ver. 0.299 - 21.02.2026</b><br />
|
||||
- NEW - Ukrywanie/pokazywanie kolumn w tabelach admina (toggle switch + localStorage)
|
||||
<hr>
|
||||
<b>ver. 0.298 - 20.02.2026</b><br />
|
||||
- FIX - kilka poprawek po aktualizacji
|
||||
<hr>
|
||||
<b>ver. 0.297 - 19.02.2026</b><br />
|
||||
- NEW - REST API produktów (lista, szczegóły, tworzenie, aktualizacja)
|
||||
- NEW - Endpoint products z filtrowaniem, sortowaniem i paginacjÄ…
|
||||
- NEW - Partial update produktĂłw (tylko zmienione pola)
|
||||
<hr>
|
||||
<b>ver. 0.296 - 19.02.2026</b><br />
|
||||
- NEW - REST API zamówień dla ordersPRO (lista, szczegóły, zmiana statusu, płatności)
|
||||
- NEW - Endpointy słownikowe (statusy, transporty, metody płatności)
|
||||
- NEW - Autentykacja API przez X-Api-Key header
|
||||
- NEW - Kolumna updated_at w pp_shop_orders (polling zmian)
|
||||
<hr>
|
||||
<b>ver. 0.295 - 19.02.2026</b><br />
|
||||
- NEW - Edycja produktów w zamówieniu z panelu admina (dodawanie, usuwanie, zmiana ilości/cen)
|
||||
- NEW - Wyszukiwarka produktĂłw AJAX w formularzu edycji zamĂłwienia
|
||||
- NEW - Automatyczna korekta stanĂłw magazynowych i przeliczanie kosztu dostawy
|
||||
- FIX - Cena promo w zamĂłwieniu = 0 gdy identyczna z cenÄ… bazowÄ…
|
||||
<hr>
|
||||
<b>ver. 0.294 - 19.02.2026</b><br />
|
||||
- FIX - Code review zakończony (96/96 klas, ~1144 metod): 27 fixów across all layers
|
||||
- FIX - Domain: null guard na query()->fetchAll() w 8 repozytoriach, redundancja DI w PromotionRepository
|
||||
- FIX - Admin: null safety find() ?: [] w 10 kontrolerach, null guard w App logowaniu/2FA
|
||||
- FIX - Front: LayoutEngine undefined $level + $_GET null check, ShopBasketController missing global $lang_id
|
||||
- FIX - Shared: Helpers $_GET null check + bug 'png' → 'image/png' (Imagick lossless WebP nigdy nie działał)
|
||||
<hr>
|
||||
<b>ver. 0.293 - 19.02.2026</b><br />
|
||||
- FIX - ArticleRepository: SQL injection fix (addslashes→parameterized), uproszczenie articleDetailsFrontend
|
||||
- FIX - AttributeRepository: martwy class_exists('\S') blokowal czyszczenie cache/temp
|
||||
- FIX - CategoryRepository: martwy class_exists('\S') blokowal generowanie linkow SEO kategorii
|
||||
- FIX - BannerRepository: parametryzacja dat w SQL + null guard na query()
|
||||
- FIX - BasketCalculator: null guard checkProductQuantityInStock + opcjonalne DI params summaryPrice/calculateBasketProductPrice
|
||||
- FIX - PromotionRepository: null guard na $basket (produkcyjny fatal error)
|
||||
- UPDATE - OrderRepository, ShopBasketController, ajax.php: jawne DI zamiast globals w callerach BasketCalculator
|
||||
<hr>
|
||||
<b>ver. 0.292 - 18.02.2026</b><br />
|
||||
- UPDATE - pelna migracja front\factory\ do Domain (5 ostatnich klas: ShopProduct, ShopPaymentMethod, ShopPromotion, ShopStatuses, ShopTransport)
|
||||
- UPDATE - ProductRepository: ~20 nowych metod frontendowych (cache Redis, lazy loading, SKU/EAN fallback)
|
||||
- UPDATE - PromotionRepository: 5 metod aplikowania promocji (applyTypeWholeBasket/CheapestProduct/CategoriesOr/CategoriesAnd/CategoryCondition)
|
||||
- UPDATE - TransportRepository: 4 metody frontendowe z cache (transportMethodsFront, transportCostCached, findActiveByIdCached, forPaymentMethod)
|
||||
- UPDATE - PaymentMethodRepository: metody frontendowe z Redis cache
|
||||
- CLEANUP - usuniety caly folder front\factory\ (20 klas zmigrowanych) + 4 inne klasy legacy
|
||||
- FIX - broken transports_list() w ajax.php zastapiony nowa metoda forPaymentMethod()
|
||||
- UPDATE - front\controls\Site przemianowany na front\App (router, camelCase: checkUrlParams, pageTitle)
|
||||
- UPDATE - front\view\Site przemianowany na front\LayoutEngine (layout engine, camelCase: cookieInformation)
|
||||
- CLEANUP - usuniete autoload/front/controls/ i autoload/front/view/ (puste foldery + pliki legacy)
|
||||
- UPDATE - usuniecie 12 legacy klas z autoload/shop/ (~2363 linii) — pelna migracja na Domain-Driven Architecture
|
||||
- UPDATE - class.Order.php: logika Apilo sync i email statusu przeniesiona do OrderAdminService
|
||||
- UPDATE - class.Product.php: ~20 metod przeniesionych do ProductRepository, calculate_basket_product_price do BasketCalculator
|
||||
- FIX - findCached(): stale Redis cache z obiektami \shop\Product powodowal ceny 0,00 zl
|
||||
- FIX - szablony: konwersja object access na array access po migracji Product
|
||||
- UPDATE - AttributeRepository::getAttributeValueById() — dodano Redis cache
|
||||
- CLEANUP - katalog autoload/shop/ pusty, zero referencji \shop\ w aktywnym kodzie
|
||||
<hr>
|
||||
<b>ver. 0.291 - 17.02.2026</b><br />
|
||||
- UPDATE - migracja front\controls\ShopProducer + shop\Producer do Domain\Producer\ProducerRepository + front\Controllers\ShopProducerController
|
||||
- FIX - bug shop\Producer::__get() referowal nieistniejace $this->data
|
||||
<hr>
|
||||
<b>ver. 0.290 - 17.02.2026</b><br />
|
||||
- UPDATE - migracja front\factory\ShopCoupon + front\controls\ShopCoupon do Domain\Coupon\CouponRepository + front\Controllers\ShopCouponController
|
||||
- UPDATE - migracja front\factory\ShopOrder + front\controls\ShopOrder + front\view\ShopOrder do Domain\Order\OrderRepository + front\Controllers\ShopOrderController
|
||||
- FIX - kupony jednorazowe nigdy nie byly oznaczane jako uzyte (is_one_time/set_as_used w shop\Coupon)
|
||||
- FIX - webhooks przelewy24/hotpay ujednolicone z tpay (poprawna obsluga Apilo sync)
|
||||
<hr>
|
||||
<b>ver. 0.289 - 17.02.2026</b><br />
|
||||
- UPDATE - migracja front\factory\ShopCategory + front\view\ShopCategory do Domain\Category\CategoryRepository + front\Views\ShopCategory
|
||||
- UPDATE - migracja front\factory\ShopClient + front\view\ShopClient + front\controls\ShopClient do Domain\Client\ClientRepository + front\Views\ShopClient + front\Controllers\ShopClientController
|
||||
- FIX - usuniety hardcoded password bypass 'Legia1916' w logowaniu klienta
|
||||
<hr>
|
||||
<b>ver. 0.288 - 17.02.2026</b><br />
|
||||
- UPDATE - migracja front\factory\ShopBasket do Domain\Basket\BasketCalculator (4 metody statyczne)
|
||||
- UPDATE - migracja front\controls\ShopBasket do front\Controllers\ShopBasketController (camelCase, instancyjny)
|
||||
- UPDATE - routing snake_case->camelCase w dispatch dla nowych kontrolerow
|
||||
- CLEANUP - usunieta klasa cms\Layout (zastapiona $layoutsRepo->find)
|
||||
- CLEANUP - usuniete 3 klasy legacy (front\factory\ShopBasket, front\controls\ShopBasket, cms\Layout)
|
||||
<hr>
|
||||
<b>ver. 0.287 - 17.02.2026</b><br />
|
||||
- UPDATE - migracja front\factory\Scontainers do Domain\Scontainers\ScontainersRepository (frontScontainerDetails z Redis cache)
|
||||
- UPDATE - migracja front\factory\ShopAttribute do Domain\Attribute\AttributeRepository (frontAttributeDetails, frontValueDetails z Redis cache)
|
||||
- CLEANUP - usuniete 3 klasy legacy (front\factory\Scontainers, front\view\Scontainers, front\factory\ShopAttribute)
|
||||
<hr>
|
||||
<b>ver. 0.286 - 17.02.2026</b><br />
|
||||
- UPDATE - migracja front\factory\Layouts do Domain\Layouts\LayoutsRepository (6 metod frontend z Redis cache)
|
||||
- UPDATE - migracja front\factory\Menu + front\factory\Pages do Domain\Pages\PagesRepository (6 metod frontend z Redis cache)
|
||||
- UPDATE - migracja front\view\Menu do front\Views\Menu (nowy namespace)
|
||||
- CLEANUP - usuniete 4 klasy legacy + 1 martwy szablon (submenu.php)
|
||||
- FIX - null $lang_id przy wczesnym wywolaniu check_url_params()
|
||||
<hr>
|
||||
<b>ver. 0.285 - 17.02.2026</b><br />
|
||||
- UPDATE - migracja class.Tpl.php do Shared\Tpl\Tpl (~135 plikow przepietych)
|
||||
- CLEANUP - usunieta nieuzywana klasa CurlServer (curl.class.php)
|
||||
- FIX - thumb.php: naprawa require po migracji Image do Shared namespace
|
||||
- FIX - Tpl::render() branch 3: sprawdzal templates_user ale ladowal templates
|
||||
<hr>
|
||||
<b>ver. 0.284 - 16.02.2026</b><br />
|
||||
- CLEANUP - usunieta klasa DbModel (base ORM) — logika wbudowana bezposrednio w shop\Promotion
|
||||
<hr>
|
||||
<b>ver. 0.283 - 16.02.2026</b><br />
|
||||
- UPDATE - migracja class.S.php do Shared\Helpers\Helpers (~140 plikow przepietych)
|
||||
- UPDATE - migracja class.Html.php do Shared\Html\Html
|
||||
- UPDATE - migracja class.Email.php do Shared\Email\Email
|
||||
- UPDATE - migracja class.Image.php do Shared\Image\ImageManipulator
|
||||
- UPDATE - migracja class.Log.php do Shared\Log\Log (usunieta — logika przeniesiona)
|
||||
- CLEANUP - usunieta class.Mobile_Detect.php (przestarzala detekcja UA)
|
||||
- CLEANUP - usunieto 12 nieuzywanych metod z klasy S
|
||||
- FIX - array_cartesian_product() — blad iteracji po niezdefiniowanej zmiennej
|
||||
<hr>
|
||||
<b>ver. 0.282 - 16.02.2026</b><br />
|
||||
- UPDATE - Cache cleanup: eliminacja legacy class.Cache.php, migracja CacheHandler i RedisConnection do Shared\Cache namespace
|
||||
- UPDATE - 60 odwolan CacheHandler i 12 odwolan RedisConnection przepietych na Shared\Cache\
|
||||
- UPDATE - 13 metod front\factory przepietych z \Cache::fetch/store na CacheHandler (ShopProduct, ShopPaymentMethod, ShopCategory, ShopTransport, ShopAttribute)
|
||||
- FIX - naprawione rozbieznosci kluczy cache (random_products, category_name)
|
||||
- CLEANUP - usuniete: class.Cache.php, class.CacheHandler.php, class.RedisConnection.php
|
||||
- UPDATE - testy: OK (454 tests, 1449 assertions)
|
||||
<hr>
|
||||
<b>ver. 0.281 - 16.02.2026</b><br />
|
||||
- UPDATE - migracja Banners frontend: factory + view do Domain/Views (DI)
|
||||
- NEW - `front\Views\Banners` — czysty VIEW (banners, mainBanner)
|
||||
- UPDATE - `BannerRepository` rozszerzony o 2 metody frontendowe (banners, mainBanner) z Redis cache
|
||||
- UPDATE - `front\view\Site::show()` przepiety na repo + Views
|
||||
- CLEANUP - usuniete: front\factory\Banners, front\view\Banners
|
||||
- UPDATE - testy: OK (454 tests, 1449 assertions)
|
||||
<hr>
|
||||
<b>ver. 0.280 - 16.02.2026</b><br />
|
||||
- UPDATE - migracja Articles frontend: factory + view + encja do Domain/Views (DI)
|
||||
- NEW - `front\Views\Articles` — czysty VIEW + utility (renderowanie, generateTableOfContents, generateHeadersIds, getImage)
|
||||
- UPDATE - `ArticleRepository` rozszerzony o 8 metod frontendowych (z Redis cache)
|
||||
- UPDATE - `front\view\Site::show()` — 5 sekcji przepietych na repo + Views
|
||||
- UPDATE - `front\controls\Site::route()` — single article + page_type switch przepiete na repo + Views
|
||||
- UPDATE - 5 szablonow `templates/articles/*` przepietych na `\front\Views\Articles::`
|
||||
- CLEANUP - usuniete: `class.Article` (encja + metody statyczne), `front\factory\Settings` (fasada)
|
||||
- FIX - eliminacja `global $lang` z `articleNoindex()`, eliminacja zaleznosci od `front\factory\Pages::page_sort()`
|
||||
- UPDATE - testy: `OK (450 tests, 1431 assertions)`
|
||||
<hr>
|
||||
<b>ver. 0.279 - 16.02.2026</b><br />
|
||||
- UPDATE - migracja Newsletter frontend: factory + view + controls do Domain/Controllers/Views (DI)
|
||||
- UPDATE - nowy namespace `front\Controllers` z `NewsletterController` (DI via factory closures)
|
||||
- UPDATE - nowy namespace `front\Views` z `Languages` i `Newsletter` (czyste VIEW, statyczne metody)
|
||||
- UPDATE - routing frontend: `Site::getControllerFactories()` z fallback na stare kontrolery
|
||||
- FIX - `newsletter_unsubscribe()` — poprawiona skladnia medoo `delete()` (2 argumenty zamiast 3)
|
||||
- UPDATE - eliminacja fasady `front\factory\Languages` — 26 zaleznosci przepietych na `LanguagesRepository`
|
||||
- CLEANUP - usuniete: `front\factory\Languages`, `front\factory\Newsletter`, `front\view\Languages`, `front\view\Newsletter`, `front\controls\Newsletter`
|
||||
- UPDATE - testy: `OK (437 tests, 1398 assertions)`
|
||||
<hr>
|
||||
<b>ver. 0.278 - 16.02.2026</b><br />
|
||||
- UPDATE - migracja Settings + Languages do wspolnych klas Domain (z cache Redis)
|
||||
- FIX - `get_single_settings_value()` — parametr `$param` poprawnie uzywany (wczesniej hardcoded `firm_name`)
|
||||
<hr>
|
||||
<b>ver. 0.277 - 16.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopProduct` (factory) — pelna migracja ~40 metod do `ProductRepository` + ~30 akcji w `ShopProductController`
|
||||
- NEW - migracja modulu `Dashboard` do Domain + DI (`DashboardRepository`, `DashboardController`)
|
||||
- NEW - migracja modulu `Update` do Domain + DI (`UpdateRepository`, `UpdateController`)
|
||||
- UPDATE - klasa `admin\Site` przemianowana na `admin\App` (plik `App.php`)
|
||||
- UPDATE - refaktoring routingu — usunieto fallback na stare kontrolery, uproszczony routing
|
||||
- UPDATE - template `update/main-view.php` — panele zamiast `gridEdit`, `$.confirm()` zamiast `$.prompt()`
|
||||
- CLEANUP - usuniete stare foldery: `autoload/admin/controls/`, `autoload/admin/factory/`, `autoload/admin/view/`
|
||||
- CLEANUP - usuniete legacy: `class.Dashboard.php` (controls/shop), `class.Update.php` (controls/factory/view), `class.Articles.php` (factory), `class.Page.php` (view), `class.ShopProduct.php` (controls/factory/view)
|
||||
- UPDATE - `front\factory\Newsletter` przepieta na `ArticleRepository::articlesByDateAdd()`
|
||||
- UPDATE - testy: `OK (414 tests, 1335 assertions)`
|
||||
<hr>
|
||||
<b>ver. 0.276 - 15.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopOrder` do architektury Domain + DI (`Domain\Order\OrderRepository`, `Domain\Order\OrderAdminService`, `admin\Controllers\ShopOrderController`)
|
||||
- UPDATE - modul `/admin/shop_order/*` przepiety na nowy routing (kanoniczny URL `/admin/shop_order/list/`) i nowe widoki (`orders-list`, `order-details`, `order-edit`)
|
||||
- FIX - stabilizacja listy zamowien (`OrderRepository::listForAdmin`) oraz poprawa wygladu tabeli (`components/table-list`, wyrownanie komorek i `text-right`)
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopOrder.php`, `autoload/admin/factory/class.ShopOrder.php`, `admin/templates/shop-order/view-list.php`
|
||||
- UPDATE - usunieta fasada `autoload/admin/factory/class.Integrations.php`; wywolania przepiete na `Domain\Integrations\IntegrationsRepository`
|
||||
- NEW - globalna wyszukiwarka admin (produkty + zamowienia) przy "Wyczysc cache" + endpoint `/admin/settings/globalSearchAjax/`
|
||||
- FIX - wyszukiwanie po pelnym imieniu i nazwisku w global search
|
||||
- UPDATE - testy: `OK (385 tests, 1246 assertions)`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.276.zip`, `ver_0.276_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.275 - 15.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopCategory` do architektury Domain + DI (`Domain\Category\CategoryRepository`, `admin\Controllers\ShopCategoryController`)
|
||||
- UPDATE - modul `/admin/shop_category/*` przepiety na nowy routing (kanoniczny URL `/admin/shop_category/list/`) i endpointy AJAX kontrolera (`save_categories_order`, `save_products_order`, `cookie_categories`)
|
||||
- UPDATE - widoki `shop-category/*`: wydzielenie skryptow do `*-custom-script.php`, ujednolicone strzalki drzewa (`button + caret + aria-expanded`)
|
||||
- UPDATE - przepiecie zaleznosci `ShopProduct` z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository`
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopCategory.php`, `autoload/admin/factory/class.ShopCategory.php`, `autoload/admin/view/class.ShopCategory.php`
|
||||
- UPDATE - testy: `OK (377 tests, 1197 assertions)`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.275.zip`, `ver_0.275_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.274 - 15.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopClients` do architektury Domain + DI (`Domain\Client\ClientRepository`, `admin\Controllers\ShopClientsController`)
|
||||
- UPDATE - modul `/admin/shop_clients/*` przepiety na `components/table-list` (lista klientow i szczegoly zamowien)
|
||||
- UPDATE - routing i menu admin przepiete na kanoniczny URL `/admin/shop_clients/list/`
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopClients.php`, `autoload/admin/factory/class.ShopClients.php`
|
||||
- UPDATE - testy: `OK (361 tests, 1125 assertions)`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.274.zip`, `ver_0.274_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.273 - 15.02.2026</b><br />
|
||||
- NEW - migracja `/admin/shop_product/mass_edit/*` do `Domain\Product\ProductRepository` + `admin\Controllers\ShopProductController` (DI + routing)
|
||||
- UPDATE - nowy widok/skrypt masowej edycji (`mass-edit`, `mass-edit-custom-script`) z iCheck i ujednoliconymi strzalkami drzewa
|
||||
- FIX - zaznaczanie kategorii w mass-edit nie zaznacza automatycznie produktow na liscie
|
||||
- UPDATE - ujednolicenie strzalek/checkboxow w drzewkach: `/admin/pages/list/*` oraz zakladka wyswietlania w `/admin/articles/edit/*`
|
||||
- UPDATE - testy: `OK (351 tests, 1091 assertions)`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.273.zip`, `ver_0.273_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.272 - 15.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopProductSets` do architektury Domain + DI (`Domain\ProductSet\ProductSetRepository`, `admin\Controllers\ShopProductSetsController`)
|
||||
- UPDATE - modul `/admin/shop_product_sets/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit` + multi-select Selectize
|
||||
- UPDATE - routing i menu admin przepiete na kanoniczny URL `/admin/shop_product_sets/list/`
|
||||
- UPDATE - `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository`
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php`
|
||||
- UPDATE - testy: `OK (324 tests, 1000 assertions)` + nowe pliki testowe `ProductSetRepositoryTest`, `ShopProductSetsControllerTest`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.272.zip`, `ver_0.272_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.271 - 14.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopAttribute` do architektury Domain + DI (`Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`)
|
||||
- UPDATE - modul `/admin/shop_attribute/*` przepiety z legacy `grid/gridEdit` na `components/table-list`, `components/form-edit` oraz nowy edytor wartosci (`values-edit`)
|
||||
- UPDATE - routing i menu admin przepiete na kanoniczny URL `/admin/shop_attribute/list/` (bez aliasow legacy)
|
||||
- UPDATE - przepiecie zaleznosci kombinacji produktu: `admin\controls\ShopProduct`, `admin\factory\ShopProduct`, `admin/templates/shop-product/product-combination.php`
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopAttribute.php`, `autoload/admin/factory/class.ShopAttribute.php`, `autoload/admin/view/class.ShopAttribute.php`, `admin/templates/shop-attribute/_partials/value.php`
|
||||
- UPDATE - testy: `OK (312 tests, 948 assertions)` + nowe pliki testowe `AttributeRepositoryTest`, `ShopAttributeControllerTest`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.271.zip`, `ver_0.271_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.270 - 14.02.2026</b><br />
|
||||
- FIX - Apilo: `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z `payment_method_id`), zamiast stalego `type = 1`
|
||||
- NEW - Apilo: dodana kolejka retry `temp/apilo-sync-queue.json` dla nieudanych syncow platnosci/statusu (chwilowa niedostepnosc API)
|
||||
- UPDATE - `cron.php`: automatyczne ponawianie zaleglych syncow przez `Order::process_apilo_sync_queue(10)`
|
||||
- UPDATE - debug Apilo: rozszerzone logi odpowiedzi o HTTP code i bledy cURL dla sync platnosci/statusu
|
||||
- UPDATE - testy: `OK (300 tests, 895 assertions)`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.270.zip`, `ver_0.270_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.269 - 14.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopPaymentMethod` do architektury Domain + DI (`Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`)
|
||||
- UPDATE - modul `/admin/shop_payment_method/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit` (nowe widoki listy i edycji)
|
||||
- UPDATE - przepiecie zaleznosci na nowe repozytorium: `admin\controls\ShopTransport`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopPaymentMethod.php`, `autoload/admin/factory/class.ShopPaymentMethod.php`, `autoload/admin/view/class.ShopPaymentMethod.php`, `admin/templates/shop-payment-method/view-list.php`
|
||||
- UPDATE - Apilo: dodane automatyczne odswiezanie tokenu przed wygasnieciem (`apiloKeepalive`) oraz bardziej szczegolowe komunikaty bledow integracji
|
||||
- UPDATE - testy: `OK (280 tests, 828 assertions)` + nowe pliki testowe `PaymentMethodRepositoryTest`, `ShopPaymentMethodControllerTest`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.269.zip`, `ver_0.269_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.268 - 14.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopStatuses` do architektury Domain + DI (`Domain\ShopStatus\ShopStatusRepository`, `admin\Controllers\ShopStatusesController`)
|
||||
- UPDATE - modul `/admin/shop_statuses/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
|
||||
- NEW - nowy typ pola formularza `color` (HTML5 color picker + pole tekstowe zsynchronizowane)
|
||||
- UPDATE - `front\factory\ShopStatuses` dziala jako fasada do `Domain\ShopStatus\ShopStatusRepository`
|
||||
- UPDATE - menu admin przepiete na kanoniczny URL `/admin/shop_statuses/list/`
|
||||
- CLEANUP - usuniete legacy klasy: `autoload/admin/controls/class.ShopStatuses.php`, `autoload/admin/factory/class.ShopStatuses.php`
|
||||
- UPDATE - reorganizacja dokumentacji technicznej: pliki przeniesione do folderu `docs/` i rozbite na mniejsze pliki tematyczne
|
||||
- UPDATE - testy: `OK (254 tests, 736 assertions)` + nowe pliki testowe `ShopStatusRepositoryTest`, `ShopStatusesControllerTest`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.268.zip`, `ver_0.268_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.267 - 13.02.2026</b><br />
|
||||
- FIX - front: poprawione dobieranie layoutu dla kategorii/produktu/koszyka i innych stron modułowych (fallback do layoutu domyślnego)
|
||||
- FIX - produkt/koszyk: poprawiona obsługa ilości dla kombinacji (stan 0 po dodaniu do koszyka, limit max, odczyt `stock_0_buy`)
|
||||
- FIX - produkt: usunięty błąd JS `TypeError: $(...).visible is not a function` (zamiana na `:visible`)
|
||||
- FIX - SEO redirecty produktów: blokada konfliktów po kopiowaniu URL oraz utwardzone wykrywanie pętli redirectów (`lang_id` + graf przejść)
|
||||
- UPDATE - admin: `input-switch` zapisuje wartość `on` (spójnie z obsługą pól checkbox w formularzach)
|
||||
- CLEANUP - usunięte pliki: `apilo-bck`, `geocode-cache.php`
|
||||
- UPDATE - testy: `OK (235 tests, 682 assertions)`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.267.zip`, `ver_0.267_files.txt`, `ver_0.267_sql.txt`
|
||||
<hr>
|
||||
<b>ver. 0.266 - 13.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopCoupon` do architektury Domain + DI (`Domain\Coupon\CouponRepository`, `admin\Controllers\ShopCouponController`)
|
||||
- UPDATE - modul `/admin/shop_coupon/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
|
||||
- UPDATE - nowe widoki i partiale: `shop-coupon/coupons-list`, `shop-coupon/coupon-edit-new`, `shop-coupon/coupon-categories-selector`, `shop-coupon/coupon-categories-tree`, `shop-coupon/coupon-edit-custom-script`
|
||||
- UPDATE - zachowana kompatybilnosc aliasow legacy akcji (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) w nowym kontrolerze
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopCoupon.php`, `autoload/admin/factory/class.ShopCoupon.php`, `admin/templates/shop-coupon/view-list.php`, `admin/templates/shop-coupon/coupon-edit.php`
|
||||
- UPDATE - menu admin wskazuje kanoniczny URL `/admin/shop_coupon/list/`
|
||||
- FIX - ujednolicone drzewka (strzalki + focus) i wyglad checkboxow miedzy `/admin/shop_coupon/edit/*` oraz `/admin/layouts/edit/*`
|
||||
- UPDATE - testy: `OK (235 tests, 682 assertions)` + nowe pliki testowe `CouponRepositoryTest`, `ShopCouponControllerTest`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.266.zip`, `ver_0.266_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.265 - 13.02.2026</b><br />
|
||||
- UPDATE - modul `/admin/shop_promotion/*`: dodano pole `Data od` (`date_from`) w repozytorium, formularzu i liscie
|
||||
- UPDATE - front: `shop\Promotion::get_active_promotions()` uwzglednia `date_from` (okno aktywnosci od-do)
|
||||
- FIX - edycja promocji zapisuje aktualizacje rekordu zamiast tworzenia nowego (`id` przekazywane przez hidden field + fallback z URL)
|
||||
- UPDATE - testy: `OK (222 tests, 614 assertions)`
|
||||
<hr><b>ver. 0.264 - 13.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopPromotion` do architektury Domain + DI (`Domain\Promotion\PromotionRepository`, `admin\Controllers\ShopPromotionController`)
|
||||
- UPDATE - modul `/admin/shop_promotion/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
|
||||
- UPDATE - nowe widoki i partiale: `shop-promotion/promotions-list`, `shop-promotion/promotion-edit`, `shop-promotion/promotion-categories-selector`, `shop-promotion/promotion-categories-tree`, `shop-promotion/promotion-edit-custom-script`
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopPromotion.php`, `autoload/admin/factory/class.ShopPromotion.php`, `admin/templates/shop-promotion/view-list.php`
|
||||
- UPDATE - menu admin wskazuje kanoniczny URL `/admin/shop_promotion/list/`
|
||||
- UPDATE - testy: `OK (222 tests, 609 assertions)` + nowe pliki testowe `PromotionRepositoryTest`, `ShopPromotionControllerTest`
|
||||
<hr><b>ver. 0.263 - 13.02.2026</b><br />
|
||||
- NEW - migracja modulu `Integrations` do architektury Domain + DI (`Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`)
|
||||
- CLEANUP - usunieto integracje Sellasist i Baselinker z calego projektu (kontrolery, factory, szablony, referencje w cron/Order/ShopStatuses/ShopTransport/ShopPaymentMethod/ShopProduct)
|
||||
- UPDATE - `admin\factory\Integrations` jako fasada delegujaca do repozytorium (tylko Apilo + ShopPRO)
|
||||
- FIX - naprawione polskie znaki w `product-edit.php` (usuniety podwojny encoding UTF-8/CP1250)
|
||||
- CLEANUP - usuniete pliki: `controls/Integrations`, `controls/Baselinker`, `factory/Baselinker`, `front/factory/Shop`, `shop/ShopStatus`, szablony sellasist/baselinker
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.263.zip`, `ver_0.263_files.txt`
|
||||
<hr><b>ver. 0.262 - 13.02.2026</b><br />
|
||||
- NEW - migracja modulu `Pages` do architektury Domain + DI (`Domain\\Pages\\PagesRepository`, `admin\\Controllers\\PagesController`)
|
||||
- UPDATE - widoki `/admin/pages/*` przepiete na nowy routing i komponent `components/form-edit` (menu/page edit)
|
||||
- FIX - przywrocony przycisk generowania linku SEO w edycji strony (zakladka SEO, pola jezykowe)
|
||||
- FIX - popup potwierdzenia usuwania menu/strony ujednolicony z `table-list-confirm-dialog` + poprawione polskie znaki
|
||||
- CLEANUP - usuniete legacy pliki Pages: `admin/ajax/pages.php`, `autoload/admin/controls/class.Pages.php`, `autoload/admin/factory/class.Pages.php`, `autoload/admin/view/class.Pages.php`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.262.zip`, `ver_0.262_files.txt`
|
||||
<hr><b>ver. 0.261 - 13.02.2026</b><br />
|
||||
- UPDATE - finalizacja refaktoryzacji modulu `Articles` (`/admin/articles`) w warstwie Domain + DI
|
||||
- UPDATE - nowe akcje AJAX w `admin\Controllers\ArticlesController` (m.in. `files_order_save`)
|
||||
- UPDATE - sortowanie zalacznikow i zdjec w edycji artykulu (drag&drop + zapis kolejnosci przy pierwszym zapisie)
|
||||
- UPDATE - utwardzenie uploadow (wspolny helper `libraries/plupload/upload-common.php`, walidacje i tokeny)
|
||||
- FIX - potwierdzenia usuwania zdjec/zalacznikow ujednolicone z widokiem listy (jquery-confirm)
|
||||
- CLEANUP - usuniete legacy `admin/ajax/articles.php` i `autoload/admin/view/class.Articles.php`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.261.zip`, `ver_0.261_files.txt`, `ver_0.261_sql.txt`
|
||||
<hr><b>ver. 0.260 - 12.02.2026</b><br />
|
||||
- NEW - migracja modulu `ArticlesArchive` do architektury Domain + DI (`admin\\Controllers\\ArticlesArchiveController`)
|
||||
- UPDATE - `Domain\\Article\\ArticleRepository` rozszerzone o metody `listArchivedForAdmin`, `restore`, `deletePermanently`
|
||||
- UPDATE - widok `/admin/articles_archive/view_list/` przepiety z legacy `grid` na `components/table-list`
|
||||
- UPDATE - routing DI (`admin\\Site`) rozszerzony o modul `ArticlesArchive` + mapowanie akcji `article_restore -> restore`
|
||||
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.ArticlesArchive.php`, `autoload/admin/factory/class.ArticlesArchive.php`, `autoload/admin/view/class.ArticlesArchive.php`
|
||||
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.260_files.txt`
|
||||
<hr><b>ver. 0.259 - 12.02.2026</b><br />
|
||||
- NEW - migracja modulu `Scontainers` do architektury Domain + DI (`Domain\\Scontainers\\ScontainersRepository`, `admin\\Controllers\\ScontainersController`)
|
||||
- UPDATE - widoki `/admin/scontainers/*` przepiete z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
|
||||
- UPDATE - routing DI (`admin\\Site`) rozszerzony o modul `Scontainers` + mapowanie akcji `container_edit/container_save/container_delete`
|
||||
- UPDATE - `admin\\factory\\Scontainers` dziala jako fasada do repozytorium (backward compatibility)
|
||||
- UPDATE - `front\\factory\\Scontainers` korzysta z `Domain\\Scontainers\\ScontainersRepository`
|
||||
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Scontainers.php`, `autoload/admin/view/class.Scontainers.php`
|
||||
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.259_files.txt`
|
||||
<hr><b>ver. 0.258 - 12.02.2026</b><br />
|
||||
- UPDATE - modul `Newsletter`: funkcjonalnosc `Wysylka - przygotowanie` zostala tymczasowo wylaczona (menu + akcje `prepare/send/preview`)
|
||||
- UPDATE - modul `Newsletter`: lista `Szablony uzytkownika` zostala tymczasowo wylaczona (menu + akcja `email_templates_user`)
|
||||
- UPDATE - `NewsletterController`: lista szablonow ograniczona do szablonow administracyjnych (`is_admin = 1`)
|
||||
- UPDATE - `email_template_edit` i `template_save` obsluguja tylko szablony administracyjne
|
||||
- CLEANUP - usuniete nieuzywane szablony newslettera: `admin/templates/newsletter/prepare.php`, `admin/templates/newsletter/preview.php`, `admin/templates/newsletter/email-templates-user.php`
|
||||
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.258_files.txt`
|
||||
<hr><b>ver. 0.257 - 12.02.2026</b><br />
|
||||
- NEW - migracja modulu `Newsletter` do architektury Domain + DI (`Domain\\Newsletter\\NewsletterRepository`, `Domain\\Newsletter\\NewsletterPreviewRenderer`, `admin\\Controllers\\NewsletterController`)
|
||||
- UPDATE - widoki `/admin/newsletter/*` przepiete z legacy `grid/gridEdit` na nowe komponenty (`components/table-list`, `components/form-edit`) + nowy endpoint `/admin/newsletter/preview/`
|
||||
- UPDATE - routing DI (`admin\\Site`) rozszerzony o moduł `Newsletter`
|
||||
- UPDATE - `admin\\factory\\Newsletter` dziala jako fasada do nowego repozytorium (backward compatibility)
|
||||
- UPDATE - `front\\factory\\Newsletter` nie korzysta juz z `admin\\view\\Newsletter`
|
||||
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Newsletter.php`, `autoload/admin/view/class.Newsletter.php`
|
||||
<hr><b>ver. 0.256 - 12.02.2026</b><br />
|
||||
- NEW - migracja modulu `Layouts` do architektury Domain + DI (`Domain\\Layouts\\LayoutsRepository`, `admin\\Controllers\\LayoutsController`)
|
||||
- UPDATE - lista `/admin/layouts/view_list/` przepieta z legacy `grid` na `components/table-list` (filtry, sortowanie, paginacja)
|
||||
- UPDATE - `layouts/layout-edit` korzysta z danych z repozytorium (menus/categories), bez wywolan legacy factory w widoku
|
||||
- UPDATE - `Domain\\Languages\\LanguagesRepository` rozszerzone o wspolna metode `defaultLanguageId()`
|
||||
- UPDATE - `admin\\Controllers\\ArticlesController` pobiera layouty przez `Domain\\Layouts\\LayoutsRepository` (DI)
|
||||
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Layouts.php`, `autoload/admin/view/class.Layouts.php`
|
||||
<hr><b>ver. 0.255 - 12.02.2026</b><br />
|
||||
- UPDATE - kontrolery admin `Settings`, `Banners`, `Dictionaries`, `Articles` pobieraja liste jezykow przez `Domain\\Languages\\LanguagesRepository` (DI)
|
||||
- UPDATE - routing DI (`admin\\Site`) przekazuje `LanguagesRepository` do kontrolerow `Articles`, `Banners`, `Settings`, `Dictionaries`
|
||||
- UPDATE - aktywne legacy odwolania (`admin\\controls`, `admin\\factory\\Shop*`) przepiete z `admin\\factory\\Languages` na `LanguagesRepository`
|
||||
- FIX - `autoload/admin/factory/class.Languages.php` uzywa pelnego znacznika PHP (zgodnosc z `short_open_tag=Off`)
|
||||
<hr><b>ver. 0.254 - 12.02.2026</b><br />
|
||||
- UPDATE - modul `Languages` w panelu admin przepiety na `Domain\\Languages\\LanguagesRepository` + `admin\\Controllers\\LanguagesController`
|
||||
- UPDATE - migracja widokow languages (`languages-list`, `language-edit`, `translations-list`, `translation-edit`) na `components/table-list` i `components/form-edit`
|
||||
- UPDATE - routing DI dla `Languages` w `admin\\Site` oraz kompatybilna fasada `admin\\factory\\Languages` delegujaca do repozytorium
|
||||
- UPDATE - naprawiono zapis edycji jezyka (ID jezyka pobierane z URL przy edycji)
|
||||
- UPDATE - globalne poprawki UX filtrĂłw w `components/table-list` (kompaktowe kolumny `Aktywny`/`Domyslny`, spacing i pelna szerokosc selecta)
|
||||
- CLEANUP - usuniete legacy klasy: `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php`
|
||||
<hr>
|
||||
<b>ver. 0.253 - 12.02.2026</b><br />
|
||||
- UPDATE - modul `Users` w panelu admin w pelni przepiety na `Domain\\User\\UserRepository` + `admin\\Controllers\\UsersController`
|
||||
- UPDATE - migracja widokow users z `grid/gridEdit` na nowe komponenty (`components/table-list`, `components/form-edit`)
|
||||
- UPDATE - dodana walidacja warunkowa: przy wlaczonym 2FA pole `E-mail do 2FA` jest wymagane
|
||||
- UPDATE - globalne ulepszenia `components/table-list` (kompaktowe filtry select/status i odstepy w formularzu paginacji)
|
||||
- CLEANUP - usuniete legacy klasy users: `autoload/admin/controls/class.Users.php`, `autoload/admin/factory/class.Users.php`, `autoload/admin/view/class.Users.php`
|
||||
<hr>
|
||||
<b>ver. 0.252 - 10.02.2026</b><br />
|
||||
- UPDATE - migracja listy archiwum produktow do nowego komponentu tabeli (`components/table-list`) z filtrowaniem i paginacja
|
||||
- UPDATE - banery i archiwum produktow: wydzielenie CSS/JS do osobnych widokow `*-custom-script.php`
|
||||
- UPDATE - filemanager przepiety na nowy routing (`admin\\Controllers\\FilemanagerController`)
|
||||
- FIX - naprawiono blad `Invalid Key` w widoku filemanagera po refaktoryzacji
|
||||
- UPDATE - usunieto legacy klasy i stare szablony (`admin\\controls`, `admin\\view`, `admin/templates/product_archive`)
|
||||
<hr>
|
||||
<b>ver. 0.251 - 09.02.2026</b><br />
|
||||
- NEW - migracja modulu Dictionaries do nowej architektury (Domain + admin Controller + DI)
|
||||
- UPDATE - lista i formularz Dictionaries przepiete na nowe komponenty (`components/table-list`, `components/form-edit`)
|
||||
- UPDATE - dodano globalne ograniczenie szerokosci pierwszej kolumny (Lp.) w `components/table-list`
|
||||
- FIX - zapis tlumaczen jednostek obsluguje `lang_id` jako string (`pl`, `en`)
|
||||
- UPDATE - usunieto legacy klasy Dictionaries: `admin\\controls`, `admin\\factory`, `front\\factory`
|
||||
- UPDATE - przepieto uzycia na `Domain\\Dictionaries\\DictionariesRepository` (`shop-product`, `shop_product` admin)
|
||||
<hr>
|
||||
<b>ver. 0.250</b><br />
|
||||
- UPDATE - refaktoryzacja Settings: `Domain\\Settings\\SettingsRepository` ma bezposredni dostep do bazy (bez delegacji do `admin\\factory\\Settings`)
|
||||
- UPDATE - przepieto pozostale uzycia `admin\\factory\\Settings` na `Domain\\Settings\\SettingsRepository` (`admin\\controls\\Settings`, `admin\\controls\\Newsletter`, `front\\factory\\Newsletter`)
|
||||
- UPDATE - DI dla SettingsController: repozytorium otrzymuje `$mdb` w `admin\\Site`
|
||||
- UPDATE - Settings: widok edycji przeniesiony na nowy mechanizm formularza (`FormEditViewModel` + `components/form-edit`) jak w banerach
|
||||
- UPDATE - usunieto nieuzywana legacy klase `autoload/admin/factory/class.Settings.php`
|
||||
- UPDATE - usunieto legacy fallback kontrolera `autoload/admin/controls/class.Settings.php`
|
||||
- UPDATE - usunieto nieuzywana klase widoku `autoload/admin/view/class.Settings.php`
|
||||
<hr><b>ver. 0.249</b><br />
|
||||
- FIX - banner edit: poprawiono zapisywanie danych jezykowych i synchronizacje CKEditor przed zapisem
|
||||
- FIX - banner edit: naprawiono hash zakladek (usunieto `undefined` w URL)
|
||||
- FIX - filemanager: przywrocono dzialanie popupa wyboru obrazka z banera
|
||||
- UPDATE - komunikaty zapisu w nowym formularzu sa wyswietlane w stylu panelu (bez natywnego alertu JS)
|
||||
- UPDATE - lista banerow: dodano kolumne miniatury oraz podglad duzego obrazka w popup po najechaniu
|
||||
- UPDATE - usunieto nieuzywane legacy klasy banerow: `admin\\view\\Banners`, `admin\\factory\\Banners`
|
||||
<hr><b>ver. 0.248</b><br />
|
||||
- UPDATE - filtry w nowych tabelach dzialaja automatycznie na `onchange`
|
||||
- UPDATE - `components/table-list`: auto-submit formularza filtrow po zmianie pola (select, date, text)
|
||||
<hr><b>ver. 0.247</b><br />
|
||||
- UPDATE - nowy dialog potwierdzenia usuwania w `components/table-list` (zamiast natywnego `confirm`)
|
||||
- UPDATE - popup usuwania: wiekszy rozmiar i centrowanie na srodku ekranu
|
||||
<hr><b>ver. 0.246</b><br />
|
||||
- UPDATE - migracja listy banerow do nowego mechanizmu tabeli (`components/table-list`, filtrowanie, sortowanie, paginacja)
|
||||
- UPDATE - `admin\Controllers\BannerController::list()` buduje `PaginatedTableViewModel`
|
||||
- UPDATE - `Domain\Banner\BannerRepository::listForAdmin()` (bezpieczne filtrowanie i sortowanie)
|
||||
- UPDATE - usunieto legacy kontroler `autoload/admin/controls/class.Banners.php`
|
||||
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.246_files.txt`
|
||||
<hr><b>ver. 0.245</b><br />
|
||||
- UPDATE - refaktoryzacja listy artykulow: wspolny komponent `admin/templates/components/table-list.php` + `PaginatedTableViewModel`
|
||||
- NEW - `admin\Support\TableListRequestFactory` (wspolna obsluga filtrow, sortowania i paginacji dla list)
|
||||
- UPDATE - `Domain\Article\ArticleRepository::listForAdmin()` utwardzone pod katem bezpieczenstwa (whitelist sortowania, bind params, limit per_page)
|
||||
- UPDATE - usunieto legacy `browse_list` dla modulu Articles
|
||||
- UPDATE - usuniete pliki legacy sa wyszczegolnione w `updates/0.20/ver_0.245_files.txt`
|
||||
- FIX - generator `.htaccess` i `libraries/htaccess.conf` (QSA dla `/admin/...`, komentarz niedozwolonych dyrektyw `SetHandler/AddHandler/ForceType`)
|
||||
<hr><b>ver. 0.244</b><br />
|
||||
- UPDATE - refaktoryzacja: article_save przeniesiony do Domain\Article\ArticleRepository::save() z prywatnymi helperami
|
||||
- UPDATE - refaktoryzacja: article_delete przeniesiony do Domain\Article\ArticleRepository::archive()
|
||||
- UPDATE - ArticlesController: nowe akcje save() i delete() z DI
|
||||
- UPDATE - admin\factory\Articles::article_save() i articles_set_archive() delegują do repozytorium (kompatybilność)
|
||||
<hr><b>ver. 0.243</b><br />
|
||||
- UPDATE - refaktoryzacja: cleanup nieprzypisanych plików/zdjęć artykułów przeniesiony do Domain\Article\ArticleRepository
|
||||
- UPDATE - ArticlesController::edit() używa repozytorium do cleanupu, a admin\factory\Articles zachowuje delegowanie (kompatybilność)
|
||||
<hr><b>ver. 0.242</b><br />
|
||||
- NEW - refaktoryzacja: Domain\Article\ArticleRepository + migracja article_edit do admin\Controllers\ArticlesController (DI)
|
||||
- UPDATE - admin\factory\Articles::article_details() deleguje do nowego repozytorium (kompatybilność zachowana)
|
||||
- UPDATE - metody przejęte przez nowe kontrolery oznaczone jako @deprecated w legacy kontrolerach admin\controls
|
||||
<hr><b>ver. 0.241</b><br />
|
||||
- NEW - refaktoryzacja: admin\Controllers\ProductArchiveController - archiwum produktĂłw z DI
|
||||
- NEW - ProductRepository::archive(), unarchive() - operacje archiwizacji w repozytorium
|
||||
- FIX - naprawiono SQL w liście archiwum (puste wyszukiwanie filtrowało wszystkie wyniki)
|
||||
- FIX - naprawiono brakujÄ…cy filtr archive = 1 w zapytaniu bez wyszukiwania
|
||||
- UPDATE - wyczyszczono szablony archiwum (usunięto zbędne funkcje: apilo, baselinker, duplikowanie)
|
||||
<hr>
|
||||
<b>ver. 0.240</b><br />
|
||||
- NEW - refaktoryzacja: Domain\Settings\SettingsRepository + admin\Controllers\SettingsController (architektura Domain-Driven)
|
||||
- NEW - refaktoryzacja: Domain\Cache\CacheRepository - czyszczenie cache z obsługą Redis
|
||||
- FIX - komunikat potwierdzenia zapisu ustawień w panelu administratora
|
||||
- FIX - naprawiono element #content w layoucie admina (powiadomienia grid.js)
|
||||
<hr><b>ver. 0.239</b><br />
|
||||
- NEW - refaktoryzacja: Domain\Banner\BannerRepository + admin\Controllers\BannerController (pełna migracja kontrolera)
|
||||
- NEW - refaktoryzacja: Domain\Product\ProductRepository::getPrice(), getName() - migracja kolejnych metod
|
||||
- NEW - router admin z obsługą nowych kontrolerów (fallback na stare)
|
||||
- UPDATE - shop\Product::get_product_price(), get_product_name() używają nowego repozytorium (kompatybilność zachowana)
|
||||
<hr>
|
||||
<b>ver. 0.238</b><br />
|
||||
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven
|
||||
- NEW - Dependency Injection zamiast global variables
|
||||
- UPDATE - shop\Product::get_product_quantity() używa teraz nowego repozytorium (kompatybilność zachowana)
|
||||
<hr>
|
||||
<b>ver. 0.237</b><br />
|
||||
- NEW - automatyczne czyszczenie cache produktu po aktualizacji przez CRON (Sellasist, Apilo, Baselinker)
|
||||
- UPDATE - przycisk "Wyczyść cache" w panelu administratora z obsługą AJAX i komunikatami o postępie
|
||||
<hr>
|
||||
<b>ver. 0.236</b><br />
|
||||
- FIX - zabezpieczenie przed duplikatami zamówień w Apilo - automatyczne pobieranie ID zamówienia przy błędzie "idExternal już wykorzystywany"
|
||||
<hr>
|
||||
<b>ver. 0.235</b><br />
|
||||
- FIX - poprawka funkcji aktualizacji
|
||||
<hr>
|
||||
<b>ver. 0.234</b><br />
|
||||
- NEW - przycisk zaznaczania zamówienia jako wysłane do trustmate.io
|
||||
<hr>
|
||||
<b>ver. 0.232</b><br />
|
||||
- NEW - opcje GPSR
|
||||
<hr>
|
||||
<b>ver. 0.231</b><br />
|
||||
- FIX - poprawki bezpieczeństwa + dwuetapowa weryfikacja logowania
|
||||
<hr>
|
||||
<b>ver. 0.230</b><br />
|
||||
- FIX - poprawki bezpieczeństwa
|
||||
<hr>
|
||||
<b>ver. 0.229</b><br />
|
||||
- NEW - pola dodatkowe z opcjÄ… wymagane/niewymagane
|
||||
<hr>
|
||||
<b>ver. 0.228</b><br />
|
||||
- NEW - cron do wysyłania zamówień do trustmate.io
|
||||
<hr>
|
||||
<b>ver. 0.227</b><br />
|
||||
- NEW - historia kodĂłw rabatowych
|
||||
<hr>
|
||||
<b>ver. 0.226</b><br />
|
||||
- NEW - dodanie opcji faktury do zamĂłwienia
|
||||
<hr>
|
||||
<b>ver. 0.225</b><br />
|
||||
- NEW - przycisk czyszczenia cache<br>
|
||||
- NEW - ponowne wysyłanie zamówienia do apilo
|
||||
<hr>
|
||||
<b>ver. 0.224</b><br />
|
||||
- NEW - sortowanie form dostawy
|
||||
<hr>
|
||||
<b>ver. 0.223</b><br />
|
||||
- FIX - integracja z Orlen Paczka
|
||||
<hr>
|
||||
<b>ver. 0.222</b><br />
|
||||
- NEW - integracja z Orlen Paczka
|
||||
<hr>
|
||||
<b>ver. 0.221</b><br />
|
||||
- NEW - Automatyczne przekierowania adresĂłw URL produktĂłw, zmiany w pliku htaccess
|
||||
<hr>
|
||||
<b>ver. 0.220</b><br />
|
||||
- NEW - Dodanie możliwości wyświetlenia na strone ostatnio dodane produkty [PRODUKTY_NEW] lub [PRODUKTY_NEW:10].<br>
|
||||
- NEW - Dodanie możliwości wyświetlenia na strone popularnych produktów [PRODUKTY_TOP] lub [PRODUKTY_TOP:10].
|
||||
<hr>
|
||||
<b>ver. 0.219</b><br />
|
||||
- NEW - Dodanie możliwości zmiany daty w artykułach
|
||||
<hr>
|
||||
<b>ver. 0.218</b><br />
|
||||
- NEW - indywidualny kod GTM
|
||||
<hr>
|
||||
<b>ver. 0.217</b><br />
|
||||
- NEW - zwiększenie obsługi REDIS
|
||||
<hr>
|
||||
<b>ver. 0.216</b><br />
|
||||
- NEW - aktualizacja api i cron (apilo)
|
||||
<hr>
|
||||
<b>ver. 0.215</b><br />
|
||||
- FIX - generowanie pliku .htaccess
|
||||
<hr>
|
||||
<b>ver. 0.214</b><br />
|
||||
- NEW - dodanie API
|
||||
<hr>
|
||||
<b>ver. 0.213</b><br />
|
||||
- FIX - wyliczenie darmowej dostawy
|
||||
<hr>
|
||||
<b>ver. 0.212</b><br />
|
||||
- NEW - zmiany w zapisywaniu zamĂłwienia do apilo
|
||||
<hr>
|
||||
<b>ver. 0.211</b><br />
|
||||
- NEW - Debugowanie apilo + wyświetlanie podkategorii
|
||||
<hr>
|
||||
<b>ver. 0.210</b><br />
|
||||
- NEW - dodatkowe pola w widoku produktĂłw
|
||||
<hr>
|
||||
<b>ver. 0.209</b><br />
|
||||
- NEW - zmiany w widoku produktĂłw (panel administratora)
|
||||
<hr>
|
||||
<b>ver. 0.208</b><br />
|
||||
- NEW - zmiany w wyszukiwarce produktĂłw
|
||||
<hr>
|
||||
<b>ver. 0.204-0.207</b><br />
|
||||
- NEW - htaccess update
|
||||
<hr>
|
||||
<b>ver. 0.204-0.206</b><br />
|
||||
- NEW - wysyłanie produktów do apilo
|
||||
<hr>
|
||||
<b>ver. 0.203</b><br />
|
||||
- NEW - zmiana sposobu wyliczania cen produkty z dodatkami
|
||||
<hr>
|
||||
<b>ver. 0.202</b><br />
|
||||
- NEW - dodano "główne zdjęcie" w edycji artykułu
|
||||
<hr>
|
||||
<b>ver. 0.201</b><br />
|
||||
- FIX - aktualizacja statusĂłw na podstawie baselinkera
|
||||
<hr>
|
||||
<b>ver. 0.200</b><br />
|
||||
- NEW - wysyłanie produktów do baselinker
|
||||
<hr>
|
||||
<b>ver. 0.199</b><br />
|
||||
- NEW - usprawnienie edycji danych do XML
|
||||
<hr>
|
||||
<b>ver. 0.198</b><br />
|
||||
- NEW - automatyczne generowanie kodĂłw SKU
|
||||
<hr>
|
||||
<b>ver. 0.197</b><br />
|
||||
- FIX - poprawki w Dashboard
|
||||
<hr>
|
||||
<b>ver. 0.196</b><br />
|
||||
- FIX - integracja z apilo.com
|
||||
<hr>
|
||||
<b>ver. 0.195</b><br />
|
||||
- FIX - aktualizacja statusĂłw
|
||||
<hr>
|
||||
<b>ver. 0.194</b><br />
|
||||
- UPDATE - integracja apilo
|
||||
<hr>
|
||||
<b>ver. 0.193</b><br />
|
||||
- UPDATE - aktualizacja synchronizacji z baselinker
|
||||
<hr>
|
||||
<b>ver. 0.192</b><br />
|
||||
- NEW - pobieranie statusĂłw z sellasist
|
||||
<hr>
|
||||
<b>ver. 0.191</b><br />
|
||||
- NEW - integracja z selasist
|
||||
<hr>
|
||||
<b>ver. 0.190</b><br />
|
||||
- FIX - produkty powiÄ…zane
|
||||
<hr>
|
||||
<b>ver. 0.189</b><br />
|
||||
- FIX - ceny promocyjne produktĂłw z dodatkiem
|
||||
<hr>
|
||||
<b>ver. 0.188</b><br />
|
||||
- NEW - widok listy produktĂłw
|
||||
<hr>
|
||||
<b>ver. 0.187</b><br />
|
||||
- FIX - pobieranie cen z APILO
|
||||
<hr>
|
||||
<b>ver. 0.186</b><br />
|
||||
- FIX - dodawanie do koszyka tych samych produktów ale z różną personalizacją
|
||||
<hr>
|
||||
<b>ver. 0.185</b><br />
|
||||
- FIX - masowa edycja produktĂłw
|
||||
<hr>
|
||||
<b>ver. 0.184</b><br />
|
||||
- NEW - druga część integracji z apilo, masowa edycja produktów
|
||||
<hr>
|
||||
<b>ver. 0.183</b><br />
|
||||
- NEW - pierwsza część integracji z apilo
|
||||
<hr>
|
||||
<b>ver. 0.182</b><br />
|
||||
- FIX - layout
|
||||
<hr>
|
||||
<b>ver. 0.181</b><br />
|
||||
- NEW - infinitescroll - opcja włączy/wyłącz
|
||||
<hr>
|
||||
<b>ver. 0.180</b><br />
|
||||
- NEW - aktualizacja dashboard
|
||||
<hr>
|
||||
<b>ver. 0.179</b><br />
|
||||
- NEW - obsługa EAN
|
||||
<hr>
|
||||
<b>ver. 0.177, 0.178</b><br />
|
||||
- FIX - custom_label
|
||||
<hr>
|
||||
<b>ver. 0.176</b><br />
|
||||
- NEW - custom_label
|
||||
<hr>
|
||||
<b>ver. 0.175</b><br />
|
||||
- NEW - nowe statystyki
|
||||
<hr>
|
||||
<b>ver. 0.174</b><br />
|
||||
- FIX - generowanie xml
|
||||
<hr>
|
||||
<b>ver. 0.173</b><br />
|
||||
- NEW - duplikowanie produktu wraz z kombinacjami
|
||||
- NEW - dodanie przechodzenia pomiędzy zamówienia (poprzednie/następne zamówienie)
|
||||
<hr>
|
||||
<b>ver. 0.172</b><br />
|
||||
- FIX - poprawki w Cache
|
||||
<hr>
|
||||
<b>ver. 0.171</b><br />
|
||||
- FIX - poprawki w Cache
|
||||
<hr>
|
||||
<b>ver. 0.170</b><br />
|
||||
- NEW - usuwanie cache produktu przy zapisie
|
||||
<hr>
|
||||
<b>ver. 0.169</b><br />
|
||||
- FIX - poprawki w liście produktów
|
||||
<hr>
|
||||
<b>ver. 0.168</b><br />
|
||||
- NEW - archiwum produktĂłw
|
||||
<hr>
|
||||
<b>ver. 0.167</b><br />
|
||||
- NEW - dodanie obsługi cen i stanów magazynowych kombinacji produktów
|
||||
<hr>
|
||||
<b>ver. 0.166</b><br />
|
||||
- NEW - współpraca z GTM
|
||||
<hr>
|
||||
<b>ver. 0.164/5</b><br />
|
||||
- FIX - ukrywanie produktĂłw nieaktywnych
|
||||
<hr>
|
||||
<b>ver. 0.163</b><br />
|
||||
- NEW - automatyczne podpowiadanie produktów do zestawu na podstawie wcześniejszych zakupów klientów
|
||||
<hr>
|
||||
<b>ver. 0.162</b><br />
|
||||
- NEW - GA4
|
||||
<hr>
|
||||
<b>ver. 0.161</b><br />
|
||||
- UPDATE - aktualizacja menu administratora
|
||||
<hr>
|
||||
<b>ver. 0.160</b><br />
|
||||
- UPDATE - aktualizacja cron Baselinker
|
||||
- NEW - waga i cena jednostkowa produktu
|
||||
<hr>
|
||||
<b>ver. 0.159</b><br />
|
||||
- FIX - cron Baselinker
|
||||
<hr>
|
||||
<b>ver. 0.158</b><br />
|
||||
- UPDATE - poprawa kolorystyki przyciskĂłw
|
||||
<hr>
|
||||
<b>ver. 0.157</b><br />
|
||||
- NEW - szybka zmiana statusu produktu
|
||||
<hr>
|
||||
<b>ver. 0.156</b><br />
|
||||
- NEW - dodanie szybkiej edycji google xml label
|
||||
<hr>
|
||||
<b>ver. 0.155</b><br />
|
||||
- NEW - infinite scroll w widoku kategorii
|
||||
<hr>
|
||||
<b>ver. 0.154</b><br />
|
||||
- FIX - atrybuty produktĂłw
|
||||
<hr>
|
||||
<b>ver. 0.153</b><br />
|
||||
- FIX - atrybuty produktĂłw
|
||||
<hr>
|
||||
<b>ver. 0.152</b><br />
|
||||
- FIX - tematy maili
|
||||
<hr>
|
||||
<b>ver. 0.151</b><br />
|
||||
- FIX - tematy maili
|
||||
<hr>
|
||||
<b>ver. 0.150</b><br />
|
||||
- NEW - domyślna forma transportu
|
||||
<hr>
|
||||
<b>ver. 0.149</b><br />
|
||||
- NEW - tematy maili
|
||||
<hr>
|
||||
<b>ver. 0.148</b><br />
|
||||
- FIX - cron-xml
|
||||
<hr>
|
||||
<b>ver. 0.147</b><br />
|
||||
- FIX - cron-xml
|
||||
<hr>
|
||||
<b>ver. 0.146</b><br />
|
||||
- NEW - cron-xml
|
||||
<hr>
|
||||
<b>ver. 0.145</b><br />
|
||||
- NEW - omnibus ready
|
||||
<hr>
|
||||
<b>ver. 0.144</b><br />
|
||||
- FIX - usunięcie adresu marianek.pl z kodu
|
||||
<hr>
|
||||
<b>ver. 0.143</b><br />
|
||||
- FIX - poprawa generowania plikĂłw WEBP
|
||||
<hr>
|
||||
<b>ver. 0.142</b><br />
|
||||
- FIX - poprawa adresu strony głównej
|
||||
<b>ver. 0.314 - 23.02.2026</b><br />
|
||||
FIX - naprawa globalnej wyszukiwarki admin (Content-Type, Cache-Control, POST, try/catch), NEW - title strony z numerem zamówienia
|
||||
<hr>
|
||||
<b>ver. 0.313 - 23.02.2026</b><br />
|
||||
FIX - sync płatności Apilo (int cast na apilo_order_id PPxxxxxx dawał 0) + logowanie decyzji sync do pp_log
|
||||
<hr>
|
||||
<b>ver. 0.312 - 23.02.2026</b><br />
|
||||
FIX - krytyczne bugi integracji Apilo: curl_getinfo po curl_close, nieskończona pętla wysyłki, ceny 0.00 PLN, walidacja cen
|
||||
<hr>
|
||||
<b>ver. 0.311 - 23.02.2026</b><br />
|
||||
FIX - race condition callback płatności Apilo, persistence filtrów tabel admin, poprawki cen zamówień
|
||||
<hr>
|
||||
<b>ver. 0.310 - 23.02.2026</b><br />
|
||||
NEW - Zakladka Logi w sekcji Integracje (podglad pp_log z paginacja, sortowaniem, filtrami)
|
||||
<hr>
|
||||
<b>ver. 0.309 - 23.02.2026</b><br />
|
||||
NEW - ApiloLogger (logowanie operacji Apilo do pp_log), cache-busting CSS/JS w admin panelu, poprawki UI listy produktow, clipboard API
|
||||
<hr>
|
||||
<b>ver. 0.308 - 22.02.2026</b><br />
|
||||
NEW - kolorowe badge statusow zamowien, walidacja hex, sanityzacja HTML transport, optymalizacja SQL
|
||||
<hr>
|
||||
<b>ver. 0.308 - 22.02.2026</b><br />
|
||||
NEW - kolorowe badge statusow zamowien, walidacja hex, sanityzacja HTML transport, optymalizacja SQL
|
||||
<hr>
|
||||
<?php
|
||||
// Auto-generated changelog from manifest files + legacy entries.
|
||||
// Scans manifest JSON files, merges with changelog-legacy.json, sorts descending, outputs HTML.
|
||||
|
||||
$entries = [];
|
||||
|
||||
// 1. Scan manifest files
|
||||
$manifests = glob( __DIR__ . '/0.*/ver_*_manifest.json' );
|
||||
if ( $manifests ) {
|
||||
foreach ( $manifests as $file ) {
|
||||
$json = @file_get_contents( $file );
|
||||
if ( !$json ) continue;
|
||||
// Strip UTF-8 BOM if present
|
||||
if ( substr( $json, 0, 3 ) === "\xEF\xBB\xBF" ) {
|
||||
$json = substr( $json, 3 );
|
||||
}
|
||||
$data = @json_decode( $json, true );
|
||||
if ( !$data || empty( $data['version'] ) || empty( $data['changelog'] ) ) continue;
|
||||
|
||||
$date = isset( $data['date'] ) ? $data['date'] : '';
|
||||
// Convert YYYY-MM-DD to DD.MM.YYYY
|
||||
if ( $date && preg_match( '/^(\d{4})-(\d{2})-(\d{2})$/', $date, $m ) ) {
|
||||
$date = $m[3] . '.' . $m[2] . '.' . $m[1];
|
||||
}
|
||||
|
||||
$entries[] = [
|
||||
'version' => (float) $data['version'],
|
||||
'ver_str' => $data['version'],
|
||||
'date' => $date,
|
||||
'text' => $data['changelog'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Load legacy entries
|
||||
$legacyFile = __DIR__ . '/changelog-legacy.json';
|
||||
if ( file_exists( $legacyFile ) ) {
|
||||
$legacyJson = @file_get_contents( $legacyFile );
|
||||
if ( $legacyJson ) {
|
||||
$legacy = @json_decode( $legacyJson, true );
|
||||
if ( is_array( $legacy ) ) {
|
||||
foreach ( $legacy as $item ) {
|
||||
if ( empty( $item['version'] ) ) continue;
|
||||
$entries[] = [
|
||||
'version' => (float) $item['version'],
|
||||
'ver_str' => $item['version'],
|
||||
'date' => isset( $item['date'] ) ? $item['date'] : '',
|
||||
'text' => isset( $item['text'] ) ? $item['text'] : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Sort descending by version
|
||||
usort( $entries, function ( $a, $b ) {
|
||||
if ( $a['version'] == $b['version'] ) return 0;
|
||||
return ( $a['version'] > $b['version'] ) ? -1 : 1;
|
||||
} );
|
||||
|
||||
// 4. Output HTML
|
||||
foreach ( $entries as $entry ) {
|
||||
$header = 'ver. ' . htmlspecialchars( $entry['ver_str'] );
|
||||
if ( $entry['date'] ) {
|
||||
$header .= ' - ' . htmlspecialchars( $entry['date'] );
|
||||
}
|
||||
$text = nl2br( htmlspecialchars( $entry['text'] ) );
|
||||
|
||||
echo '<b>' . $header . '</b><br />' . "\n";
|
||||
echo $text . "\n";
|
||||
echo '<hr>' . "\n";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 306;
|
||||
<?
|
||||
$current_ver = 315;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user