Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2b2629d49 | |||
| b409806f02 |
41
.updateignore
Normal file
41
.updateignore
Normal file
@@ -0,0 +1,41 @@
|
||||
# Dokumentacja (tylko wewnetrzna/deweloperska)
|
||||
*.md
|
||||
docs/
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
|
||||
# Narzedzia deweloperskie
|
||||
.claude/
|
||||
.gitignore
|
||||
.git/
|
||||
tests/
|
||||
phpunit.xml
|
||||
phpunit.phar
|
||||
composer.json
|
||||
composer.lock
|
||||
vendor/
|
||||
test.ps1
|
||||
memory/
|
||||
|
||||
# Infrastruktura aktualizacji (meta, nie runtime)
|
||||
updates/changelog.php
|
||||
updates/versions.php
|
||||
updates/install.php
|
||||
.updateignore
|
||||
build-update.ps1
|
||||
migrations/
|
||||
|
||||
# Pliki konfiguracyjne klienta (wdrazane osobno)
|
||||
config.php
|
||||
.htaccess
|
||||
admin/.htaccess
|
||||
libraries/version.ini
|
||||
|
||||
# Temp / cache / backups
|
||||
temp/
|
||||
backups/
|
||||
cache/
|
||||
cron/temp/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
2
TODO.md
2
TODO.md
@@ -1,2 +0,0 @@
|
||||
1. Operacje na kombinacjach nie czyszczą cache
|
||||
2. Sprawdzić funkcję pod kątem SQL INJECTION
|
||||
229
admin/layout/style-css/order-details-mobile.css
Normal file
229
admin/layout/style-css/order-details-mobile.css
Normal file
@@ -0,0 +1,229 @@
|
||||
/* ==========================================================================
|
||||
Order Details — Mobile Responsive
|
||||
Scoped to .od-actions and .order-details
|
||||
All rules inside @media (max-width: 767px) — desktop layout untouched
|
||||
========================================================================== */
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
/* --- 1. Action buttons bar --- */
|
||||
|
||||
.od-actions {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.od-actions .btn {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
padding: 6px 10px;
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
/* Hide button labels on small screens — show only icons */
|
||||
.od-actions .od-actions-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Integrations dropdown — push to end, cancel float */
|
||||
.od-actions .pull-right,
|
||||
.od-actions .dropdown.pull-right {
|
||||
float: none !important;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Dropdown menu must not clip inside scroll container */
|
||||
.od-actions .dropdown {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.od-actions + .order-details .dropdown-menu,
|
||||
#integrationsDropdownMenu.show {
|
||||
position: fixed;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border-radius: 12px 12px 0 0;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
|
||||
z-index: 1060;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#integrationsDropdownMenu.show .dropdown-item {
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* --- 2. Full-width columns stacking --- */
|
||||
|
||||
.order-details .col-sm-6,
|
||||
.order-details .col-lg-8,
|
||||
.order-details .col-lg-4,
|
||||
.order-details .col-xl-5,
|
||||
.order-details .col-xl-7 {
|
||||
width: 100%;
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Spacing between stacked sections */
|
||||
.order-details .col-sm-6 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* "Resend confirmation" button — allow text to wrap */
|
||||
.order-details .resend_order_confirmation_email .btn {
|
||||
font-size: 12px;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* --- 3. Status section --- */
|
||||
|
||||
.order-details .status_select #order-status {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.order-details .buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.order-details .buttons .btn {
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.order-details .order-status {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.order-details .order-history {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* --- 4. Products table — compact list --- */
|
||||
|
||||
.order-details table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.order-details table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.order-details table tbody {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.order-details table tr.order-product-details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.order-details table tr.order-product-details:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Image cell */
|
||||
.order-details table td.product-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin-right: 10px;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.order-details table td.product-image img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Name cell — takes remaining space next to image */
|
||||
.order-details table tr.order-product-details td:nth-child(2) {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.order-details table tr.order-product-details td:nth-child(2) a {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Hide quantity, price, discounted-price, total columns on mobile */
|
||||
.order-details table tr.order-product-details td.tab-center,
|
||||
.order-details table tr.order-product-details td.tab-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show mobile price summary line */
|
||||
.od-mobile-price-line {
|
||||
display: block !important;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #2a3042;
|
||||
}
|
||||
|
||||
/* --- 5. Notes --- */
|
||||
|
||||
.order-details #notes {
|
||||
font-size: 14px;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* --- 6. Order summary panel --- */
|
||||
|
||||
.order-details .panel .panel-body {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* --- 7. Paid status --- */
|
||||
|
||||
.order-details .paid-status .panel-body a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* --- 8. Message section — hide empty spacer columns --- */
|
||||
|
||||
.order-details .d-none.d-lg-block {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Desktop: action bar spacing (replaces mr5 class) --- */
|
||||
.od-actions .btn + .btn,
|
||||
.od-actions .btn + .dropdown {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Mobile price line — hidden on desktop */
|
||||
.od-mobile-price-line {
|
||||
display: none;
|
||||
}
|
||||
@@ -57,6 +57,36 @@
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* --- Filter toggle --- */
|
||||
|
||||
.table-filters-wrapper {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-filters-wrapper.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.js-filter-toggle-btn.active {
|
||||
background-color: #e8e8e8;
|
||||
border-color: #adadad;
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.table-filter-badge {
|
||||
display: inline-block;
|
||||
min-width: 16px;
|
||||
padding: 2px 5px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
background-color: #337ab7;
|
||||
border-radius: 10px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
/* --- Column visibility toggle --- */
|
||||
|
||||
.table-list-header-actions {
|
||||
|
||||
@@ -19,6 +19,14 @@ $totalPages = max(1, (int)($list->pagination['total_pages'] ?? 1));
|
||||
$total = (int)($list->pagination['total'] ?? 0);
|
||||
$perPage = (int)($list->pagination['per_page'] ?? 15);
|
||||
|
||||
$hasActiveFilters = false;
|
||||
foreach ($list->filters as $filter) {
|
||||
if (isset($filter['value']) && (string)$filter['value'] !== '') {
|
||||
$hasActiveFilters = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$isCompactColumn = function(array $column): bool {
|
||||
$key = strtolower(trim((string)($column['key'] ?? '')));
|
||||
$label = strtolower(trim((string)($column['label'] ?? '')));
|
||||
@@ -48,6 +56,14 @@ $isCompactColumn = function(array $column): bool {
|
||||
<div class="col-sm-4 text-right">
|
||||
<div class="table-list-header-actions">
|
||||
<span class="text-muted">Wyników: <?= $total; ?></span>
|
||||
<?php if (!empty($list->filters)): ?>
|
||||
<button type="button" class="btn btn-default btn-sm js-filter-toggle-btn<?= $hasActiveFilters ? ' active' : ''; ?>" title="Filtry">
|
||||
<i class="fa fa-filter"></i>
|
||||
<?php if ($hasActiveFilters): ?>
|
||||
<span class="badge badge-primary table-filter-badge"><?= count(array_filter($list->filters, function($f) { return isset($f['value']) && (string)$f['value'] !== ''; })); ?></span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<div class="table-col-toggle-wrapper">
|
||||
<button type="button" class="btn btn-default btn-sm js-col-toggle-btn" title="Widoczność kolumn">
|
||||
<i class="fa fa-columns"></i>
|
||||
@@ -75,6 +91,7 @@ $isCompactColumn = function(array $column): bool {
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<?php foreach ($list->filters as $filter): ?>
|
||||
<?php
|
||||
@@ -148,6 +165,7 @@ $isCompactColumn = function(array $column): bool {
|
||||
<a href="<?= htmlspecialchars($list->basePath, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-default btn-sm">Wyczyść</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-striped table-bordered mbn table-list-table">
|
||||
@@ -469,5 +487,47 @@ $isCompactColumn = function(array $column): bool {
|
||||
saveHiddenCols([]);
|
||||
applyColumnVisibility([]);
|
||||
});
|
||||
|
||||
// --- Filter toggle ---
|
||||
var filterStorageKey = 'tableListFilters_' + <?= json_encode($list->basePath); ?>;
|
||||
|
||||
function isFilterVisible() {
|
||||
try {
|
||||
return localStorage.getItem(filterStorageKey) === '1';
|
||||
} catch (e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
function saveFilterState(visible) {
|
||||
try {
|
||||
localStorage.setItem(filterStorageKey, visible ? '1' : '0');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
var $filterWrapper = $('.js-table-filters-wrapper');
|
||||
var $filterBtn = $('.js-filter-toggle-btn');
|
||||
var hasActiveFilters = $filterWrapper.hasClass('open');
|
||||
|
||||
if (!hasActiveFilters && isFilterVisible()) {
|
||||
$filterWrapper.addClass('open');
|
||||
$filterBtn.addClass('active');
|
||||
}
|
||||
|
||||
$(document).off('click.filterToggle', '.js-filter-toggle-btn');
|
||||
$(document).on('click.filterToggle', '.js-filter-toggle-btn', function() {
|
||||
var $wrapper = $('.js-table-filters-wrapper');
|
||||
var $btn = $(this);
|
||||
var isOpen = $wrapper.hasClass('open');
|
||||
|
||||
if (isOpen) {
|
||||
$wrapper.removeClass('open');
|
||||
$btn.removeClass('active');
|
||||
saveFilterState(false);
|
||||
} else {
|
||||
$wrapper.addClass('open');
|
||||
$btn.addClass('active');
|
||||
saveFilterState(true);
|
||||
}
|
||||
});
|
||||
})(window.jQuery);
|
||||
</script>
|
||||
|
||||
@@ -4,23 +4,23 @@ $orderId = (int)($this -> order['id'] ?? 0);
|
||||
|
||||
<div class="site-title">Szczegóły zamówienia: <?= htmlspecialchars((string)($this -> order['number'] ?? ''), ENT_QUOTES, 'UTF-8');?></div>
|
||||
|
||||
<div class="mb15">
|
||||
<a href="/admin/shop_order/list/" class="btn btn-dark btn-sm mr5">
|
||||
<i class="fa fa-reply"></i> Wstecz
|
||||
<div class="od-actions mb15">
|
||||
<a href="/admin/shop_order/list/" class="btn btn-dark btn-sm">
|
||||
<i class="fa fa-reply"></i> <span class="od-actions-label">Wstecz</span>
|
||||
</a>
|
||||
<a href="/admin/shop_order/order_edit/order_id=<?= $orderId;?>" class="btn btn-danger btn-sm mr5">
|
||||
<i class="fa fa-pencil"></i> Edytuj zamówienie
|
||||
<a href="/admin/shop_order/order_edit/order_id=<?= $orderId;?>" class="btn btn-danger btn-sm">
|
||||
<i class="fa fa-pencil"></i> <span class="od-actions-label">Edytuj</span>
|
||||
</a>
|
||||
|
||||
<? if ( $this -> prev_order_id ):?>
|
||||
<a href="/admin/shop_order/order_details/order_id=<?= (int)$this -> prev_order_id;?>" class="btn btn-success btn-sm mr5">
|
||||
<i class="fa fa-arrow-left"></i> Poprzednie zamówienie
|
||||
<a href="/admin/shop_order/order_details/order_id=<?= (int)$this -> prev_order_id;?>" class="btn btn-success btn-sm">
|
||||
<i class="fa fa-arrow-left"></i> <span class="od-actions-label">Poprzednie</span>
|
||||
</a>
|
||||
<? endif;?>
|
||||
|
||||
<? if ( $this -> next_order_id ):?>
|
||||
<a href="/admin/shop_order/order_details/order_id=<?= (int)$this -> next_order_id;?>" class="btn btn-success btn-sm mr5">
|
||||
<i class="fa fa-arrow-right"></i> Następne zamówienie
|
||||
<a href="/admin/shop_order/order_details/order_id=<?= (int)$this -> next_order_id;?>" class="btn btn-success btn-sm">
|
||||
<i class="fa fa-arrow-right"></i> <span class="od-actions-label">Następne</span>
|
||||
</a>
|
||||
<? endif;?>
|
||||
|
||||
@@ -183,6 +183,9 @@ $orderId = (int)($this -> order['id'] ?? 0);
|
||||
<div class="product-message">
|
||||
<?= $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ł
|
||||
</div>
|
||||
</td>
|
||||
<td class="tab-center"><?= $product[ 'quantity' ];?></td>
|
||||
<td class="tab-right"><?= \Shared\Helpers\Helpers::decimal( $product[ 'price_brutto' ] );?> zł</td>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<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" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-page">
|
||||
|
||||
@@ -41,6 +41,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<? if ( !empty( $this->log ) ): ?>
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">Log ostatniej aktualizacji</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<pre style="max-height: 400px; overflow-y: auto; font-size: 12px;"><?= htmlspecialchars( $this->log ); ?></pre>
|
||||
</div>
|
||||
</div>
|
||||
<? endif; ?>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">Changelog</span>
|
||||
|
||||
@@ -61,10 +61,284 @@ class UpdateRepository
|
||||
return [ 'success' => true, 'log' => $log, 'no_updates' => true ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatcher — próbuje pobrać manifest, jeśli jest → nowa ścieżka, jeśli brak → legacy.
|
||||
*/
|
||||
private function downloadAndApply( string $ver, string $dir, array $log ): array
|
||||
{
|
||||
$baseUrl = 'https://shoppro.project-dc.pl/updates/' . $dir;
|
||||
|
||||
$manifest = $this->downloadManifest( $baseUrl, $ver );
|
||||
|
||||
if ( $manifest !== null ) {
|
||||
$log[] = '[INFO] Znaleziono manifest dla wersji ' . $ver;
|
||||
return $this->downloadAndApplyWithManifest( $ver, $dir, $manifest, $log );
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Brak manifestu, używam trybu legacy';
|
||||
return $this->downloadAndApplyLegacy( $ver, $dir, $log );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera manifest JSON dla danej wersji.
|
||||
*
|
||||
* @return array|null Zdekodowany manifest lub null jeśli brak
|
||||
*/
|
||||
private function downloadManifest( string $baseUrl, string $ver )
|
||||
{
|
||||
$manifestUrl = $baseUrl . '/ver_' . $ver . '_manifest.json';
|
||||
|
||||
$ch = curl_init( $manifestUrl );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||
curl_setopt( $ch, CURLOPT_TIMEOUT, 15 );
|
||||
$response = curl_exec( $ch );
|
||||
$httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
curl_close( $ch );
|
||||
|
||||
if ( !$response || $httpCode !== 200 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$manifest = json_decode( $response, true );
|
||||
if ( !is_array( $manifest ) || !isset( $manifest['version'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualizacja z użyciem manifestu — checksum, backup, SQL z manifestu, usuwanie z manifestu.
|
||||
*/
|
||||
private function downloadAndApplyWithManifest( string $ver, string $dir, array $manifest, array $log ): array
|
||||
{
|
||||
$baseUrl = 'https://shoppro.project-dc.pl/updates/' . $dir;
|
||||
|
||||
$log[] = '[INFO] Tryb aktualizacji: manifest';
|
||||
|
||||
// 1. Pobieranie ZIP
|
||||
$zipUrl = $baseUrl . '/ver_' . $ver . '.zip';
|
||||
$log[] = '[INFO] Pobieranie pliku ZIP: ' . $zipUrl;
|
||||
$file = @file_get_contents( $zipUrl );
|
||||
|
||||
if ( $file === false ) {
|
||||
$log[] = '[ERROR] Nie udało się pobrać pliku ZIP';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$fileSize = strlen( $file );
|
||||
$log[] = '[OK] Pobrano plik ZIP, rozmiar: ' . $fileSize . ' bajtów';
|
||||
|
||||
if ( $fileSize < 100 ) {
|
||||
$log[] = '[ERROR] Plik ZIP jest za mały (prawdopodobnie błąd pobierania)';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$dlHandler = @fopen( 'update.zip', 'w' );
|
||||
if ( !$dlHandler ) {
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku update.zip do zapisu';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
$written = fwrite( $dlHandler, $file );
|
||||
fclose( $dlHandler );
|
||||
|
||||
if ( $written === false || $written === 0 ) {
|
||||
$log[] = '[ERROR] Nie udało się zapisać pliku ZIP';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Zapisano plik ZIP (' . $written . ' bajtów)';
|
||||
|
||||
// 2. Weryfikacja checksum
|
||||
if ( isset( $manifest['checksum_zip'] ) ) {
|
||||
$checksumResult = $this->verifyChecksum( 'update.zip', $manifest['checksum_zip'], $log );
|
||||
$log = $checksumResult['log'];
|
||||
|
||||
if ( !$checksumResult['valid'] ) {
|
||||
@unlink( 'update.zip' );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Backup plików przed nadpisaniem
|
||||
$log = $this->createBackup( $manifest, $log );
|
||||
|
||||
// 4. SQL z manifestu
|
||||
if ( !empty( $manifest['sql'] ) ) {
|
||||
$log[] = '[INFO] Wykonywanie zapytań SQL z manifestu (' . count( $manifest['sql'] ) . ')';
|
||||
$success = 0;
|
||||
$errors = 0;
|
||||
|
||||
foreach ( $manifest['sql'] as $query ) {
|
||||
$query = trim( $query );
|
||||
if ( $query !== '' ) {
|
||||
if ( $this->db->query( $query ) ) {
|
||||
$success++;
|
||||
} else {
|
||||
$errors++;
|
||||
$log[] = '[WARNING] Błąd SQL: ' . $query;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Wykonano zapytania SQL - sukces: ' . $success . ', błędy: ' . $errors;
|
||||
}
|
||||
|
||||
// 5. Usuwanie plików z manifestu
|
||||
if ( !empty( $manifest['files']['deleted'] ) ) {
|
||||
$deletedCount = 0;
|
||||
foreach ( $manifest['files']['deleted'] as $relativePath ) {
|
||||
$fullPath = '../' . $relativePath;
|
||||
if ( file_exists( $fullPath ) ) {
|
||||
if ( @unlink( $fullPath ) ) {
|
||||
$deletedCount++;
|
||||
} else {
|
||||
$log[] = '[WARNING] Nie udało się usunąć pliku: ' . $fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
$log[] = '[INFO] Usunięto plików: ' . $deletedCount;
|
||||
}
|
||||
|
||||
// 6. Usuwanie katalogów z manifestu
|
||||
if ( !empty( $manifest['directories_deleted'] ) ) {
|
||||
$deletedDirs = 0;
|
||||
foreach ( $manifest['directories_deleted'] as $dirPath ) {
|
||||
$fullPath = '../' . $dirPath;
|
||||
if ( is_dir( $fullPath ) ) {
|
||||
\Shared\Helpers\Helpers::delete_dir( $fullPath );
|
||||
$deletedDirs++;
|
||||
}
|
||||
}
|
||||
$log[] = '[INFO] Usunięto katalogów: ' . $deletedDirs;
|
||||
}
|
||||
|
||||
// 7. Rozpakowywanie ZIP
|
||||
$log = $this->extractZip( 'update.zip', $log );
|
||||
|
||||
// 8. Aktualizacja wersji
|
||||
$versionFile = '../libraries/version.ini';
|
||||
$handle = @fopen( $versionFile, 'w' );
|
||||
if ( !$handle ) {
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku version.ini do zapisu';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
fwrite( $handle, $ver );
|
||||
fclose( $handle );
|
||||
|
||||
$log[] = '[OK] Zaktualizowano plik version.ini do wersji: ' . $ver;
|
||||
$log[] = '[SUCCESS] Aktualizacja do wersji ' . $ver . ' zakończona pomyślnie';
|
||||
|
||||
return [ 'success' => true, 'log' => $log ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Weryfikuje sumę kontrolną pliku.
|
||||
*
|
||||
* @param string $filePath Ścieżka do pliku
|
||||
* @param string $expectedChecksum Suma w formacie "sha256:abc123..."
|
||||
* @param array $log Tablica logów
|
||||
* @return array{valid: bool, log: array}
|
||||
*/
|
||||
private function verifyChecksum( string $filePath, string $expectedChecksum, array $log ): array
|
||||
{
|
||||
$parts = explode( ':', $expectedChecksum, 2 );
|
||||
if ( count( $parts ) !== 2 ) {
|
||||
$log[] = '[ERROR] Nieprawidłowy format sumy kontrolnej: ' . $expectedChecksum;
|
||||
return [ 'valid' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$algorithm = $parts[0];
|
||||
$expected = $parts[1];
|
||||
|
||||
$actual = @hash_file( $algorithm, $filePath );
|
||||
|
||||
if ( $actual === false ) {
|
||||
$log[] = '[ERROR] Nie udało się obliczyć sumy kontrolnej pliku';
|
||||
return [ 'valid' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
if ( $actual !== $expected ) {
|
||||
$log[] = '[ERROR] Suma kontrolna nie zgadza się! Oczekiwano: ' . $expected . ', otrzymano: ' . $actual;
|
||||
return [ 'valid' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Suma kontrolna ZIP zgodna';
|
||||
return [ 'valid' => true, 'log' => $log ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy kopię zapasową plików przed aktualizacją.
|
||||
*
|
||||
* @param array $manifest Dane z manifestu
|
||||
* @param array $log Tablica logów
|
||||
* @return array Zaktualizowana tablica logów
|
||||
*/
|
||||
private function createBackup( array $manifest, array $log ): array
|
||||
{
|
||||
$version = isset( $manifest['version'] ) ? $manifest['version'] : 'unknown';
|
||||
$backupDir = '../backups/' . str_replace( '.', '_', $version ) . '_' . date( 'Ymd_His' );
|
||||
|
||||
$log[] = '[INFO] Tworzenie kopii zapasowej w: ' . $backupDir;
|
||||
|
||||
$projectRoot = realpath( '../' );
|
||||
if ( !$projectRoot ) {
|
||||
$log[] = '[WARNING] Nie udało się określić katalogu projektu, pomijam backup';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$filesToBackup = [];
|
||||
if ( isset( $manifest['files']['modified'] ) && is_array( $manifest['files']['modified'] ) ) {
|
||||
$filesToBackup = array_merge( $filesToBackup, $manifest['files']['modified'] );
|
||||
}
|
||||
if ( isset( $manifest['files']['deleted'] ) && is_array( $manifest['files']['deleted'] ) ) {
|
||||
$filesToBackup = array_merge( $filesToBackup, $manifest['files']['deleted'] );
|
||||
}
|
||||
|
||||
if ( empty( $filesToBackup ) ) {
|
||||
$log[] = '[INFO] Brak plików do backupu';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$backedUp = 0;
|
||||
foreach ( $filesToBackup as $relativePath ) {
|
||||
$sourcePath = $projectRoot . '/' . $relativePath;
|
||||
if ( !file_exists( $sourcePath ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetPath = $backupDir . '/' . $relativePath;
|
||||
$targetDir = dirname( $targetPath );
|
||||
|
||||
if ( !is_dir( $targetDir ) ) {
|
||||
@mkdir( $targetDir, 0755, true );
|
||||
}
|
||||
|
||||
if ( @copy( $sourcePath, $targetPath ) ) {
|
||||
$backedUp++;
|
||||
} else {
|
||||
$log[] = '[WARNING] Nie udało się skopiować do backupu: ' . $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[OK] Backup: skopiowano ' . $backedUp . ' plików';
|
||||
|
||||
@file_put_contents(
|
||||
$backupDir . '/manifest.json',
|
||||
json_encode( $manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE )
|
||||
);
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy — stary format aktualizacji (ZIP + _sql.txt + _files.txt).
|
||||
*/
|
||||
private function downloadAndApplyLegacy( string $ver, string $dir, array $log ): array
|
||||
{
|
||||
$baseUrl = 'https://shoppro.project-dc.pl/updates/' . $dir;
|
||||
|
||||
// Pobieranie ZIP
|
||||
$zipUrl = $baseUrl . '/ver_' . $ver . '.zip';
|
||||
$log[] = '[INFO] Pobieranie pliku ZIP: ' . $zipUrl;
|
||||
|
||||
@@ -14,9 +14,12 @@ class UpdateController
|
||||
|
||||
public function main_view(): string
|
||||
{
|
||||
$logContent = @file_get_contents( '../libraries/update_log.txt' );
|
||||
|
||||
return \Shared\Tpl\Tpl::view( 'update/main-view', [
|
||||
'ver' => \Shared\Helpers\Helpers::get_version(),
|
||||
'new_ver' => \Shared\Helpers\Helpers::get_new_version(),
|
||||
'log' => $logContent ?: '',
|
||||
] );
|
||||
}
|
||||
|
||||
|
||||
388
build-update.ps1
Normal file
388
build-update.ps1
Normal file
@@ -0,0 +1,388 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Automatyczne budowanie paczki aktualizacji shopPRO na podstawie git diff miedzy tagami.
|
||||
|
||||
.DESCRIPTION
|
||||
Skrypt porownuje dwa tagi git, filtruje pliki przez .updateignore,
|
||||
tworzy ZIP + manifest JSON i aktualizuje changelog.php oraz versions.php.
|
||||
|
||||
.PARAMETER FromTag
|
||||
Tag poczatkowy (np. v0.299). Domyslnie: ostatni tag.
|
||||
|
||||
.PARAMETER ToTag
|
||||
Tag docelowy (np. v0.300). Wymagany.
|
||||
|
||||
.PARAMETER ChangelogEntry
|
||||
Wpis do changelogu. Wymagany (chyba ze -DryRun).
|
||||
|
||||
.PARAMETER DryRun
|
||||
Tylko pokaz co zostaloby zrobione, bez tworzenia plikow.
|
||||
|
||||
.EXAMPLE
|
||||
./build-update.ps1 -ToTag v0.300 -ChangelogEntry "NEW - Manifest-based update system"
|
||||
./build-update.ps1 -FromTag v0.299 -ToTag v0.300 -ChangelogEntry "NEW - opis" -DryRun
|
||||
#>
|
||||
|
||||
param(
|
||||
[string]$FromTag = "",
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$ToTag,
|
||||
[string]$ChangelogEntry = "",
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
function Write-Step($msg) {
|
||||
Write-Host " [*] $msg" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Write-Ok($msg) {
|
||||
Write-Host " [OK] $msg" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Warn($msg) {
|
||||
Write-Host " [!] $msg" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Write-Err($msg) {
|
||||
Write-Host " [ERROR] $msg" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# --- 1. Walidacja tagow ---
|
||||
|
||||
Write-Host "`n=== shopPRO Build Update ===" -ForegroundColor White
|
||||
|
||||
if (-not $FromTag) {
|
||||
$FromTag = (git describe --tags --abbrev=0 2>$null)
|
||||
if (-not $FromTag) {
|
||||
Write-Err "Nie znaleziono zadnego taga. Uzyj parametru -FromTag."
|
||||
exit 1
|
||||
}
|
||||
Write-Step "Auto-detect FromTag: $FromTag"
|
||||
}
|
||||
|
||||
# Sprawdz czy tagi istnieja
|
||||
$tagExists = git tag -l $FromTag
|
||||
if (-not $tagExists) {
|
||||
Write-Err "Tag '$FromTag' nie istnieje."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$toTagExists = git tag -l $ToTag
|
||||
if (-not $toTagExists) {
|
||||
Write-Warn "Tag '$ToTag' nie istnieje. Uzywam HEAD jako punktu docelowego."
|
||||
$diffTarget = "HEAD"
|
||||
} else {
|
||||
$diffTarget = $ToTag
|
||||
}
|
||||
|
||||
# --- 2. Wersja i katalog ---
|
||||
|
||||
$versionNumber = $ToTag -replace '^v', ''
|
||||
$versionInt = [int]($versionNumber -replace '^0\.', '')
|
||||
|
||||
# Oblicz katalog: 0.001-0.009 -> 0.00, 0.010-0.099 -> 0.00, 0.100-0.199 -> 0.10, 0.200-0.299 -> 0.20, 0.300-0.399 -> 0.30
|
||||
$dirTens = [math]::Floor($versionInt / 100)
|
||||
$dirStr = "0.{0}0" -f ($dirTens * 10).ToString().PadLeft(1, '0')
|
||||
|
||||
# Format: jesli dirTens < 10 to "0.X0", np 0.00, 0.10, 0.20, 0.30
|
||||
if ($dirTens -lt 10) {
|
||||
$dirStr = "0.{0}0" -f $dirTens.ToString().PadLeft(1, '0')
|
||||
} else {
|
||||
$dirStr = "0.{0}0" -f $dirTens
|
||||
}
|
||||
|
||||
Write-Step "Wersja: $versionNumber (int: $versionInt)"
|
||||
Write-Step "Katalog: updates/$dirStr/"
|
||||
|
||||
# --- 3. Git diff ---
|
||||
|
||||
Write-Step "Porownywanie: $FromTag..$diffTarget"
|
||||
|
||||
$diffOutput = git diff --name-status "$FromTag..$diffTarget" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "git diff nie powiodl sie: $diffOutput"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$addedFiles = @()
|
||||
$modifiedFiles = @()
|
||||
$deletedFiles = @()
|
||||
$renamedFiles = @()
|
||||
|
||||
foreach ($line in ($diffOutput -split "`n")) {
|
||||
$line = $line.Trim()
|
||||
if (-not $line) { continue }
|
||||
|
||||
$parts = $line -split "`t"
|
||||
$status = $parts[0]
|
||||
$filePath = if ($parts.Count -gt 1) { $parts[1] } else { "" }
|
||||
|
||||
# Zamien backslash na slash
|
||||
$filePath = $filePath -replace '\\', '/'
|
||||
|
||||
switch -Wildcard ($status) {
|
||||
"A" { $addedFiles += $filePath }
|
||||
"M" { $modifiedFiles += $filePath }
|
||||
"D" { $deletedFiles += $filePath }
|
||||
"R*" {
|
||||
# Rename: stary plik = deleted, nowy = added
|
||||
$newPath = if ($parts.Count -gt 2) { $parts[2] -replace '\\', '/' } else { "" }
|
||||
$deletedFiles += $filePath
|
||||
if ($newPath) { $addedFiles += $newPath }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Step "Zmiany: A=$($addedFiles.Count), M=$($modifiedFiles.Count), D=$($deletedFiles.Count)"
|
||||
|
||||
# --- 4. Filtrowanie przez .updateignore ---
|
||||
|
||||
$ignorePatterns = @()
|
||||
$ignoreFile = ".updateignore"
|
||||
|
||||
if (Test-Path $ignoreFile) {
|
||||
$ignorePatterns = Get-Content $ignoreFile | ForEach-Object { $_.Trim() } | Where-Object { $_ -and ($_ -notmatch '^\s*#') }
|
||||
Write-Step "Zaladowano $($ignorePatterns.Count) wzorcow z .updateignore"
|
||||
}
|
||||
|
||||
function Test-Ignored {
|
||||
param([string]$FilePath)
|
||||
|
||||
foreach ($pattern in $ignorePatterns) {
|
||||
# Wzorzec katalogu (konczy sie na /)
|
||||
if ($pattern.EndsWith('/')) {
|
||||
$dirPattern = $pattern.TrimEnd('/')
|
||||
if ($FilePath -like "$dirPattern/*" -or $FilePath -eq $dirPattern) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
# Wzorzec z wildcard
|
||||
elseif ($pattern.Contains('*')) {
|
||||
# *.md -> dopasuj nazwe pliku
|
||||
if ($pattern.StartsWith('*')) {
|
||||
$fileName = Split-Path $FilePath -Leaf
|
||||
if ($fileName -like $pattern) { return $true }
|
||||
}
|
||||
# Zwykly glob
|
||||
elseif ($FilePath -like $pattern) { return $true }
|
||||
}
|
||||
# Dokladne dopasowanie
|
||||
else {
|
||||
if ($FilePath -eq $pattern) { return $true }
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
$filteredAdded = @()
|
||||
$filteredModified = @()
|
||||
$filteredDeleted = @()
|
||||
$ignoredCount = 0
|
||||
|
||||
foreach ($f in $addedFiles) {
|
||||
if (Test-Ignored $f) { $ignoredCount++; continue }
|
||||
$filteredAdded += $f
|
||||
}
|
||||
foreach ($f in $modifiedFiles) {
|
||||
if (Test-Ignored $f) { $ignoredCount++; continue }
|
||||
$filteredModified += $f
|
||||
}
|
||||
foreach ($f in $deletedFiles) {
|
||||
if (Test-Ignored $f) { $ignoredCount++; continue }
|
||||
$filteredDeleted += $f
|
||||
}
|
||||
|
||||
Write-Step "Po filtrowaniu: A=$($filteredAdded.Count), M=$($filteredModified.Count), D=$($filteredDeleted.Count) (pominieto: $ignoredCount)"
|
||||
|
||||
# Rozdziel usuniete pliki i katalogi
|
||||
$deletedDirs = @()
|
||||
$deletedFilesOnly = @()
|
||||
|
||||
foreach ($f in $filteredDeleted) {
|
||||
# Sprawdz czy to byl katalog (w git nie ma katalogow, ale mozemy sprawdzic po wzorcu)
|
||||
# Git nie trackuje pustych katalogow, wiec deleted entries to zawsze pliki
|
||||
$deletedFilesOnly += $f
|
||||
}
|
||||
|
||||
# --- 5. Odczyt migracji SQL ---
|
||||
|
||||
$sqlQueries = @()
|
||||
$migrationFile = "migrations/$versionNumber.sql"
|
||||
|
||||
if (Test-Path $migrationFile) {
|
||||
$sqlQueries = Get-Content $migrationFile | Where-Object { $_.Trim() -ne '' }
|
||||
Write-Step "Znaleziono migracje SQL: $migrationFile ($($sqlQueries.Count) zapytan)"
|
||||
} else {
|
||||
Write-Step "Brak migracji SQL ($migrationFile nie istnieje)"
|
||||
}
|
||||
|
||||
# --- 6. Podsumowanie ---
|
||||
|
||||
$filesToPack = $filteredAdded + $filteredModified
|
||||
|
||||
if ($filesToPack.Count -eq 0 -and $filteredDeleted.Count -eq 0 -and $sqlQueries.Count -eq 0) {
|
||||
Write-Warn "Brak zmian do pakowania (po filtrowaniu). Przerywam."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "`n--- Pliki do paczki ---" -ForegroundColor White
|
||||
foreach ($f in $filteredAdded) { Write-Host " A $f" -ForegroundColor Green }
|
||||
foreach ($f in $filteredModified) { Write-Host " M $f" -ForegroundColor Yellow }
|
||||
foreach ($f in $filteredDeleted) { Write-Host " D $f" -ForegroundColor Red }
|
||||
|
||||
if ($sqlQueries.Count -gt 0) {
|
||||
Write-Host "`n--- SQL ---" -ForegroundColor White
|
||||
foreach ($q in $sqlQueries) { Write-Host " $q" -ForegroundColor Gray }
|
||||
}
|
||||
|
||||
# --- DryRun? ---
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host "`n[DRY RUN] Zadne pliki nie zostaly utworzone.`n" -ForegroundColor Magenta
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- 7. Walidacja ChangelogEntry ---
|
||||
|
||||
if (-not $ChangelogEntry) {
|
||||
Write-Err "Parametr -ChangelogEntry jest wymagany (chyba ze uzywasz -DryRun)."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- 8. Tworzenie temp i kopiowanie plikow ---
|
||||
|
||||
$tempDir = "temp/temp_$versionInt"
|
||||
|
||||
if (Test-Path $tempDir) {
|
||||
Remove-Item -Recurse -Force $tempDir
|
||||
}
|
||||
|
||||
foreach ($f in $filesToPack) {
|
||||
$destPath = Join-Path $tempDir $f
|
||||
$destDir = Split-Path $destPath -Parent
|
||||
|
||||
if (-not (Test-Path $destDir)) {
|
||||
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
|
||||
}
|
||||
|
||||
if (Test-Path $f) {
|
||||
Copy-Item $f $destPath -Force
|
||||
} else {
|
||||
Write-Warn "Plik nie istnieje (moze zostal usuniety po TAGU): $f"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Ok "Skopiowano $($filesToPack.Count) plikow do $tempDir"
|
||||
|
||||
# --- 9. Tworzenie ZIP ---
|
||||
|
||||
$updatesDir = "updates/$dirStr"
|
||||
if (-not (Test-Path $updatesDir)) {
|
||||
New-Item -ItemType Directory -Path $updatesDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$zipPath = "$updatesDir/ver_$versionNumber.zip"
|
||||
|
||||
if (Test-Path $zipPath) {
|
||||
Remove-Item $zipPath -Force
|
||||
}
|
||||
|
||||
# Pakuj zawartosc temp dir (bez folderu temp/)
|
||||
$originalLocation = Get-Location
|
||||
Set-Location $tempDir
|
||||
Compress-Archive -Path '*' -DestinationPath "../../$zipPath" -Force
|
||||
Set-Location $originalLocation
|
||||
|
||||
Write-Ok "Utworzono ZIP: $zipPath"
|
||||
|
||||
# --- 10. Checksum SHA256 ---
|
||||
|
||||
$hash = (Get-FileHash $zipPath -Algorithm SHA256).Hash.ToLower()
|
||||
Write-Ok "SHA256: $hash"
|
||||
|
||||
# --- 11. Manifest JSON ---
|
||||
|
||||
$manifest = @{
|
||||
version = $versionNumber
|
||||
date = (Get-Date -Format "yyyy-MM-dd")
|
||||
checksum_zip = "sha256:$hash"
|
||||
files = @{
|
||||
modified = $filteredModified
|
||||
added = $filteredAdded
|
||||
deleted = $deletedFilesOnly
|
||||
}
|
||||
directories_deleted = $deletedDirs
|
||||
sql = $sqlQueries
|
||||
changelog = $ChangelogEntry
|
||||
}
|
||||
|
||||
$manifestJson = $manifest | ConvertTo-Json -Depth 4
|
||||
$manifestPath = "$updatesDir/ver_${versionNumber}_manifest.json"
|
||||
$manifestJson | Out-File $manifestPath -Encoding UTF8
|
||||
|
||||
Write-Ok "Utworzono manifest: $manifestPath"
|
||||
|
||||
# --- 12. Legacy _sql.txt i _files.txt (okres przejsciowy) ---
|
||||
|
||||
if ($sqlQueries.Count -gt 0) {
|
||||
$sqlPath = "$updatesDir/ver_${versionNumber}_sql.txt"
|
||||
($sqlQueries -join "`n") | Out-File $sqlPath -Encoding UTF8 -NoNewline
|
||||
Write-Ok "Utworzono legacy SQL: $sqlPath"
|
||||
}
|
||||
|
||||
if ($deletedFilesOnly.Count -gt 0 -or $deletedDirs.Count -gt 0) {
|
||||
$filesContent = @()
|
||||
foreach ($f in $deletedFilesOnly) { $filesContent += "F: ../$f" }
|
||||
foreach ($d in $deletedDirs) { $filesContent += "D: ../$d" }
|
||||
|
||||
$filesPath = "$updatesDir/ver_${versionNumber}_files.txt"
|
||||
($filesContent -join "`n") | Out-File $filesPath -Encoding UTF8 -NoNewline
|
||||
Write-Ok "Utworzono legacy files: $filesPath"
|
||||
}
|
||||
|
||||
# --- 13. Aktualizacja versions.php ---
|
||||
|
||||
$versionsFile = "updates/versions.php"
|
||||
if (Test-Path $versionsFile) {
|
||||
$content = Get-Content $versionsFile -Raw
|
||||
$content = $content -replace '\$current_ver\s*=\s*\d+;', "`$current_ver = $versionInt;"
|
||||
$content | Out-File $versionsFile -Encoding UTF8 -NoNewline
|
||||
Write-Ok "Zaktualizowano versions.php: `$current_ver = $versionInt"
|
||||
}
|
||||
|
||||
# --- 14. Aktualizacja changelog.php ---
|
||||
|
||||
$changelogFile = "updates/changelog.php"
|
||||
if (Test-Path $changelogFile) {
|
||||
$dateStr = Get-Date -Format "dd.MM.yyyy"
|
||||
$newEntry = "<b>ver. $versionNumber - $dateStr</b><br />`n$ChangelogEntry`n<hr>`n"
|
||||
|
||||
$changelogContent = Get-Content $changelogFile -Raw
|
||||
$changelogContent = $newEntry + $changelogContent
|
||||
$changelogContent | Out-File $changelogFile -Encoding UTF8 -NoNewline
|
||||
Write-Ok "Zaktualizowano changelog.php"
|
||||
}
|
||||
|
||||
# --- 15. Cleanup ---
|
||||
|
||||
if (Test-Path $tempDir) {
|
||||
Remove-Item -Recurse -Force $tempDir
|
||||
}
|
||||
# Usun pusty folder temp jesli nie ma juz w nim nic
|
||||
if ((Test-Path "temp") -and ((Get-ChildItem "temp" -Force).Count -eq 0)) {
|
||||
Remove-Item "temp" -Force
|
||||
}
|
||||
|
||||
Write-Ok "Wyczyszczono pliki tymczasowe"
|
||||
|
||||
# --- Podsumowanie ---
|
||||
|
||||
Write-Host "`n=== Gotowe ===" -ForegroundColor Green
|
||||
Write-Host " ZIP: $zipPath"
|
||||
Write-Host " Manifest: $manifestPath"
|
||||
Write-Host " Wersja: $versionNumber (int: $versionInt)"
|
||||
Write-Host ""
|
||||
@@ -4,6 +4,31 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.301 (2026-02-22) - Mobile responsive: filtry tabel i szczegoly zamowienia
|
||||
|
||||
- **NEW**: Filtry w tabelach admina domyslnie ukryte — przycisk toggle z ikona filtra, badge z liczba aktywnych filtrow
|
||||
- **NEW**: Stan filtrow zapamietywany w `localStorage` per widok; auto-show gdy filtry aktywne
|
||||
- **NEW**: Mobilna wersja szczegolow zamowienia — responsywny layout (CSS-only, breakpoint 767px)
|
||||
- **NEW**: Pasek akcji zamowienia — ikony-only na mobile, flex row bez zawijania
|
||||
- **NEW**: Tabela produktow zamowienia — kompaktowa lista na mobile (obraz + nazwa + linia `qty x cena = suma`)
|
||||
- **NEW**: Sekcje informacyjne zamowienia — full-width stacking na mobile
|
||||
- **NEW**: Dropdown integracji jako bottom-sheet na mobile
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.300 (2026-02-21) - System aktualizacji oparty na manifestach JSON
|
||||
|
||||
- **NEW**: Manifest JSON per wersja — zastępuje osobne pliki `_sql.txt` i `_files.txt`
|
||||
- **NEW**: Weryfikacja checksum SHA256 pobranych paczek ZIP
|
||||
- **NEW**: Automatyczny backup plików przed nadpisaniem (`backups/` directory)
|
||||
- **NEW**: `build-update.ps1` — automatyczne budowanie paczek z `git diff` między tagami
|
||||
- **NEW**: `.updateignore` — wzorce plików wykluczonych z paczek aktualizacji
|
||||
- **NEW**: `migrations/` — folder na wersjonowane pliki SQL
|
||||
- **NEW**: Panel "Log ostatniej aktualizacji" w widoku aktualizacji admina
|
||||
- **UPDATE**: `UpdateRepository` — dual-mode dispatcher (manifest + legacy fallback)
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.299 (2026-02-21) - Widoczność kolumn w tabelach
|
||||
|
||||
- **NEW**: Toggle widoczności kolumn w komponentach `table-list` — przycisk z ikoną kolumn, dropdown z toggle switchami
|
||||
|
||||
@@ -23,10 +23,10 @@ composer test # standard
|
||||
## Aktualny stan
|
||||
|
||||
```text
|
||||
OK (687 tests, 1971 assertions)
|
||||
OK (692 tests, 1988 assertions)
|
||||
```
|
||||
|
||||
Zweryfikowano: 2026-02-19 (ver. 0.297)
|
||||
Zweryfikowano: 2026-02-21 (ver. 0.300)
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
@@ -66,6 +66,7 @@ tests/
|
||||
| | |-- Settings/SettingsRepositoryTest.php
|
||||
| | |-- ShopStatus/ShopStatusRepositoryTest.php
|
||||
| | |-- Transport/TransportRepositoryTest.php
|
||||
| | |-- Update/UpdateRepositoryTest.php
|
||||
| | `-- User/UserRepositoryTest.php
|
||||
| `-- admin/
|
||||
| `-- Controllers/
|
||||
|
||||
@@ -1,136 +1,113 @@
|
||||
# Instrukcja tworzenia aktualizacji shopPRO
|
||||
|
||||
## Struktura aktualizacji
|
||||
## Nowy sposób (od v0.301) — automatyczny build script
|
||||
|
||||
### Wymagania
|
||||
- Git z tagami wersji (np. `v0.299`, `v0.300`)
|
||||
- PowerShell
|
||||
|
||||
### Workflow
|
||||
|
||||
```
|
||||
1. Pracuj normalnie: commit, push, commit, push...
|
||||
2. Gdy wersja gotowa:
|
||||
→ git tag v0.XXX
|
||||
→ ./build-update.ps1 -ToTag v0.XXX -ChangelogEntry "NEW - opis"
|
||||
3. Upload plików z updates/0.XX/ na serwer aktualizacji
|
||||
```
|
||||
|
||||
### Użycie build-update.ps1
|
||||
|
||||
```powershell
|
||||
# Podgląd zmian (bez tworzenia plików)
|
||||
./build-update.ps1 -ToTag v0.301 -DryRun
|
||||
|
||||
# Budowanie paczki (auto-detect poprzedniego tagu)
|
||||
./build-update.ps1 -ToTag v0.301 -ChangelogEntry "NEW - opis zmiany"
|
||||
|
||||
# Z jawnym tagiem źródłowym
|
||||
./build-update.ps1 -FromTag v0.300 -ToTag v0.301 -ChangelogEntry "NEW - opis"
|
||||
```
|
||||
|
||||
### Co robi skrypt automatycznie
|
||||
1. `git diff --name-status` między tagami → listy dodanych/zmodyfikowanych/usuniętych plików
|
||||
2. Filtrowanie przez `.updateignore` (pliki deweloperskie, konfiguracyjne itp.)
|
||||
3. Kopiowanie plików do temp, tworzenie ZIP
|
||||
4. SHA256 checksum ZIP-a
|
||||
5. Generowanie `ver_X.XXX_manifest.json`
|
||||
6. Generowanie legacy `_sql.txt` i `_files.txt` (okres przejściowy)
|
||||
7. Aktualizacja `versions.php` i `changelog.php`
|
||||
8. Cleanup
|
||||
|
||||
### Pliki wynikowe
|
||||
- `updates/0.XX/ver_X.XXX.zip` — paczka z plikami
|
||||
- `updates/0.XX/ver_X.XXX_manifest.json` — manifest z checksumem, listą zmian, SQL
|
||||
- `updates/0.XX/ver_X.XXX_sql.txt` — legacy SQL (okres przejściowy)
|
||||
- `updates/0.XX/ver_X.XXX_files.txt` — legacy lista plików do usunięcia (okres przejściowy)
|
||||
|
||||
### Migracje SQL
|
||||
Pliki SQL umieszczaj w `migrations/{version}.sql` (np. `migrations/0.301.sql`).
|
||||
Build script automatycznie je wczyta i umieści w manifeście + legacy `_sql.txt`.
|
||||
|
||||
### Format manifestu
|
||||
```json
|
||||
{
|
||||
"version": "0.301",
|
||||
"date": "2026-02-22",
|
||||
"checksum_zip": "sha256:abc123...",
|
||||
"files": {
|
||||
"modified": ["autoload/Domain/Order/OrderRepository.php"],
|
||||
"added": ["autoload/Domain/Order/NewHelper.php"],
|
||||
"deleted": ["autoload/shop/OldClass.php"]
|
||||
},
|
||||
"directories_deleted": [],
|
||||
"sql": ["ALTER TABLE pp_x ADD COLUMN y INT DEFAULT 0"],
|
||||
"changelog": "NEW - opis zmiany"
|
||||
}
|
||||
```
|
||||
|
||||
### .updateignore
|
||||
Plik w katalogu głównym projektu, wzorce plików wykluczonych z paczek (jak `.gitignore`).
|
||||
|
||||
---
|
||||
|
||||
## Stary sposób (do v0.300) — ręczne pakowanie
|
||||
|
||||
### Struktura aktualizacji
|
||||
|
||||
Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesiątki wersji.
|
||||
|
||||
### Pliki aktualizacji:
|
||||
#### Pliki aktualizacji:
|
||||
- `ver_X.XXX.zip` - paczka ZIP ze zmienionymi plikami (BEZ folderu wersji, bezpośrednio struktura katalogów)
|
||||
- `ver_X.XXX_sql.txt` - opcjonalny plik z zapytaniami SQL (jeśli wymagane zmiany w bazie)
|
||||
- `ver_X.XXX_files.txt` - opcjonalny plik z listą plików do **USUNIĘCIA** przy aktualizacji (format: `F: ../sciezka/do/pliku.php`)
|
||||
- `changelog.php` - historia zmian
|
||||
- `versions.php` - konfiguracja wersji (zmienna `$current_ver`)
|
||||
|
||||
### Zasada pakowania plików
|
||||
#### Zasada pakowania plików
|
||||
- Do paczek aktualizacji **nie dodajemy plików `*.md`** (dokumentacja jest tylko wewnętrzna/deweloperska).
|
||||
- Do paczek aktualizacji **nie dodajemy `updates/changelog.php`** (to plik serwisowy po stronie repozytorium aktualizacji, nie runtime klienta).
|
||||
- Do paczek aktualizacji **nie dodajemy głównego `.htaccess` z katalogu projektu** (ten plik wdrażamy osobno, poza ZIP aktualizacji).
|
||||
|
||||
## Procedura tworzenia nowej aktualizacji
|
||||
### Procedura ręczna
|
||||
|
||||
## Status biezacej aktualizacji (ver. 0.299)
|
||||
1. Określ numer wersji
|
||||
2. Utwórz folder tymczasowy: `mkdir -p temp/temp_XXX/sciezka/do/pliku`
|
||||
3. Skopiuj zmienione pliki do folderu tymczasowego
|
||||
4. Utwórz ZIP z zawartości folderu (nie z samego folderu!)
|
||||
5. Usuń folder tymczasowy
|
||||
6. Zaktualizuj `changelog.php` i `versions.php`
|
||||
7. (Opcjonalnie) Utwórz `_sql.txt` i `_files.txt`
|
||||
|
||||
- Wersja udostepniona: `0.299` (data: 2026-02-21).
|
||||
**WAŻNE:** W archiwum ZIP NIE powinno być folderu z nazwą wersji. Struktura ZIP zaczyna się bezpośrednio od katalogów projektu (admin/, autoload/, itp.).
|
||||
|
||||
## Status bieżącej aktualizacji (ver. 0.301)
|
||||
|
||||
- Wersja udostępniona: `0.301` (data: 2026-02-22).
|
||||
- Pliki publikacyjne:
|
||||
- `updates/0.20/ver_0.299.zip`
|
||||
- `updates/0.30/ver_0.301.zip`
|
||||
- Pliki metadanych aktualizacji:
|
||||
- `updates/changelog.php`
|
||||
- `updates/versions.php` (`$current_ver = 299`)
|
||||
- Weryfikacja testow przed publikacja:
|
||||
- `OK (687 tests, 1971 assertions)`
|
||||
|
||||
### 1. Określ numer wersji
|
||||
Sprawdź ostatnią wersję w `updates/` i zwiększ o 1.
|
||||
|
||||
### 2. Utwórz folder tymczasowy ze strukturą w katalogu temp
|
||||
```bash
|
||||
mkdir -p temp/temp_XXX/sciezka/do/pliku
|
||||
```
|
||||
|
||||
**WAŻNE:** W archiwum ZIP NIE powinno być folderu z nazwą wersji (np. ver_0.234/).
|
||||
Struktura ZIP powinna zaczynać się bezpośrednio od katalogów projektu (admin/, autoload/, itp.).
|
||||
|
||||
### 3. Skopiuj zmienione pliki do folderu tymczasowego
|
||||
```bash
|
||||
cp sciezka/do/pliku.php temp/temp_XXX/sciezka/do/pliku.php
|
||||
```
|
||||
|
||||
### 4. Utwórz plik ZIP z zawartości folderu (nie z samego folderu!)
|
||||
```powershell
|
||||
cd temp/temp_XXX
|
||||
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../ver_X.XXX.zip' -Force"
|
||||
```
|
||||
|
||||
### 5. Usuń folder tymczasowy
|
||||
```bash
|
||||
rm -rf temp/temp_XXX
|
||||
```
|
||||
|
||||
### 6. Zaktualizuj changelog.php
|
||||
Dodaj wpis na początku pliku:
|
||||
```html
|
||||
<b>ver. X.XXX - DD.MM.YYYY</b><br />
|
||||
- NEW/FIX/UPDATE - opis zmiany
|
||||
<hr>
|
||||
```
|
||||
|
||||
Prefiksy:
|
||||
- `NEW` - nowa funkcjonalność
|
||||
- `FIX` - naprawa błędu
|
||||
- `UPDATE` - aktualizacja istniejącej funkcjonalności
|
||||
|
||||
### 7. Zaktualizuj versions.php
|
||||
Zmień wartość `$current_ver` na nowy numer wersji (bez przedrostka 0.):
|
||||
```php
|
||||
$current_ver = 234; // dla wersji 0.234
|
||||
```
|
||||
|
||||
### 8. (Opcjonalnie) Utwórz plik SQL
|
||||
Jeśli aktualizacja wymaga zmian w bazie danych, utwórz plik `ver_X.XXX_sql.txt` z zapytaniami SQL.
|
||||
|
||||
### 9. (Opcjonalnie) Utwórz plik z listą plików do usunięcia
|
||||
Jeśli aktualizacja wymaga usunięcia przestarzałych plików, utwórz plik `ver_X.XXX_files.txt`:
|
||||
```
|
||||
F: ../sciezka/do/pliku1.php
|
||||
F: ../sciezka/do/pliku2.php
|
||||
```
|
||||
**UWAGA:** Pliki wymienione w tym pliku zostaną USUNIĘTE z systemu podczas aktualizacji.
|
||||
|
||||
## Przykład - aktualizacja 0.234
|
||||
|
||||
Zmienione pliki:
|
||||
- `autoload/admin/controls/class.ShopOrder.php`
|
||||
- `admin/templates/shop-order/order-details.php`
|
||||
|
||||
Opis: Dodanie przycisku do zaznaczania zamówienia jako wysłane do trustmate.io
|
||||
|
||||
### Komendy:
|
||||
|
||||
```bash
|
||||
# Utwórz strukturę w folderze tymczasowym
|
||||
mkdir -p temp/temp_234/autoload/admin/controls
|
||||
mkdir -p temp/temp_234/admin/templates/shop-order
|
||||
|
||||
# Skopiuj pliki
|
||||
cp autoload/admin/controls/class.ShopOrder.php temp/temp_234/autoload/admin/controls/
|
||||
cp admin/templates/shop-order/order-details.php temp/temp_234/admin/templates/shop-order/
|
||||
|
||||
# Utwórz ZIP z ZAWARTOŚCI folderu (ważne: wejdź do folderu i spakuj '*')
|
||||
cd temp/temp_234
|
||||
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../ver_0.234.zip' -Force"
|
||||
|
||||
# Wróć i usuń folder tymczasowy
|
||||
cd ..
|
||||
rm -rf temp_234
|
||||
```
|
||||
|
||||
### Poprawna struktura ZIP:
|
||||
```
|
||||
ver_0.234.zip
|
||||
├── admin/
|
||||
│ └── templates/
|
||||
│ └── shop-order/
|
||||
│ └── order-details.php
|
||||
└── autoload/
|
||||
└── admin/
|
||||
└── controls/
|
||||
└── class.ShopOrder.php
|
||||
```
|
||||
|
||||
### NIEPOPRAWNA struktura (do uniknięcia):
|
||||
```
|
||||
ver_0.234.zip
|
||||
└── ver_0.234/ <-- tego folderu NIE powinno być!
|
||||
├── admin/
|
||||
└── autoload/
|
||||
```
|
||||
- `updates/versions.php` (`$current_ver = 301`)
|
||||
- Weryfikacja testów przed publikacją:
|
||||
- `OK (692 tests, 1988 assertions)`
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
REM Skrypt do uruchamiania testów z PEŁNYMI szczegółami
|
||||
|
||||
echo.
|
||||
echo ================================
|
||||
echo Testy DEBUG - pełne szczegóły
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
C:\xampp\php\php.exe phpunit.phar --debug %*
|
||||
|
||||
echo.
|
||||
pause
|
||||
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
REM Skrypt do uruchamiania testów - tylko kropki
|
||||
|
||||
echo.
|
||||
echo ================================
|
||||
echo Testy jednostkowe shopPRO
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
C:\xampp\php\php.exe phpunit.phar %*
|
||||
|
||||
echo.
|
||||
pause
|
||||
13
test.bat
13
test.bat
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
REM Skrypt do uruchamiania testów PHPUnit
|
||||
|
||||
echo.
|
||||
echo ================================
|
||||
echo Testy jednostkowe shopPRO
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
C:\xampp\php\php.exe phpunit.phar --testdox %*
|
||||
|
||||
echo.
|
||||
pause
|
||||
47
test.ps1
47
test.ps1
@@ -1,47 +0,0 @@
|
||||
Param(
|
||||
[Parameter(ValueFromRemainingArguments = $true)]
|
||||
[string[]]$PhpUnitArgs
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Resolve-PhpExe {
|
||||
$cmd = Get-Command php -ErrorAction SilentlyContinue
|
||||
if ($cmd -and $cmd.Source) {
|
||||
return $cmd.Source
|
||||
}
|
||||
|
||||
$candidates = @(
|
||||
"C:\xampp\php\php.exe",
|
||||
"C:\php\php.exe",
|
||||
"C:\Program Files\PHP\php.exe"
|
||||
)
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
if (Test-Path $candidate) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
|
||||
throw "Nie znaleziono interpretera PHP. Dodaj php do PATH albo zainstaluj PHP (np. XAMPP)."
|
||||
}
|
||||
|
||||
$phpExe = Resolve-PhpExe
|
||||
$phpUnitPhar = Join-Path $PSScriptRoot "phpunit.phar"
|
||||
|
||||
if (-not (Test-Path $phpUnitPhar)) {
|
||||
throw "Brak pliku phpunit.phar w katalogu projektu: $PSScriptRoot"
|
||||
}
|
||||
|
||||
$args = @($phpUnitPhar, "--do-not-cache-result") + $PhpUnitArgs
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================"
|
||||
Write-Host " Testy jednostkowe shopPRO"
|
||||
Write-Host "================================"
|
||||
Write-Host "PHP: $phpExe"
|
||||
Write-Host "Cmd: $phpExe $($args -join ' ')"
|
||||
Write-Host ""
|
||||
|
||||
& $phpExe @args
|
||||
exit $LASTEXITCODE
|
||||
10
test.sh
10
test.sh
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Skrypt do uruchamiania testów PHPUnit
|
||||
|
||||
echo ""
|
||||
echo "================================"
|
||||
echo " Testy jednostkowe shopPRO"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
/c/xampp/php/php.exe phpunit.phar "$@"
|
||||
@@ -50,7 +50,7 @@ class UpdateRepositoryTest extends TestCase
|
||||
$repository = new UpdateRepository($db);
|
||||
$repository->runPendingMigrations();
|
||||
|
||||
$this->assertTrue(true); // No exception thrown
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testHasPrivateHelperMethods(): void
|
||||
@@ -59,6 +59,11 @@ class UpdateRepositoryTest extends TestCase
|
||||
|
||||
$privateMethods = [
|
||||
'downloadAndApply',
|
||||
'downloadAndApplyLegacy',
|
||||
'downloadAndApplyWithManifest',
|
||||
'downloadManifest',
|
||||
'verifyChecksum',
|
||||
'createBackup',
|
||||
'executeSql',
|
||||
'deleteFiles',
|
||||
'extractZip',
|
||||
@@ -77,4 +82,99 @@ class UpdateRepositoryTest extends TestCase
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testVerifyChecksumValidFormat(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
|
||||
$reflection = new \ReflectionClass($repository);
|
||||
$method = $reflection->getMethod('verifyChecksum');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Create a temp file with known content
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'test_checksum_');
|
||||
file_put_contents($tmpFile, 'test content for checksum');
|
||||
|
||||
$expectedHash = hash_file('sha256', $tmpFile);
|
||||
$result = $method->invoke($repository, $tmpFile, 'sha256:' . $expectedHash, []);
|
||||
|
||||
$this->assertTrue($result['valid']);
|
||||
$this->assertNotEmpty($result['log']);
|
||||
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
public function testVerifyChecksumInvalidHash(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
|
||||
$reflection = new \ReflectionClass($repository);
|
||||
$method = $reflection->getMethod('verifyChecksum');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'test_checksum_');
|
||||
file_put_contents($tmpFile, 'test content');
|
||||
|
||||
$result = $method->invoke($repository, $tmpFile, 'sha256:invalidhash', []);
|
||||
|
||||
$this->assertFalse($result['valid']);
|
||||
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
public function testVerifyChecksumInvalidFormat(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
|
||||
$reflection = new \ReflectionClass($repository);
|
||||
$method = $reflection->getMethod('verifyChecksum');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($repository, '/tmp/nonexistent', 'badformat', []);
|
||||
|
||||
$this->assertFalse($result['valid']);
|
||||
}
|
||||
|
||||
public function testCreateBackupWithEmptyManifest(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
|
||||
$reflection = new \ReflectionClass($repository);
|
||||
$method = $reflection->getMethod('createBackup');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$manifest = [
|
||||
'version' => '0.999',
|
||||
'files' => [],
|
||||
];
|
||||
|
||||
$log = $method->invoke($repository, $manifest, []);
|
||||
|
||||
$this->assertIsArray($log);
|
||||
$hasBackupInfo = false;
|
||||
foreach ($log as $entry) {
|
||||
if (strpos($entry, 'Brak plików do backupu') !== false) {
|
||||
$hasBackupInfo = true;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($hasBackupInfo);
|
||||
}
|
||||
|
||||
public function testDownloadManifestReturnsNullForInvalidUrl(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
|
||||
$reflection = new \ReflectionClass($repository);
|
||||
$method = $reflection->getMethod('downloadManifest');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($repository, 'http://invalid.nonexistent.test', '0.999');
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
updates/0.30/ver_0.300.zip
Normal file
BIN
updates/0.30/ver_0.300.zip
Normal file
Binary file not shown.
@@ -1,3 +1,7 @@
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 299;
|
||||
$current_ver = 300;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user