ver. 0.277: ShopProduct factory, Dashboard, Update migration, legacy cleanup, admin\App
- ShopProduct factory: full migration (~40 ProductRepository methods, ~30 controller actions) - Dashboard: Domain+DI migration (DashboardRepository + DashboardController) - Update: Domain+DI migration (UpdateRepository + UpdateController, template rewrite) - Renamed admin\Site to admin\App, removed dead fallback routing - Removed all legacy folders: admin/controls, admin/factory, admin/view - Newsletter: switched from admin\factory\Articles to ArticleRepository - 414 tests, 1335 assertions passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -840,4 +840,28 @@ class ArticleRepository
|
||||
|
||||
$this->db->delete('pp_articles_images', ['article_id' => null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera artykuly opublikowane w podanym zakresie dat.
|
||||
*/
|
||||
public function articlesByDateAdd( string $dateStart, string $dateEnd ): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT id FROM pp_articles '
|
||||
. 'WHERE status = 1 '
|
||||
. 'AND date_add BETWEEN \'' . addslashes( $dateStart ) . '\' AND \'' . addslashes( $dateEnd ) . '\' '
|
||||
. 'ORDER BY date_add DESC'
|
||||
);
|
||||
|
||||
$articles = [];
|
||||
$rows = $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
|
||||
if ( is_array( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$articles[] = \front\factory\Articles::article_details( $row['id'], 'pl' );
|
||||
}
|
||||
}
|
||||
|
||||
return $articles;
|
||||
}
|
||||
}
|
||||
|
||||
153
autoload/Domain/Dashboard/DashboardRepository.php
Normal file
153
autoload/Domain/Dashboard/DashboardRepository.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
namespace Domain\Dashboard;
|
||||
|
||||
class DashboardRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function summaryOrders(): int
|
||||
{
|
||||
try {
|
||||
$redis = \RedisConnection::getInstance()->getConnection();
|
||||
if ( $redis ) {
|
||||
$cached = $redis->get( 'summary_ordersd' );
|
||||
if ( $cached !== false ) {
|
||||
return (int) unserialize( $cached );
|
||||
}
|
||||
$summary = (int) $this->db->count( 'pp_shop_orders', [ 'status' => 6 ] );
|
||||
$redis->setex( 'summary_ordersd', 300, serialize( $summary ) );
|
||||
return $summary;
|
||||
}
|
||||
} catch ( \RedisException $e ) {
|
||||
// fallback
|
||||
}
|
||||
|
||||
return (int) $this->db->count( 'pp_shop_orders', [ 'status' => 6 ] );
|
||||
}
|
||||
|
||||
public function summarySales(): float
|
||||
{
|
||||
try {
|
||||
$redis = \RedisConnection::getInstance()->getConnection();
|
||||
if ( $redis ) {
|
||||
$cached = $redis->get( 'summary_salesd' );
|
||||
if ( $cached !== false ) {
|
||||
return (float) unserialize( $cached );
|
||||
}
|
||||
$summary = $this->calculateTotalSales();
|
||||
$redis->setex( 'summary_salesd', 300, serialize( $summary ) );
|
||||
return $summary;
|
||||
}
|
||||
} catch ( \RedisException $e ) {
|
||||
// fallback
|
||||
}
|
||||
|
||||
return $this->calculateTotalSales();
|
||||
}
|
||||
|
||||
private function calculateTotalSales(): float
|
||||
{
|
||||
return (float) $this->db->sum( 'pp_shop_orders', 'summary', [ 'status' => 6 ] )
|
||||
- (float) $this->db->sum( 'pp_shop_orders', 'transport_cost', [ 'status' => 6 ] );
|
||||
}
|
||||
|
||||
public function salesGrid(): array
|
||||
{
|
||||
$grid = [];
|
||||
$rows = $this->db->select( 'pp_shop_orders', [ 'id', 'date_order' ], [ 'status' => 6 ] );
|
||||
|
||||
if ( is_array( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$ts = strtotime( $row['date_order'] );
|
||||
$dayOfWeek = date( 'N', $ts );
|
||||
$hour = date( 'G', $ts );
|
||||
if ( !isset( $grid[$dayOfWeek][$hour] ) ) {
|
||||
$grid[$dayOfWeek][$hour] = 0;
|
||||
}
|
||||
$grid[$dayOfWeek][$hour]++;
|
||||
}
|
||||
}
|
||||
|
||||
return $grid;
|
||||
}
|
||||
|
||||
public function mostViewedProducts(): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT id, SUM(visits) AS visits '
|
||||
. 'FROM pp_shop_products '
|
||||
. 'GROUP BY id '
|
||||
. 'ORDER BY visits DESC '
|
||||
. 'LIMIT 10'
|
||||
);
|
||||
|
||||
return $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
}
|
||||
|
||||
public function bestSalesProducts(): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT parent_product_id, SUM(quantity) AS quantity_summary, SUM(price_brutto_promo * quantity) AS sales '
|
||||
. 'FROM pp_shop_order_products AS psop '
|
||||
. 'INNER JOIN pp_shop_orders AS pso ON pso.id = psop.order_id '
|
||||
. 'WHERE pso.status = 6 '
|
||||
. 'GROUP BY parent_product_id '
|
||||
. 'ORDER BY sales DESC '
|
||||
. 'LIMIT 10'
|
||||
);
|
||||
|
||||
return $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
}
|
||||
|
||||
public function last24MonthsSales(): array
|
||||
{
|
||||
$sales = [];
|
||||
$date = new \DateTime();
|
||||
|
||||
for ( $i = 0; $i < 24; $i++ ) {
|
||||
$dateStart = $date->format( 'Y-m-01' );
|
||||
$dateEnd = $date->format( 'Y-m-t' );
|
||||
|
||||
$where = [
|
||||
'AND' => [
|
||||
'status' => 6,
|
||||
'date_order[>=]' => $dateStart,
|
||||
'date_order[<=]' => $dateEnd,
|
||||
]
|
||||
];
|
||||
|
||||
$monthSales = (float) $this->db->sum( 'pp_shop_orders', 'summary', $where )
|
||||
- (float) $this->db->sum( 'pp_shop_orders', 'transport_cost', $where );
|
||||
|
||||
$sales[] = [
|
||||
'date' => $date->format( 'Y-m' ),
|
||||
'sales' => $monthSales,
|
||||
];
|
||||
|
||||
$date->sub( new \DateInterval( 'P1M' ) );
|
||||
}
|
||||
|
||||
return $sales;
|
||||
}
|
||||
|
||||
public function lastOrders( int $limit = 10 ): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT id, number, date_order, '
|
||||
. 'CONCAT( client_name, \' \', client_surname ) AS client, '
|
||||
. 'client_email, '
|
||||
. 'CONCAT( client_street, \', \', client_postal_code, \' \', client_city ) AS address, '
|
||||
. 'status, client_phone, summary '
|
||||
. 'FROM pp_shop_orders '
|
||||
. 'ORDER BY date_order DESC '
|
||||
. 'LIMIT ' . (int) $limit
|
||||
);
|
||||
|
||||
return $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
319
autoload/Domain/Update/UpdateRepository.php
Normal file
319
autoload/Domain/Update/UpdateRepository.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
namespace Domain\Update;
|
||||
|
||||
class UpdateRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wykonuje aktualizację do następnej wersji.
|
||||
*
|
||||
* @return array{success: bool, log: array, no_updates?: bool}
|
||||
*/
|
||||
public function update(): array
|
||||
{
|
||||
global $settings;
|
||||
|
||||
@file_put_contents( '../libraries/update_log.txt', '' );
|
||||
|
||||
$log = [];
|
||||
$log[] = '[START] Rozpoczęcie aktualizacji - ' . date( 'Y-m-d H:i:s' );
|
||||
$log[] = '[INFO] Aktualna wersja: ' . \S::get_version();
|
||||
|
||||
\S::delete_session( 'new-version' );
|
||||
|
||||
$versionsUrl = 'https://shoppro.project-dc.pl/updates/versions.php?key=' . $settings['update_key'];
|
||||
$versions = @file_get_contents( $versionsUrl );
|
||||
|
||||
if ( $versions === false ) {
|
||||
$log[] = '[ERROR] Nie udało się pobrać listy wersji z: ' . $versionsUrl;
|
||||
$this->saveLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Pobrano listę wersji';
|
||||
$versions = explode( PHP_EOL, $versions );
|
||||
$log[] = '[INFO] Znaleziono ' . count( $versions ) . ' wersji do sprawdzenia';
|
||||
|
||||
foreach ( $versions as $ver ) {
|
||||
$ver = trim( $ver );
|
||||
if ( floatval( $ver ) <= (float) \S::get_version() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Aktualizacja do wersji: ' . $ver;
|
||||
$dir = strlen( $ver ) == 5
|
||||
? substr( $ver, 0, strlen( $ver ) - 2 ) . '0'
|
||||
: substr( $ver, 0, strlen( $ver ) - 1 ) . '0';
|
||||
|
||||
$result = $this->downloadAndApply( $ver, $dir, $log );
|
||||
$this->saveLog( $result['log'] );
|
||||
return $result;
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Brak nowych wersji do zainstalowania';
|
||||
$this->saveLog( $log );
|
||||
return [ 'success' => true, 'log' => $log, 'no_updates' => true ];
|
||||
}
|
||||
|
||||
private function downloadAndApply( 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;
|
||||
$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';
|
||||
$log[] = '[INFO] Katalog roboczy: ' . getcwd();
|
||||
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)';
|
||||
|
||||
// Wykonanie SQL
|
||||
$log = $this->executeSql( $baseUrl . '/ver_' . $ver . '_sql.txt', $log );
|
||||
|
||||
// Usuwanie plików
|
||||
$log = $this->deleteFiles( $baseUrl . '/ver_' . $ver . '_files.txt', $log );
|
||||
|
||||
// Rozpakowywanie ZIP
|
||||
$log = $this->extractZip( 'update.zip', $log );
|
||||
|
||||
// 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 ];
|
||||
}
|
||||
|
||||
private function executeSql( string $sqlUrl, array $log ): array
|
||||
{
|
||||
$log[] = '[INFO] Sprawdzanie aktualizacji SQL: ' . $sqlUrl;
|
||||
|
||||
$ch = curl_init( $sqlUrl );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||
$response = curl_exec( $ch );
|
||||
$contentType = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
|
||||
$httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
curl_close( $ch );
|
||||
|
||||
if ( !$response || strpos( $contentType, 'text/plain' ) === false ) {
|
||||
$log[] = '[INFO] Brak aktualizacji SQL (HTTP: ' . $httpCode . ')';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$queries = explode( PHP_EOL, $response );
|
||||
$log[] = '[OK] Pobrano ' . count( $queries ) . ' zapytań SQL';
|
||||
$success = 0;
|
||||
$errors = 0;
|
||||
|
||||
foreach ( $queries as $query ) {
|
||||
$query = trim( $query );
|
||||
if ( $query !== '' ) {
|
||||
if ( $this->db->query( $query ) ) {
|
||||
$success++;
|
||||
} else {
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Wykonano zapytania SQL - sukces: ' . $success . ', błędy: ' . $errors;
|
||||
return $log;
|
||||
}
|
||||
|
||||
private function deleteFiles( string $filesUrl, array $log ): array
|
||||
{
|
||||
$log[] = '[INFO] Sprawdzanie plików do usunięcia: ' . $filesUrl;
|
||||
|
||||
$ch = curl_init( $filesUrl );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||
$response = curl_exec( $ch );
|
||||
$contentType = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
|
||||
curl_close( $ch );
|
||||
|
||||
if ( !$response || strpos( $contentType, 'text/plain' ) === false ) {
|
||||
$log[] = '[INFO] Brak plików do usunięcia';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$files = explode( PHP_EOL, $response );
|
||||
$deletedFiles = 0;
|
||||
$deletedDirs = 0;
|
||||
|
||||
foreach ( $files as $entry ) {
|
||||
if ( strpos( $entry, 'F: ' ) !== false ) {
|
||||
$path = substr( $entry, 3 );
|
||||
if ( file_exists( $path ) ) {
|
||||
if ( @unlink( $path ) ) {
|
||||
$deletedFiles++;
|
||||
} else {
|
||||
$log[] = '[WARNING] Nie udało się usunąć pliku: ' . $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( strpos( $entry, 'D: ' ) !== false ) {
|
||||
$path = substr( $entry, 3 );
|
||||
if ( is_dir( $path ) ) {
|
||||
\S::delete_dir( $path );
|
||||
$deletedDirs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Usunięto plików: ' . $deletedFiles . ', katalogów: ' . $deletedDirs;
|
||||
return $log;
|
||||
}
|
||||
|
||||
private function extractZip( string $fileName, array $log ): array
|
||||
{
|
||||
$log[] = '[INFO] Rozpoczęcie rozpakowywania pliku ZIP';
|
||||
|
||||
$path = pathinfo( realpath( $fileName ), PATHINFO_DIRNAME );
|
||||
$path = substr( $path, 0, strlen( $path ) - 5 );
|
||||
|
||||
if ( !is_dir( $path ) || !is_writable( $path ) ) {
|
||||
$log[] = '[ERROR] Ścieżka docelowa nie istnieje lub brak uprawnień: ' . $path;
|
||||
return $log;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
$res = $zip->open( $fileName );
|
||||
|
||||
if ( $res !== true ) {
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku ZIP (kod: ' . $res . ')';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$log[] = '[OK] Otwarto archiwum ZIP, liczba plików: ' . $zip->numFiles;
|
||||
$extracted = 0;
|
||||
$errors = 0;
|
||||
|
||||
for ( $i = 0; $i < $zip->numFiles; $i++ ) {
|
||||
$filename = str_replace( '\\', '/', $zip->getNameIndex( $i ) );
|
||||
|
||||
if ( substr( $filename, -1 ) === '/' ) {
|
||||
$dirPath = $path . '/' . $filename;
|
||||
if ( !is_dir( $dirPath ) ) {
|
||||
@mkdir( $dirPath, 0755, true );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetFile = $path . '/' . $filename;
|
||||
$targetDir = dirname( $targetFile );
|
||||
|
||||
if ( !is_dir( $targetDir ) ) {
|
||||
@mkdir( $targetDir, 0755, true );
|
||||
}
|
||||
|
||||
$existed = file_exists( $targetFile );
|
||||
$content = $zip->getFromIndex( $i );
|
||||
|
||||
if ( $content === false ) {
|
||||
$log[] = '[ERROR] Nie udało się odczytać z ZIP: ' . $filename;
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( @file_put_contents( $targetFile, $content ) === false ) {
|
||||
$log[] = '[ERROR] Nie udało się zapisać: ' . $filename;
|
||||
$errors++;
|
||||
} else {
|
||||
$tag = $existed ? '[UPDATED]' : '[NEW]';
|
||||
$log[] = $tag . ' ' . $filename . ' (' . strlen( $content ) . ' bajtów)';
|
||||
$extracted++;
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[OK] Rozpakowano ' . $extracted . ' plików, błędów: ' . $errors;
|
||||
$zip->close();
|
||||
|
||||
if ( @unlink( $fileName ) ) {
|
||||
$log[] = '[OK] Usunięto plik update.zip';
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
private function saveLog( array $log ): void
|
||||
{
|
||||
@file_put_contents( '../libraries/update_log.txt', implode( "\n", $log ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wykonuje zaległe migracje z tabeli pp_updates.
|
||||
*/
|
||||
public function runPendingMigrations(): void
|
||||
{
|
||||
$results = $this->db->select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] );
|
||||
if ( !is_array( $results ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $results as $row ) {
|
||||
$method = $row['name'];
|
||||
if ( method_exists( $this, $method ) ) {
|
||||
$this->$method();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function update0197(): void
|
||||
{
|
||||
$rows = $this->db->select( 'pp_shop_order_products', [ 'id', 'product_id' ], [ 'parent_product_id' => null ] );
|
||||
|
||||
if ( is_array( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$parentId = $this->db->get( 'pp_shop_products', 'parent_id', [ 'id' => $row['product_id'] ] );
|
||||
$this->db->update( 'pp_shop_order_products', [
|
||||
'parent_product_id' => $parentId ?: $row['product_id'],
|
||||
], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_updates', [ 'done' => 1 ], [ 'name' => 'update0197' ] );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user