Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ee4116f50 | |||
| a6b821bb75 |
@@ -115,3 +115,10 @@ 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:
|
||||
|
||||
# The language backend to use for this project.
|
||||
# If not set, the global setting from serena_config.yml is used.
|
||||
# Valid values: LSP, JetBrains
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
16
CLAUDE.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Workflow
|
||||
|
||||
## KONIEC PRACY
|
||||
|
||||
Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
|
||||
|
||||
1. Przeprowadzenie testów.
|
||||
2. Aktualizacja dokumentacji technicznej, jeśli zmiany tego wymagają:
|
||||
- `docs/PROJECT_STRUCTURE.md`
|
||||
- `docs/FORM_EDIT_SYSTEM.md`
|
||||
3. Migracje SQL (jeśli były zmiany w bazie danych):
|
||||
- Plik: `migrations/{version}.sql` (np. `migrations/0.304.sql`)
|
||||
- **NIE** w `updates/` — build script sam wczyta z `migrations/`
|
||||
- Sprawdź czy plik istnieje i jest poprawnie nazwany przed commitem
|
||||
4. Commit.
|
||||
5. Push.
|
||||
@@ -4,14 +4,20 @@ function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( $c == 'Savant3' )
|
||||
{
|
||||
require_once( '../autoload/Savant3.php' );
|
||||
return true;
|
||||
}
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
|
||||
|
||||
@@ -16,9 +16,14 @@ function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
|
||||
|
||||
10
ajax.php
@@ -4,10 +4,14 @@ function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
10
api.php
@@ -4,10 +4,14 @@ function __autoload_my_classes($classname)
|
||||
{
|
||||
$q = explode('\\', $classname);
|
||||
$c = array_pop($q);
|
||||
$f = 'autoload/' . implode('/', $q) . '/class.' . $c . '.php';
|
||||
|
||||
if (file_exists($f))
|
||||
require_once($f);
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode('/', $q) . '/class.' . $c . '.php';
|
||||
if (file_exists($f)) { require_once($f); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode('/', $q) . '/' . $c . '.php';
|
||||
if (file_exists($f)) require_once($f);
|
||||
}
|
||||
spl_autoload_register('__autoload_my_classes');
|
||||
date_default_timezone_set('Europe/Warsaw');
|
||||
|
||||
213
autoload/Domain/Languages/LanguagesRepository.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
namespace Domain\Languages;
|
||||
|
||||
class LanguagesRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Odczyt
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function languagesList(): array
|
||||
{
|
||||
return $this->db->select( 'pp_langs', '*', [ 'ORDER' => [ 'o' => 'ASC' ] ] ) ?: [];
|
||||
}
|
||||
|
||||
public function languageDetails( string $languageId ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_langs', '*', [ 'id' => $languageId ] ) ?: null;
|
||||
}
|
||||
|
||||
public function availableDomains(): array
|
||||
{
|
||||
return $this->db->query(
|
||||
'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL GROUP BY domain'
|
||||
)->fetchAll( \PDO::FETCH_ASSOC ) ?: [];
|
||||
}
|
||||
|
||||
public function defaultDomain(): ?string
|
||||
{
|
||||
$results = $this->db->query(
|
||||
'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL AND main_domain = 1'
|
||||
)->fetchAll();
|
||||
return $results[0][0] ?? null;
|
||||
}
|
||||
|
||||
public function defaultLanguage( string $domain = '' ): ?string
|
||||
{
|
||||
if ( !$default = \Shared\Cache\CacheHandler::fetch( "default_language:$domain" ) )
|
||||
{
|
||||
if ( $domain )
|
||||
$results = $this->db->query(
|
||||
'SELECT id FROM pp_langs WHERE status = 1 AND domain = \'' . $domain . '\' ORDER BY start DESC, o ASC LIMIT 1'
|
||||
)->fetchAll();
|
||||
|
||||
if ( !$domain || !$this->defaultDomain() )
|
||||
$results = $this->db->query(
|
||||
'SELECT id FROM pp_langs WHERE status = 1 AND domain IS NULL ORDER BY start DESC, o ASC LIMIT 1'
|
||||
)->fetchAll();
|
||||
|
||||
$default = $results[0][0] ?? null;
|
||||
\Shared\Cache\CacheHandler::store( "default_language:$domain", $default );
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function activeLanguages(): array
|
||||
{
|
||||
if ( !$active = \Shared\Cache\CacheHandler::fetch( 'active_languages' ) )
|
||||
{
|
||||
$active = $this->db->select( 'pp_langs', [ 'id', 'name', 'domain' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] ) ?: [];
|
||||
\Shared\Cache\CacheHandler::store( 'active_languages', $active );
|
||||
}
|
||||
return $active;
|
||||
}
|
||||
|
||||
public function langTranslations( string $language = 'pl' ): array
|
||||
{
|
||||
if ( !$translations = \Shared\Cache\CacheHandler::fetch( "lang_translations:$language" ) )
|
||||
{
|
||||
$translations = [ '0' => $language ];
|
||||
|
||||
$results = $this->db->select( 'pp_langs_translations', [ 'text', $language ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$translations[ $row['text'] ] = $row[ $language ];
|
||||
|
||||
\Shared\Cache\CacheHandler::store( "lang_translations:$language", $translations );
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
|
||||
public function translationDetails( int $translationId ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_langs_translations', '*', [ 'id' => $translationId ] ) ?: null;
|
||||
}
|
||||
|
||||
public function maxOrder(): int
|
||||
{
|
||||
return (int) $this->db->max( 'pp_langs', 'o' );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Zapis / usuwanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function languageSave( string $languageId, string $name, $status, $start, $o, $domain, $main_domain ): string
|
||||
{
|
||||
if ( $start == 'on' && $status == 'on' && !\S::get_domain( $domain ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 0 ], [ 'id[!]' => $languageId ] );
|
||||
|
||||
if ( $start == 'on' && $status == 'on' && \S::get_domain( $domain ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 0 ], [
|
||||
'AND' => [ 'id[!]' => $languageId, 'domain' => \S::get_domain( $domain ) ]
|
||||
] );
|
||||
|
||||
if ( $main_domain == 'on' && $domain && $status == 'on' )
|
||||
$this->db->update( 'pp_langs', [ 'main_domain' => 0 ], [ ' id[!]' => $languageId ] );
|
||||
|
||||
if ( $this->db->count( 'pp_langs', [ 'id' => $languageId ] ) )
|
||||
{
|
||||
$this->db->update( 'pp_langs', [
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'name' => $name,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ?: null,
|
||||
'main_domain' => $main_domain == 'on' && \S::get_domain( $domain ) ? 1 : 0,
|
||||
], [ 'id' => $languageId ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $this->db->query( 'ALTER TABLE pp_langs_translations ADD ' . strtolower( $languageId ) . ' TEXT NULL DEFAULT NULL' ) )
|
||||
{
|
||||
$this->db->insert( 'pp_langs', [
|
||||
'id' => strtolower( $languageId ),
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ?: null,
|
||||
'main_domain' => $main_domain == 'on' && \S::get_domain( $domain ) ? 1 : 0,
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
// Upewnij się, że każda domena ma język startowy
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ] ] ) )
|
||||
{
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => null ] ] ) )
|
||||
{
|
||||
if ( $idTmp = $this->db->get( 'pp_langs', 'id', [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $idTmp ] );
|
||||
}
|
||||
}
|
||||
|
||||
$domains = $this->db->select( 'pp_langs', 'domain', [ 'domain[!]' => null, 'GROUP' => 'domain' ] );
|
||||
if ( is_array( $domains ) && !empty( $domains ) )
|
||||
{
|
||||
$this->db->update( 'pp_langs', [ 'start' => 0 ], [ 'domain' => null ] );
|
||||
foreach ( $domains as $dom )
|
||||
{
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => $dom ] ] ) )
|
||||
{
|
||||
if ( $idTmp = $this->db->get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain' => $dom ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $idTmp ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'main_domain' => 1 ] ] ) )
|
||||
{
|
||||
if ( $idTmp = $this->db->get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$this->db->update( 'pp_langs', [ 'main_domain' => 1 ], [ 'id' => $idTmp ] );
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $languageId;
|
||||
}
|
||||
|
||||
public function languageDelete( string $languageId ): bool
|
||||
{
|
||||
if ( $this->db->count( 'pp_langs' ) > 1 )
|
||||
{
|
||||
if ( $this->db->query( 'ALTER TABLE pp_langs_translations DROP ' . $languageId )
|
||||
&& $this->db->delete( 'pp_langs', [ 'id' => $languageId ] ) )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function translationSave( $translationId, string $text, array $languages = [] ): int
|
||||
{
|
||||
if ( $translationId )
|
||||
{
|
||||
$this->db->update( 'pp_langs_translations', [ 'text' => $text ], [ 'id' => $translationId ] );
|
||||
foreach ( $languages as $key => $val )
|
||||
$this->db->update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translationId ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->insert( 'pp_langs_translations', [ 'text' => $text ] );
|
||||
$translationId = $this->db->id();
|
||||
foreach ( $languages as $key => $val )
|
||||
$this->db->update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translationId ] );
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return (int) $translationId;
|
||||
}
|
||||
|
||||
public function translationDelete( int $translationId ): bool
|
||||
{
|
||||
return (bool) $this->db->delete( 'pp_langs_translations', [ 'id' => $translationId ] );
|
||||
}
|
||||
}
|
||||
72
autoload/Domain/Settings/SettingsRepository.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Domain\Settings;
|
||||
|
||||
class SettingsRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca wszystkie ustawienia jako tablicę asocjacyjną param => value.
|
||||
* Wynik jest cache'owany (TTL 24h).
|
||||
*/
|
||||
public function allSettings(): array
|
||||
{
|
||||
if ( !$settings = \Shared\Cache\CacheHandler::fetch( 'settings_details' ) )
|
||||
{
|
||||
$results = $this->db->select( 'pp_settings', '*' );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$settings[ $row['param'] ] = $row['value'];
|
||||
|
||||
\Shared\Cache\CacheHandler::store( 'settings_details', $settings ?? [] );
|
||||
}
|
||||
|
||||
return $settings ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert jednego parametru.
|
||||
*/
|
||||
public function update( string $param, $value ): bool
|
||||
{
|
||||
if ( $this->db->count( 'pp_settings', [ 'param' => $param ] ) )
|
||||
return (bool) $this->db->update( 'pp_settings', [ 'value' => $value ], [ 'param' => $param ] );
|
||||
else
|
||||
return (bool) $this->db->insert( 'pp_settings', [ 'param' => $param, 'value' => $value ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapisuje zbiorczo ustawienia (TRUNCATE + INSERT).
|
||||
* Czyści cache i regeneruje .htaccess.
|
||||
*
|
||||
* @param array $data Tablica asocjacyjna [ 'param' => value, ... ]
|
||||
*/
|
||||
public function save( array $data ): bool
|
||||
{
|
||||
$this->db->query( 'TRUNCATE pp_settings' );
|
||||
|
||||
$rows = [];
|
||||
foreach ( $data as $param => $value )
|
||||
$rows[] = [ 'param' => $param, 'value' => $value ];
|
||||
|
||||
$this->db->insert( 'pp_settings', $rows );
|
||||
|
||||
\S::delete_cache();
|
||||
\S::htacces();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca bieżącą wartość licznika odwiedzin.
|
||||
*/
|
||||
public function visitCounter(): ?string
|
||||
{
|
||||
return $this->db->get( 'pp_settings', 'value', [ 'param' => 'visits' ] ) ?: null;
|
||||
}
|
||||
}
|
||||
235
autoload/Domain/User/UserRepository.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
namespace Domain\User;
|
||||
|
||||
class UserRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Odczyt
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function find( int $userId ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_users', '*', [ 'id' => $userId ] ) ?: null;
|
||||
}
|
||||
|
||||
public function findByLogin( string $login ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_users', '*', [ 'login' => $login ] ) ?: null;
|
||||
}
|
||||
|
||||
public function all(): array
|
||||
{
|
||||
return $this->db->select( 'pp_users', '*' ) ?: [];
|
||||
}
|
||||
|
||||
public function privileges( int $userId ): array
|
||||
{
|
||||
return $this->db->select( 'pp_users_privileges', '*', [ 'id_user' => $userId ] ) ?: [];
|
||||
}
|
||||
|
||||
public function hasPrivilege( string $name, int $userId ): bool
|
||||
{
|
||||
if ( $userId === 1 )
|
||||
return true;
|
||||
|
||||
if ( !$result = \Shared\Cache\CacheHandler::fetch( "check_privileges:$userId:$name-tmp" ) )
|
||||
{
|
||||
$result = $this->db->count( 'pp_users_privileges', [ 'AND' => [ 'name' => $name, 'id_user' => $userId ] ] );
|
||||
\Shared\Cache\CacheHandler::store( "check_privileges:$userId:$name", $result );
|
||||
}
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Logowanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Weryfikuje login i hasło.
|
||||
* @return int 1 = OK, 0 = złe dane, -1 = konto zablokowane
|
||||
*/
|
||||
public function logon( string $login, string $password ): int
|
||||
{
|
||||
if ( !$this->db->get( 'pp_users', '*', [ 'login' => $login ] ) )
|
||||
return 0;
|
||||
|
||||
if ( !$this->db->get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'error_logged_count[<]' => 5 ] ] ) )
|
||||
return -1;
|
||||
|
||||
if ( $this->db->get( 'pp_users', '*', [
|
||||
'AND' => [
|
||||
'login' => $login,
|
||||
'status' => 1,
|
||||
'password' => md5( $password ),
|
||||
'OR' => [ 'active_to[>=]' => date( 'Y-m-d' ), 'active_to' => null ]
|
||||
]
|
||||
] ) ) {
|
||||
$this->db->update( 'pp_users', [ 'last_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count' => 0 ], [ 'login' => $login ] );
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_users', [ 'last_error_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count[+]' => 1 ], [ 'login' => $login ] );
|
||||
if ( $this->db->get( 'pp_users', 'error_logged_count', [ 'login' => $login ] ) >= 5 )
|
||||
{
|
||||
$this->db->update( 'pp_users', [ 'status' => 0 ], [ 'login' => $login ] );
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isLoginTaken( string $login, int $excludeId = 0 ): bool
|
||||
{
|
||||
return (bool) $this->db->get( 'pp_users', 'login', [ 'AND' => [ 'login' => $login, 'id[!]' => $excludeId ] ] );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2FA
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function update( int $userId, array $data ): bool
|
||||
{
|
||||
return (bool) $this->db->update( 'pp_users', $data, [ 'id' => $userId ] );
|
||||
}
|
||||
|
||||
public function sendTwofaCode( int $userId, bool $resend = false ): bool
|
||||
{
|
||||
$user = $this->find( $userId );
|
||||
if ( !$user ) return false;
|
||||
|
||||
if ( (int)$user['twofa_enabled'] !== 1 ) return false;
|
||||
|
||||
$to = $user['twofa_email'] ?: $user['login'];
|
||||
if ( !filter_var( $to, FILTER_VALIDATE_EMAIL ) ) return false;
|
||||
|
||||
if ( $resend && !empty( $user['twofa_sent_at'] ) )
|
||||
{
|
||||
$last = strtotime( $user['twofa_sent_at'] );
|
||||
if ( $last && ( time() - $last ) < 30 ) return false;
|
||||
}
|
||||
|
||||
$code = random_int( 100000, 999999 );
|
||||
$hash = password_hash( (string)$code, PASSWORD_DEFAULT );
|
||||
|
||||
$this->update( $userId, [
|
||||
'twofa_code_hash' => $hash,
|
||||
'twofa_expires_at' => date( 'Y-m-d H:i:s', time() + 10 * 60 ),
|
||||
'twofa_sent_at' => date( 'Y-m-d H:i:s' ),
|
||||
'twofa_failed_attempts' => 0,
|
||||
] );
|
||||
|
||||
$subject = 'Twój kod logowania 2FA';
|
||||
$body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie – zignoruj tę wiadomość i poinformuj administratora.";
|
||||
|
||||
$sent = \S::send_email( $to, $subject, $body );
|
||||
if ( !$sent )
|
||||
{
|
||||
$headers = "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-type: text/plain; charset=UTF-8\r\n";
|
||||
$headers .= "From: no-reply@" . ( $_SERVER['HTTP_HOST'] ?? 'localhost' ) . "\r\n";
|
||||
$sent = mail( $to, mb_encode_mimeheader( $subject, 'UTF-8' ), $body, $headers );
|
||||
}
|
||||
return (bool) $sent;
|
||||
}
|
||||
|
||||
public function verifyTwofaCode( int $userId, string $code ): bool
|
||||
{
|
||||
$user = $this->find( $userId );
|
||||
if ( !$user ) return false;
|
||||
|
||||
if ( (int)$user['twofa_failed_attempts'] >= 5 ) return false;
|
||||
|
||||
if ( empty( $user['twofa_expires_at'] ) || time() > strtotime( $user['twofa_expires_at'] ) )
|
||||
{
|
||||
$this->update( $userId, [ 'twofa_code_hash' => null, 'twofa_expires_at' => null ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
$ok = !empty( $user['twofa_code_hash'] ) && password_verify( $code, $user['twofa_code_hash'] );
|
||||
if ( $ok )
|
||||
{
|
||||
$this->update( $userId, [
|
||||
'twofa_code_hash' => null,
|
||||
'twofa_expires_at' => null,
|
||||
'twofa_sent_at' => null,
|
||||
'twofa_failed_attempts' => 0,
|
||||
'last_logged' => date( 'Y-m-d H:i:s' ),
|
||||
] );
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->update( $userId, [
|
||||
'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1,
|
||||
'last_error_logged' => date( 'Y-m-d H:i:s' ),
|
||||
] );
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Zapis / usuwanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function save(
|
||||
$userId, string $login, $status, $activeTo, string $password, string $passwordRe,
|
||||
$admin, $privileges, $twofaEnabled = 0, string $twofaEmail = ''
|
||||
): array {
|
||||
$this->db->delete( 'pp_users_privileges', [ 'id_user' => (int)$userId ] );
|
||||
|
||||
if ( !$userId )
|
||||
{
|
||||
if ( strlen( $password ) < 5 )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
|
||||
if ( $password !== $passwordRe )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ];
|
||||
|
||||
$this->db->insert( 'pp_users', [
|
||||
'login' => $login,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $activeTo === '' ? null : $activeTo,
|
||||
'admin' => $admin,
|
||||
'password' => md5( $password ),
|
||||
'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofaEmail,
|
||||
] );
|
||||
$userId = $this->db->get( 'pp_users', 'id', [ 'ORDER' => [ 'id' => 'DESC' ] ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $password && strlen( $password ) < 5 )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
|
||||
if ( $password && $password !== $passwordRe )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ];
|
||||
|
||||
if ( $password )
|
||||
$this->db->update( 'pp_users', [ 'password' => md5( $password ) ], [ 'id' => (int)$userId ] );
|
||||
|
||||
$this->db->update( 'pp_users', [
|
||||
'login' => $login,
|
||||
'admin' => $admin,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $activeTo === '' ? null : $activeTo,
|
||||
'error_logged_count' => 0,
|
||||
'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofaEmail,
|
||||
], [ 'id' => (int)$userId ] );
|
||||
}
|
||||
|
||||
$privileges = (array)$privileges;
|
||||
foreach ( $privileges as $pri )
|
||||
$this->db->insert( 'pp_users_privileges', [ 'name' => $pri, 'id_user' => $userId ] );
|
||||
|
||||
\S::delete_cache();
|
||||
return [ 'status' => 'ok', 'msg' => 'Użytkownik został zapisany.' ];
|
||||
}
|
||||
|
||||
public function delete( int $userId ): bool
|
||||
{
|
||||
return (bool) $this->db->delete( 'pp_users', [ 'id' => $userId ] );
|
||||
}
|
||||
}
|
||||
47
autoload/Shared/Cache/CacheHandler.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Shared\Cache;
|
||||
|
||||
class CacheHandler
|
||||
{
|
||||
public static function store( $key, $data, $ttl = 86400 )
|
||||
{
|
||||
file_put_contents( self::get_file_name( $key ), gzdeflate( serialize( array( time() + $ttl, $data ) ) ) );
|
||||
}
|
||||
|
||||
private static function get_file_name( $key )
|
||||
{
|
||||
$md5 = md5( $key );
|
||||
$dir = 'temp/' . $md5[0] . '/' . $md5[1] . '/';
|
||||
|
||||
if ( !is_dir( $dir ) )
|
||||
mkdir( $dir, 0755, true );
|
||||
|
||||
return $dir . 's_cache_' . $md5;
|
||||
}
|
||||
|
||||
public static function fetch( $key )
|
||||
{
|
||||
$filename = self::get_file_name( $key );
|
||||
|
||||
if ( !file_exists( $filename ) || !is_readable( $filename ) )
|
||||
return false;
|
||||
|
||||
$data = gzinflate( file_get_contents( $filename ) );
|
||||
|
||||
$data = @unserialize( $data );
|
||||
if ( !$data )
|
||||
{
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( time() > $data[0] )
|
||||
{
|
||||
if ( file_exists( $filename ) )
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data[1];
|
||||
}
|
||||
}
|
||||
1281
autoload/Shared/Helpers/Helpers.php
Normal file
93
autoload/Shared/Html/Html.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace Shared\Html;
|
||||
|
||||
class Html
|
||||
{
|
||||
public static function form_text( array $params = array() )
|
||||
{
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/form-text' );
|
||||
}
|
||||
|
||||
public static function input_switch( array $params = array() )
|
||||
{
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/input-switch' );
|
||||
}
|
||||
|
||||
public static function select( array $params = array() )
|
||||
{
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/select' );
|
||||
}
|
||||
|
||||
public static function textarea( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'rows' => 4,
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/textarea' );
|
||||
}
|
||||
|
||||
public static function input_icon( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/input-icon' );
|
||||
}
|
||||
|
||||
public static function input( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/input' );
|
||||
}
|
||||
|
||||
public static function button( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'class' => 'btn-sm btn-info',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/button' );
|
||||
}
|
||||
|
||||
public static function panel( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'title' => 'panel-title',
|
||||
'class' => 'panel-primary',
|
||||
'content' => 'panel-content'
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/panel' );
|
||||
}
|
||||
}
|
||||
314
autoload/Shared/Image/ImageManipulator.php
Normal file
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
namespace Shared\Image;
|
||||
|
||||
class ImageManipulator
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $image;
|
||||
protected $img_src;
|
||||
|
||||
/**
|
||||
* Image manipulator constructor
|
||||
*
|
||||
* @param string $file OPTIONAL Path to image file or image data as string
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($file = null)
|
||||
{
|
||||
if (null !== $file) {
|
||||
if (is_file($file)) {
|
||||
$this->img_src = $file;
|
||||
$this->setImageFile($file);
|
||||
} else {
|
||||
echo 'a'; exit;
|
||||
$this->setImageString($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from file
|
||||
*
|
||||
* @param string $file Path to image file
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setImageFile($file)
|
||||
{
|
||||
if (!(is_readable($file) && is_file($file))) {
|
||||
throw new \InvalidArgumentException("Image file $file is not readable");
|
||||
}
|
||||
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
list ($this->width, $this->height, $type) = getimagesize($file);
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
$this->image = imagecreatefromgif($file);
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
$this->image = imagecreatefromjpeg($file);
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
$this->image = imagecreatefrompng($file);
|
||||
break;
|
||||
default :
|
||||
throw new \InvalidArgumentException("Image type $type not supported");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from string data
|
||||
*
|
||||
* @param string $data
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setImageString($data)
|
||||
{
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
if (!$this->image = imagecreatefromstring($data)) {
|
||||
throw new \RuntimeException('Cannot create image from data string');
|
||||
}
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resamples the current image
|
||||
*
|
||||
* @param int $width New width
|
||||
* @param int $height New height
|
||||
* @param bool $constrainProportions Constrain current image proportions when resizing
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function resample( $width, $height, $constrainProportions = true )
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
if ($constrainProportions) {
|
||||
if ($this->height >= $this->width) {
|
||||
$width = round($height / $this->height * $this->width);
|
||||
} else {
|
||||
$height = round($width / $this->width * $this->height);
|
||||
}
|
||||
}
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
|
||||
imagecopyresampled($temp, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
|
||||
|
||||
if ( function_exists('exif_read_data') )
|
||||
{
|
||||
$exif = exif_read_data( $this->img_src );
|
||||
if ( $exif && isset($exif['Orientation']) )
|
||||
{
|
||||
$orientation = $exif['Orientation'];
|
||||
if ( $orientation != 1 )
|
||||
{
|
||||
$deg = 0;
|
||||
switch ($orientation)
|
||||
{
|
||||
case 3:
|
||||
$deg = 180;
|
||||
break;
|
||||
case 6:
|
||||
$deg = 270;
|
||||
break;
|
||||
case 8:
|
||||
$deg = 90;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $deg )
|
||||
$temp = imagerotate( $temp, $deg, 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarge canvas
|
||||
*
|
||||
* @param int $width Canvas width
|
||||
* @param int $height Canvas height
|
||||
* @param array $rgb RGB colour values
|
||||
* @param int $xpos X-Position of image in new canvas, null for centre
|
||||
* @param int $ypos Y-Position of image in new canvas, null for centre
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function enlargeCanvas($width, $height, array $rgb = array(), $xpos = null, $ypos = null)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
|
||||
$width = max($width, $this->width);
|
||||
$height = max($height, $this->height);
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
if (count($rgb) == 3) {
|
||||
$bg = imagecolorallocate($temp, $rgb[0], $rgb[1], $rgb[2]);
|
||||
imagefill($temp, 0, 0, $bg);
|
||||
}
|
||||
|
||||
if (null === $xpos) {
|
||||
$xpos = round(($width - $this->width) / 2);
|
||||
}
|
||||
if (null === $ypos) {
|
||||
$ypos = round(($height - $this->height) / 2);
|
||||
}
|
||||
|
||||
imagecopy($temp, $this->image, (int) $xpos, (int) $ypos, 0, 0, $this->width, $this->height);
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop image
|
||||
*
|
||||
* @param int|array $x1 Top left x-coordinate of crop box or array of coordinates
|
||||
* @param int $y1 Top left y-coordinate of crop box
|
||||
* @param int $x2 Bottom right x-coordinate of crop box
|
||||
* @param int $y2 Bottom right y-coordinate of crop box
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function crop($x1, $y1 = 0, $x2 = 0, $y2 = 0)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
if (is_array($x1) && 4 == count($x1)) {
|
||||
list($x1, $y1, $x2, $y2) = $x1;
|
||||
}
|
||||
|
||||
$x1 = max($x1, 0);
|
||||
$y1 = max($y1, 0);
|
||||
|
||||
$x2 = min($x2, $this->width);
|
||||
$y2 = min($y2, $this->height);
|
||||
|
||||
$width = $x2 - $x1;
|
||||
$height = $y2 - $y1;
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
imagecopy($temp, $this->image, 0, 0, $x1, $y1, $width, $height);
|
||||
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace current image resource with a new one
|
||||
*
|
||||
* @param resource $res New image resource
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function _replace($res)
|
||||
{
|
||||
if (!is_resource($res)) {
|
||||
throw new \UnexpectedValueException('Invalid resource');
|
||||
}
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
$this->image = $res;
|
||||
$this->width = imagesx($res);
|
||||
$this->height = imagesy($res);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current image to file
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($fileName, $type = IMAGETYPE_JPEG)
|
||||
{
|
||||
$dir = dirname($fileName);
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
throw new \RuntimeException('Error creating directory ' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
if (!imagegif($this->image, $fileName)) {
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
if (!imagepng($this->image, $fileName)) {
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
default :
|
||||
if (!imagejpeg($this->image, $fileName, 95)) {
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
throw new \RuntimeException('Error saving image file to ' . $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GD image resource
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image resource width
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image height
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
||||
75
autoload/Shared/Tpl/Tpl.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
namespace Shared\Tpl;
|
||||
|
||||
class Tpl
|
||||
{
|
||||
protected $dir = 'templates/';
|
||||
protected $vars = array();
|
||||
|
||||
function __construct( $dir = null )
|
||||
{
|
||||
if ( $dir !== null )
|
||||
$this->dir = $dir;
|
||||
}
|
||||
|
||||
public static function view( $file, $values = '' )
|
||||
{
|
||||
$tpl = new self;
|
||||
if ( is_array( $values ) ) foreach ( $values as $key => $val )
|
||||
$tpl->$key = $val;
|
||||
return $tpl->render( $file );
|
||||
}
|
||||
|
||||
public function secureHTML( $val )
|
||||
{
|
||||
$out = stripslashes( $val );
|
||||
$out = str_replace( "'", "'", $out );
|
||||
$out = str_replace( '"', """, $out );
|
||||
$out = str_replace( "<", "<", $out );
|
||||
$out = str_replace( ">", ">", $out );
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function render( $file )
|
||||
{
|
||||
if ( file_exists( 'templates_user/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates_user/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( 'templates/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else
|
||||
return '<div class="alert alert-danger" role="alert">Nie znaleziono pliku widoku: <b>' . $this->dir . $file . '.php</b>';
|
||||
}
|
||||
|
||||
public function __set( $name, $value )
|
||||
{
|
||||
$this->vars[ $name ] = $value;
|
||||
}
|
||||
|
||||
public function __get( $name )
|
||||
{
|
||||
return $this->vars[ $name ];
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,11 @@ class Site
|
||||
|
||||
if (!\admin\factory\Users::send_twofa_code((int)$user['id']))
|
||||
{
|
||||
\S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.');
|
||||
// E-mail nie dotarł — użytkownik podał poprawne dane, więc przepuszczamy
|
||||
\S::delete_session('twofa_pending');
|
||||
header('Location: /admin/');
|
||||
\S::alert('Nie udało się wysłać kodu 2FA — zalogowano bez weryfikacji e-mail.', 'alert-warning');
|
||||
self::finalize_admin_login($user, $domain, $cookie_name, (bool)\S::get('remember'));
|
||||
header('Location: /admin/articles/view_list/');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,181 +1,64 @@
|
||||
<?
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Languages\LanguagesRepository przez DI.
|
||||
*/
|
||||
class Languages
|
||||
{
|
||||
public static function available_domains()
|
||||
private static function repo(): \Domain\Languages\LanguagesRepository
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> query( 'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL GROUP BY domain' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
return new \Domain\Languages\LanguagesRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function default_domain()
|
||||
|
||||
public static function available_domains(): array
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> query( 'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL AND main_domain = 1' ) -> fetchAll();
|
||||
return $default_domain = $results[0][0];
|
||||
return self::repo()->availableDomains();
|
||||
}
|
||||
|
||||
public static function translation_delete( $translation_id )
|
||||
|
||||
public static function default_domain(): ?string
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> delete( 'pp_langs_translations', [ 'id' => $translation_id ] );
|
||||
return self::repo()->defaultDomain();
|
||||
}
|
||||
|
||||
|
||||
public static function translation_delete( $translation_id ): bool
|
||||
{
|
||||
return self::repo()->translationDelete( (int)$translation_id );
|
||||
}
|
||||
|
||||
public static function translation_save( $translation_id, $text, $languages )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $translation_id )
|
||||
{
|
||||
$mdb -> update( 'pp_langs_translations', [ 'text' => $text ], [ 'id' => $translation_id ] );
|
||||
if ( is_array( $languages ) and !empty( $languages ) ): foreach ( $languages as $key => $val ):
|
||||
$mdb -> update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translation_id ] );
|
||||
endforeach; endif;
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $translation_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> insert( 'pp_langs_translations', [ 'text' => $text ] );
|
||||
if ( $translation_id = $mdb -> id() )
|
||||
{
|
||||
if ( is_array( $languages ) and !empty( $languages ) ): foreach ( $languages as $key => $val ):
|
||||
$mdb -> update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translation_id ] );
|
||||
endforeach; endif;
|
||||
}
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $translation_id;
|
||||
}
|
||||
}
|
||||
|
||||
public static function translation_details( $translation_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_langs_translations', '*', [ 'id' => $translation_id ] );
|
||||
}
|
||||
|
||||
public static function language_delete( $language_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $mdb -> count( 'pp_langs' ) > 1 )
|
||||
{
|
||||
if ( $mdb -> query( 'ALTER TABLE pp_langs_translations DROP ' . $language_id )
|
||||
and
|
||||
$mdb -> delete( 'pp_langs', [ 'id' => $language_id ] )
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return self::repo()->translationSave( $translation_id, (string)$text, (array)$languages );
|
||||
}
|
||||
|
||||
public static function max_order()
|
||||
public static function translation_details( $translation_id ): ?array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> max( 'pp_langs', 'o' );
|
||||
return self::repo()->translationDetails( (int)$translation_id );
|
||||
}
|
||||
|
||||
public static function language_save( $language_id, $name, $status, $start, $o, $domain, $main_domain )
|
||||
public static function language_delete( $language_id ): bool
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $start == 'on' and $status == 'on' and !\S::get_domain( $domain ) )
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'start' => 0
|
||||
], [
|
||||
'id[!]' => $language_id
|
||||
] );
|
||||
|
||||
if ( $start == 'on' and $status == 'on' and \S::get_domain( $domain ) )
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'start' => 0
|
||||
], [
|
||||
'AND' => [ 'id[!]' => $language_id, 'domain' => \S::get_domain( $domain ) ]
|
||||
] );
|
||||
|
||||
if ( $main_domain == 'on' and $domain and $status == 'on' )
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'main_domain' => 0
|
||||
], [
|
||||
' id[!]' => $language_id
|
||||
] );
|
||||
|
||||
if ( $mdb -> count( 'pp_langs', [ 'id' => $language_id ] ) )
|
||||
{
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'name' => $name,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ? \S::get_domain( $domain ) : null,
|
||||
'main_domain' => $main_domain == 'on' and \S::get_domain( $domain ) ? 1 : 0,
|
||||
], [
|
||||
'id' => $language_id
|
||||
] );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $mdb -> query( 'ALTER TABLE pp_langs_translations ADD ' . strtolower( $language_id ) . ' TEXT NULL DEFAULT NULL' ) )
|
||||
{
|
||||
$mdb -> insert( 'pp_langs', [
|
||||
'id' => strtolower( $language_id ),
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ? \S::get_domain( $domain ) : null,
|
||||
'main_domain' => $main_domain == 'on' && \S::get_domain( $domain ) ? 1 : 0,
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ] ] ) )
|
||||
{
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => null ] ] ) )
|
||||
{
|
||||
if ( $id_tmp = $mdb -> get( 'pp_langs', 'id', [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$mdb -> update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $id_tmp ] );
|
||||
}
|
||||
}
|
||||
|
||||
$domains = $mdb -> select( 'pp_langs', 'domain', [ 'domain[!]' => null, 'GROUP' => 'domain'] );
|
||||
if ( is_array( $domains ) and !empty( $domains ) )
|
||||
{
|
||||
$mdb -> update( 'pp_langs', [ 'start' => 0 ], [ 'domain' => null ] );
|
||||
foreach ( $domains as $domain )
|
||||
{
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => $domain ] ] ) )
|
||||
{
|
||||
if ( $id_tmp = $mdb -> get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain' => $domain ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$mdb -> update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $id_tmp ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'main_domain' => 1 ] ] ) )
|
||||
{
|
||||
if ( $id_tmp = $mdb -> get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$mdb -> update( 'pp_langs', [ 'main_domain' => 1 ], [ 'id' => $id_tmp ] );
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $language_id;
|
||||
return self::repo()->languageDelete( (string)$language_id );
|
||||
}
|
||||
|
||||
public static function language_details( $language_id )
|
||||
public static function max_order(): int
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_langs', '*', [ 'id' => $language_id ] );
|
||||
return self::repo()->maxOrder();
|
||||
}
|
||||
|
||||
public static function languages_list()
|
||||
public static function language_save( $language_id, $name, $status, $start, $o, $domain, $main_domain ): string
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_langs', '*', [ 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
return self::repo()->languageSave( (string)$language_id, (string)$name, $status, $start, $o, $domain, $main_domain );
|
||||
}
|
||||
|
||||
public static function language_details( $language_id ): ?array
|
||||
{
|
||||
return self::repo()->languageDetails( (string)$language_id );
|
||||
}
|
||||
|
||||
public static function languages_list(): array
|
||||
{
|
||||
return self::repo()->languagesList();
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,147 +1,73 @@
|
||||
<?
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Settings\SettingsRepository przez DI.
|
||||
*/
|
||||
class Settings
|
||||
{
|
||||
public static function settings_update( $param, $value )
|
||||
private static function repo(): \Domain\Settings\SettingsRepository
|
||||
{
|
||||
global $mdb;
|
||||
return new \Domain\Settings\SettingsRepository( $mdb );
|
||||
}
|
||||
|
||||
if ( $mdb -> count( 'pp_settings', [ 'param' => $param ] ) )
|
||||
return $mdb -> update( 'pp_settings', [ 'value' => $value ], [ 'param' => $param ] );
|
||||
else
|
||||
return $mdb -> insert( 'pp_settings', [ 'param' => $param, 'value' => $value ] );
|
||||
public static function settings_details(): array
|
||||
{
|
||||
return self::repo()->allSettings();
|
||||
}
|
||||
|
||||
public static function settings_update( $param, $value )
|
||||
{
|
||||
return self::repo()->update( (string)$param, $value );
|
||||
}
|
||||
|
||||
public static function settings_save(
|
||||
$firm_name, $firm_adress, $additional_info, $contact_form, $contact_email, $email_host, $email_port, $email_login, $email_password, $google_maps,
|
||||
$facebook_link, $statistic_code, $htaccess, $robots, $newsletter_header, $newsletter_footer_1, $newsletter_footer_2, $google_map_key, $google_search_console, $update, $devel,
|
||||
$news_limit, $visit_counter, $calendar, $tags, $ssl, $mysql_debug, $htaccess_cache, $visits, $links_structure, $link_version, $widget_phone, $update_key )
|
||||
{
|
||||
global $mdb;
|
||||
$firm_name, $firm_adress, $additional_info, $contact_form, $contact_email,
|
||||
$email_host, $email_port, $email_login, $email_password, $google_maps,
|
||||
$facebook_link, $statistic_code, $htaccess, $robots,
|
||||
$newsletter_header, $newsletter_footer_1, $newsletter_footer_2,
|
||||
$google_map_key, $google_search_console, $update, $devel,
|
||||
$news_limit, $visit_counter, $calendar, $tags, $ssl, $mysql_debug,
|
||||
$htaccess_cache, $visits, $links_structure, $link_version,
|
||||
$widget_phone, $update_key
|
||||
): bool {
|
||||
$data = [
|
||||
'firm_name' => $firm_name,
|
||||
'firm_adress' => $firm_adress,
|
||||
'additional_info' => $additional_info,
|
||||
'contact_form' => $contact_form,
|
||||
'contact_email' => $contact_email,
|
||||
'email_host' => $email_host,
|
||||
'email_port' => $email_port,
|
||||
'email_login' => $email_login,
|
||||
'email_password' => $email_password,
|
||||
'google_maps' => $google_maps == 'on' ? 1 : 0,
|
||||
'facebook_link' => $facebook_link,
|
||||
'statistic_code' => $statistic_code,
|
||||
'htaccess' => $htaccess,
|
||||
'robots' => $robots,
|
||||
'newsletter_header' => $newsletter_header,
|
||||
'newsletter_footer_1' => $newsletter_footer_1,
|
||||
'newsletter_footer_2' => $newsletter_footer_2,
|
||||
'google_map_key' => $google_map_key,
|
||||
'google_search_console'=> $google_search_console,
|
||||
'update' => $update == 'on' ? 1 : 0,
|
||||
'devel' => $devel == 'on' ? 1 : 0,
|
||||
'news_limit' => $news_limit,
|
||||
'visit_counter' => $visit_counter == 'on' ? 1 : 0,
|
||||
'calendar' => $calendar == 'on' ? 1 : 0,
|
||||
'tags' => $tags == 'on' ? 1 : 0,
|
||||
'ssl' => $ssl == 'on' ? 1 : 0,
|
||||
'mysql_debug' => $mysql_debug == 'on' ? 1 : 0,
|
||||
'htaccess_cache' => $htaccess_cache == 'on' ? 1 : 0,
|
||||
'visits' => $visits,
|
||||
'links_structure' => $links_structure,
|
||||
'link_version' => $link_version,
|
||||
'widget_phone' => $widget_phone == 'on' ? 1 : 0,
|
||||
'update_key' => $update_key,
|
||||
];
|
||||
|
||||
$mdb -> query( 'TRUNCATE pp_settings' );
|
||||
|
||||
$mdb -> insert( 'pp_settings', [
|
||||
[
|
||||
'param' => 'firm_name',
|
||||
'value' => $firm_name,
|
||||
], [
|
||||
'param' => 'firm_adress',
|
||||
'value' => $firm_adress
|
||||
], [
|
||||
'param' => 'additional_info',
|
||||
'value' => $additional_info
|
||||
], [
|
||||
'param' => 'contact_form',
|
||||
'value' => $contact_form
|
||||
], [
|
||||
'param' => 'contact_email',
|
||||
'value' => $contact_email
|
||||
], [
|
||||
'param' => 'email_host',
|
||||
'value' => $email_host
|
||||
], [
|
||||
'param' => 'email_port',
|
||||
'value' => $email_port
|
||||
], [
|
||||
'param' => 'email_login',
|
||||
'value' => $email_login
|
||||
], [
|
||||
'param' => 'email_password',
|
||||
'value' => $email_password
|
||||
], [
|
||||
'param' => 'google_maps',
|
||||
'value' => $google_maps == 'on' ? 1 : 0
|
||||
], [
|
||||
"param" => 'facebook_link',
|
||||
'value' => $facebook_link
|
||||
], [
|
||||
'param' => 'statistic_code',
|
||||
'value' => $statistic_code
|
||||
], [
|
||||
'param' => 'htaccess',
|
||||
'value' => $htaccess
|
||||
], [
|
||||
'param' => 'robots',
|
||||
'value' => $robots
|
||||
], [
|
||||
'param' => 'newsletter_header',
|
||||
'value' => $newsletter_header
|
||||
], [
|
||||
'param' => 'newsletter_footer_1',
|
||||
'value' => $newsletter_footer_1
|
||||
], [
|
||||
'param' => 'newsletter_footer_2',
|
||||
'value' => $newsletter_footer_2
|
||||
], [
|
||||
'param' => 'google_map_key',
|
||||
'value' => $google_map_key
|
||||
], [
|
||||
'param' => 'google_search_console',
|
||||
'value' => $google_search_console
|
||||
], [
|
||||
'param' => 'update',
|
||||
'value' => $update == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'devel',
|
||||
'value' => $devel == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'news_limit',
|
||||
'value' => $news_limit
|
||||
], [
|
||||
'param' => 'visit_counter',
|
||||
'value' => $visit_counter == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'calendar',
|
||||
'value' => $calendar == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'tags',
|
||||
'value' => $tags == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'ssl',
|
||||
'value' => $ssl == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'mysql_debug',
|
||||
'value' => $mysql_debug == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'htaccess_cache',
|
||||
'value' => $htaccess_cache == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'visits',
|
||||
'value' => $visits
|
||||
], [
|
||||
'param' => 'links_structure',
|
||||
'value' => $links_structure
|
||||
], [
|
||||
'param' => 'link_version',
|
||||
'value' => $link_version
|
||||
], [
|
||||
'param' => 'widget_phone',
|
||||
'value' => $widget_phone == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'update_key',
|
||||
'value' => $update_key
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
\S::set_message( 'Ustawienia zostały zapisane' );
|
||||
\S::delete_cache();
|
||||
\S::htacces();
|
||||
|
||||
return true;
|
||||
return self::repo()->save( $data );
|
||||
}
|
||||
|
||||
public static function settings_details()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> select( 'pp_settings', '*', [ 'ORDER' => [ 'id' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$settings[$row['param']] = $row['value'];
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -1,306 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace admin\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\User\UserRepository przez DI.
|
||||
*/
|
||||
class Users
|
||||
{
|
||||
public static function user_delete($user_id)
|
||||
private static function repo(): \Domain\User\UserRepository
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
return $mdb->delete('pp_users', ['id' => (int)$user_id]);
|
||||
return new \Domain\User\UserRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function user_details($user_id)
|
||||
public static function user_delete( $user_id ): bool
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->get('pp_users', '*', ['id' => (int)$user_id]);
|
||||
return self::repo()->delete( (int)$user_id );
|
||||
}
|
||||
|
||||
public static function user_privileges($user_id)
|
||||
public static function user_details( $user_id ): ?array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->select('pp_users_privileges', '*', ['id_user' => (int)$user_id]);
|
||||
return self::repo()->find( (int)$user_id );
|
||||
}
|
||||
|
||||
public static function user_save($user_id, $login, $status, $active_to, $password, $password_re, $admin, $privileges, $twofa_enabled = 0, $twofa_email = '' )
|
||||
public static function user_privileges( $user_id ): array
|
||||
{
|
||||
global $mdb, $lang;
|
||||
|
||||
$mdb->delete('pp_users_privileges', ['id_user' => (int) $user_id]);
|
||||
|
||||
if (!$user_id)
|
||||
{
|
||||
if (strlen($password) < 5)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.'];
|
||||
|
||||
if ($password != $password_re)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasła są różne'];
|
||||
|
||||
if ($mdb->insert(
|
||||
'pp_users',
|
||||
[
|
||||
'login' => $login,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $active_to == '' ? NULL : $active_to,
|
||||
'admin' => $admin,
|
||||
'password' => md5($password),
|
||||
'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofa_email
|
||||
]
|
||||
))
|
||||
$id_user = $mdb->get('pp_users', 'id', ['ORDER' => ['id' => 'DESC']]);
|
||||
|
||||
if (is_array($privileges))
|
||||
{
|
||||
foreach ($privileges as $pri)
|
||||
{
|
||||
$mdb->insert(
|
||||
'pp_users_privileges',
|
||||
[
|
||||
'name' => $pri,
|
||||
'id_user' => $id_user
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb->insert(
|
||||
'pp_users_privileges',
|
||||
[
|
||||
'name' => $privileges,
|
||||
'id_user' => $id_user
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $response = ['status' => 'ok', 'msg' => 'Użytkownik został zapisany.'];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if ($password and strlen($password) < 5)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.'];
|
||||
|
||||
if ($password and $password != $password_re)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasła są różne'];
|
||||
|
||||
if ($password)
|
||||
$mdb->update('pp_users', [
|
||||
'password' => md5($password)
|
||||
], [
|
||||
'id' => (int) $user_id
|
||||
]);
|
||||
|
||||
$mdb->update('pp_users', [
|
||||
'login' => $login,
|
||||
'admin' => $admin,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $active_to == '' ? NULL : $active_to,
|
||||
'error_logged_count' => 0,
|
||||
'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofa_email
|
||||
], [
|
||||
'id' => (int) $user_id
|
||||
]);
|
||||
|
||||
if (is_array($privileges))
|
||||
{
|
||||
foreach ($privileges as $pri)
|
||||
{
|
||||
$mdb->insert('pp_users_privileges', [
|
||||
'name' => $pri,
|
||||
'id_user' => $user_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb->insert('pp_users_privileges', [
|
||||
'name' => $privileges,
|
||||
'id_user' => $user_id
|
||||
]);
|
||||
}
|
||||
return $response = ['status' => 'ok', 'msg' => 'Uzytkownik został zapisany.'];
|
||||
}
|
||||
\S::delete_cache();
|
||||
return self::repo()->privileges( (int)$user_id );
|
||||
}
|
||||
|
||||
public static function check_login($login, $user_id)
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ($mdb->get('pp_users', 'login', ['AND' => ['login' => $login, 'id[!]' => (int)$user_id]]))
|
||||
return $response = ['status' => 'error', 'msg' => 'Podany login jest już zajęty.'];
|
||||
|
||||
return $response = ['status' => 'ok'];
|
||||
public static function user_save(
|
||||
$user_id, $login, $status, $active_to, $password, $password_re,
|
||||
$admin, $privileges, $twofa_enabled = 0, $twofa_email = ''
|
||||
): array {
|
||||
return self::repo()->save(
|
||||
$user_id, (string)$login, $status, $active_to,
|
||||
(string)$password, (string)$password_re,
|
||||
$admin, $privileges, $twofa_enabled, (string)$twofa_email
|
||||
);
|
||||
}
|
||||
|
||||
public static function logon($login, $password)
|
||||
public static function check_login( $login, $user_id ): array
|
||||
{
|
||||
global $mdb;
|
||||
if ( self::repo()->isLoginTaken( (string)$login, (int)$user_id ) )
|
||||
return [ 'status' => 'error', 'msg' => 'Podany login jest już zajęty.' ];
|
||||
|
||||
if (!$mdb->get('pp_users', '*', ['login' => $login]))
|
||||
return 0;
|
||||
|
||||
if (!$mdb->get('pp_users', '*', ['AND' => ['login' => $login, 'status' => 1, 'error_logged_count[<]' => 5]]))
|
||||
return -1;
|
||||
|
||||
if ($mdb->get('pp_users', '*', [
|
||||
'AND' => [
|
||||
'login' => $login,
|
||||
'status' => 1,
|
||||
'password' => md5($password),
|
||||
'OR' => ['active_to[>=]' => date('Y-m-d'), 'active_to' => null]
|
||||
]
|
||||
]))
|
||||
{
|
||||
$mdb->update('pp_users', ['last_logged' => date('Y-m-d H:i:s'), 'error_logged_count' => 0], ['login' => $login]);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb->update('pp_users', ['last_error_logged' => date('Y-m-d H:i:s'), 'error_logged_count[+]' => 1], ['login' => $login]);
|
||||
if ($mdb->get('pp_users', 'error_logged_count', ['login' => $login]) >= 5)
|
||||
{
|
||||
$mdb->update('pp_users', ['status' => 0], ['login' => $login]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return [ 'status' => 'ok' ];
|
||||
}
|
||||
|
||||
public static function details($login)
|
||||
public static function logon( $login, $password ): int
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->get('pp_users', '*', ['login' => $login]);
|
||||
return self::repo()->logon( (string)$login, (string)$password );
|
||||
}
|
||||
|
||||
public static function check_privileges($name, $user_id)
|
||||
public static function details( $login ): ?array
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ($user_id == 1)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
if (!$privilages = \Cache::fetch("check_privileges:$user_id:$name-tmp"))
|
||||
{
|
||||
$privilages = $mdb->count('pp_users_privileges', ['AND' => ['name' => $name, 'id_user' => (int)$user_id]]);
|
||||
\Cache::store("check_privileges:$user_id:$name", $privilages);
|
||||
}
|
||||
return $privilages;
|
||||
}
|
||||
return self::repo()->findByLogin( (string)$login );
|
||||
}
|
||||
|
||||
static public function get_by_id(int $userId): ?array
|
||||
public static function check_privileges( $name, $user_id ): bool
|
||||
{
|
||||
|
||||
global $mdb;
|
||||
return $mdb->get('pp_users', '*', ['id' => $userId]) ?: null;
|
||||
return self::repo()->hasPrivilege( (string)$name, (int)$user_id );
|
||||
}
|
||||
|
||||
static public function send_twofa_code(int $userId, bool $resend = false): bool
|
||||
public static function get_by_id( int $userId ): ?array
|
||||
{
|
||||
|
||||
$user = self::get_by_id($userId);
|
||||
if (!$user)
|
||||
return false;
|
||||
|
||||
if ((int)$user['twofa_enabled'] !== 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$to = $user['twofa_email'] ?: $user['login'];
|
||||
if (!filter_var($to, FILTER_VALIDATE_EMAIL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($resend && !empty($user['twofa_sent_at']))
|
||||
{
|
||||
$last = strtotime($user['twofa_sent_at']);
|
||||
if ($last && (time() - $last) < 30)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$code = random_int(100000, 999999);
|
||||
$hash = password_hash((string)$code, PASSWORD_DEFAULT);
|
||||
|
||||
self::update_by_id($userId, [
|
||||
'twofa_code_hash' => $hash,
|
||||
'twofa_expires_at' => date('Y-m-d H:i:s', time() + 10 * 60), // 10 minut
|
||||
'twofa_sent_at' => date('Y-m-d H:i:s'),
|
||||
'twofa_failed_attempts' => 0,
|
||||
]);
|
||||
|
||||
$subject = 'Twój kod logowania 2FA';
|
||||
$body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie – zignoruj tę wiadomość i poinformuj administratora.";
|
||||
|
||||
$sent = \S::send_email($to, $subject, $body);
|
||||
|
||||
if (!$sent) {
|
||||
$headers = "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-type: text/plain; charset=UTF-8\r\n";
|
||||
$headers .= "From: no-reply@" . ($_SERVER['HTTP_HOST'] ?? 'localhost') . "\r\n";
|
||||
$encodedSubject = mb_encode_mimeheader($subject, 'UTF-8');
|
||||
|
||||
$sent = mail($to, $encodedSubject, $body, $headers);
|
||||
}
|
||||
|
||||
return $sent;
|
||||
return self::repo()->find( $userId );
|
||||
}
|
||||
|
||||
static public function update_by_id(int $userId, array $data): bool
|
||||
public static function send_twofa_code( int $userId, bool $resend = false ): bool
|
||||
{
|
||||
global $mdb;
|
||||
return (bool)$mdb->update('pp_users', $data, ['id' => $userId]);
|
||||
return self::repo()->sendTwofaCode( $userId, $resend );
|
||||
}
|
||||
|
||||
static public function verify_twofa_code(int $userId, string $code): bool
|
||||
public static function update_by_id( int $userId, array $data ): bool
|
||||
{
|
||||
$user = self::get_by_id( $userId );
|
||||
if (!$user) return false;
|
||||
return self::repo()->update( $userId, $data );
|
||||
}
|
||||
|
||||
if ((int)$user['twofa_failed_attempts'] >= 5)
|
||||
{
|
||||
return false; // zbyt wiele prób
|
||||
}
|
||||
|
||||
// sprawdź ważność
|
||||
if (empty($user['twofa_expires_at']) || time() > strtotime($user['twofa_expires_at']))
|
||||
{
|
||||
// wyczyść po wygaśnięciu
|
||||
self::update_by_id($userId, [
|
||||
'twofa_code_hash' => null,
|
||||
'twofa_expires_at' => null,
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$ok = (!empty($user['twofa_code_hash']) && password_verify($code, $user['twofa_code_hash']));
|
||||
if ($ok)
|
||||
{
|
||||
// sukces: czyścimy wszystko
|
||||
self::update_by_id($userId, [
|
||||
'twofa_code_hash' => null,
|
||||
'twofa_expires_at' => null,
|
||||
'twofa_sent_at' => null,
|
||||
'twofa_failed_attempts' => 0,
|
||||
'last_logged' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// zła próba — inkrementacja
|
||||
self::update_by_id($userId, [
|
||||
'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1,
|
||||
'last_error_logged' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return false;
|
||||
public static function verify_twofa_code( int $userId, string $code ): bool
|
||||
{
|
||||
return self::repo()->verifyTwofaCode( $userId, $code );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,8 @@
|
||||
<?php
|
||||
class Cache
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Cache\CacheHandler.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować \Cache na \Shared\Cache\CacheHandler.
|
||||
*/
|
||||
class Cache extends \Shared\Cache\CacheHandler
|
||||
{
|
||||
public static function store( $key, $data, $ttl = 86400 )
|
||||
{
|
||||
file_put_contents( self::get_file_name( $key ), gzdeflate( serialize( array( time() + $ttl, $data ) ) ) );
|
||||
}
|
||||
|
||||
private static function get_file_name( $key )
|
||||
{
|
||||
$md5 = md5( $key );
|
||||
$dir = 'temp/' . $md5[0] . '/' . $md5[1] . '/';
|
||||
|
||||
if ( !is_dir( $dir ) )
|
||||
mkdir( $dir , 0755 , true );
|
||||
|
||||
return $dir . 's_cache_' . $md5;
|
||||
}
|
||||
|
||||
public static function fetch( $key )
|
||||
{
|
||||
$filename = self::get_file_name( $key );
|
||||
|
||||
if ( !file_exists( $filename ) || !is_readable( $filename ) )
|
||||
return false;
|
||||
|
||||
$data = gzinflate( file_get_contents( $filename ) );
|
||||
|
||||
$data = @unserialize( $data );
|
||||
if ( !$data )
|
||||
{
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( time() > $data[0] )
|
||||
{
|
||||
if ( file_exists( $filename ) )
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data[1];
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,91 +1,8 @@
|
||||
<?php
|
||||
class Html
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Html\Html.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować \Html na \Shared\Html\Html.
|
||||
*/
|
||||
class Html extends \Shared\Html\Html
|
||||
{
|
||||
public static function form_text( array $params = array() )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/form-text' );
|
||||
}
|
||||
|
||||
public static function input_switch( array $params = array() )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/input-switch' );
|
||||
}
|
||||
|
||||
public static function select( array $params = array() )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/select' );
|
||||
}
|
||||
|
||||
public static function textarea( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'rows' => 4,
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/textarea' );
|
||||
}
|
||||
|
||||
public static function input_icon( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/input-icon' );
|
||||
}
|
||||
|
||||
public static function input( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/input' );
|
||||
}
|
||||
|
||||
public static function button( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'class' => 'btn-sm btn-info',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/button' );
|
||||
}
|
||||
|
||||
public static function panel( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'title' => 'panel-title',
|
||||
'class' => 'panel-primary',
|
||||
'content' => 'panel-content'
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/panel' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,312 +1,8 @@
|
||||
<?php
|
||||
class ImageManipulator
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Image\ImageManipulator.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować new ImageManipulator na new \Shared\Image\ImageManipulator.
|
||||
*/
|
||||
class ImageManipulator extends \Shared\Image\ImageManipulator
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $image;
|
||||
protected $img_src;
|
||||
|
||||
/**
|
||||
* Image manipulator constructor
|
||||
*
|
||||
* @param string $file OPTIONAL Path to image file or image data as string
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($file = null)
|
||||
{
|
||||
if (null !== $file) {
|
||||
if (is_file($file)) {
|
||||
$this -> img_src = $file;
|
||||
$this->setImageFile($file);
|
||||
} else {
|
||||
echo 'a'; exit;
|
||||
$this->setImageString($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from file
|
||||
*
|
||||
* @param string $file Path to image file
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setImageFile($file)
|
||||
{
|
||||
if (!(is_readable($file) && is_file($file))) {
|
||||
throw new InvalidArgumentException("Image file $file is not readable");
|
||||
}
|
||||
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
list ($this->width, $this->height, $type) = getimagesize($file);
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
$this->image = imagecreatefromgif($file);
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
$this->image = imagecreatefromjpeg($file);
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
$this->image = imagecreatefrompng($file);
|
||||
break;
|
||||
default :
|
||||
throw new InvalidArgumentException("Image type $type not supported");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from string data
|
||||
*
|
||||
* @param string $data
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function setImageString($data)
|
||||
{
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
if (!$this->image = imagecreatefromstring($data)) {
|
||||
throw new RuntimeException('Cannot create image from data string');
|
||||
}
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resamples the current image
|
||||
*
|
||||
* @param int $width New width
|
||||
* @param int $height New height
|
||||
* @param bool $constrainProportions Constrain current image proportions when resizing
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function resample( $width, $height, $constrainProportions = true )
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new RuntimeException('No image set');
|
||||
}
|
||||
if ($constrainProportions) {
|
||||
if ($this->height >= $this->width) {
|
||||
$width = round($height / $this->height * $this->width);
|
||||
} else {
|
||||
$height = round($width / $this->width * $this->height);
|
||||
}
|
||||
}
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
|
||||
imagecopyresampled($temp, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
|
||||
|
||||
if ( function_exists('exif_read_data') )
|
||||
{
|
||||
$exif = exif_read_data( $this -> img_src );
|
||||
if ( $exif && isset($exif['Orientation']) )
|
||||
{
|
||||
$orientation = $exif['Orientation'];
|
||||
if ( $orientation != 1 )
|
||||
{
|
||||
$deg = 0;
|
||||
switch ($orientation)
|
||||
{
|
||||
case 3:
|
||||
$deg = 180;
|
||||
break;
|
||||
case 6:
|
||||
$deg = 270;
|
||||
break;
|
||||
case 8:
|
||||
$deg = 90;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $deg )
|
||||
$temp = imagerotate( $temp, $deg, 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarge canvas
|
||||
*
|
||||
* @param int $width Canvas width
|
||||
* @param int $height Canvas height
|
||||
* @param array $rgb RGB colour values
|
||||
* @param int $xpos X-Position of image in new canvas, null for centre
|
||||
* @param int $ypos Y-Position of image in new canvas, null for centre
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function enlargeCanvas($width, $height, array $rgb = array(), $xpos = null, $ypos = null)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new RuntimeException('No image set');
|
||||
}
|
||||
|
||||
$width = max($width, $this->width);
|
||||
$height = max($height, $this->height);
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
if (count($rgb) == 3) {
|
||||
$bg = imagecolorallocate($temp, $rgb[0], $rgb[1], $rgb[2]);
|
||||
imagefill($temp, 0, 0, $bg);
|
||||
}
|
||||
|
||||
if (null === $xpos) {
|
||||
$xpos = round(($width - $this->width) / 2);
|
||||
}
|
||||
if (null === $ypos) {
|
||||
$ypos = round(($height - $this->height) / 2);
|
||||
}
|
||||
|
||||
imagecopy($temp, $this->image, (int) $xpos, (int) $ypos, 0, 0, $this->width, $this->height);
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop image
|
||||
*
|
||||
* @param int|array $x1 Top left x-coordinate of crop box or array of coordinates
|
||||
* @param int $y1 Top left y-coordinate of crop box
|
||||
* @param int $x2 Bottom right x-coordinate of crop box
|
||||
* @param int $y2 Bottom right y-coordinate of crop box
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function crop($x1, $y1 = 0, $x2 = 0, $y2 = 0)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new RuntimeException('No image set');
|
||||
}
|
||||
if (is_array($x1) && 4 == count($x1)) {
|
||||
list($x1, $y1, $x2, $y2) = $x1;
|
||||
}
|
||||
|
||||
$x1 = max($x1, 0);
|
||||
$y1 = max($y1, 0);
|
||||
|
||||
$x2 = min($x2, $this->width);
|
||||
$y2 = min($y2, $this->height);
|
||||
|
||||
$width = $x2 - $x1;
|
||||
$height = $y2 - $y1;
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
imagecopy($temp, $this->image, 0, 0, $x1, $y1, $width, $height);
|
||||
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace current image resource with a new one
|
||||
*
|
||||
* @param resource $res New image resource
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
protected function _replace($res)
|
||||
{
|
||||
if (!is_resource($res)) {
|
||||
throw new UnexpectedValueException('Invalid resource');
|
||||
}
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
$this->image = $res;
|
||||
$this->width = imagesx($res);
|
||||
$this->height = imagesy($res);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current image to file
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save($fileName, $type = IMAGETYPE_JPEG)
|
||||
{
|
||||
$dir = dirname($fileName);
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
throw new RuntimeException('Error creating directory ' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
if (!imagegif($this->image, $fileName)) {
|
||||
throw new RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
if (!imagepng($this->image, $fileName)) {
|
||||
throw new RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
default :
|
||||
if (!imagejpeg($this->image, $fileName, 95)) {
|
||||
throw new RuntimeException;
|
||||
}
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
throw new RuntimeException('Error saving image file to ' . $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GD image resource
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image resource width
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image height
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1257
autoload/class.S.php
@@ -1,73 +1,8 @@
|
||||
<?php
|
||||
class Tpl
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Tpl\Tpl.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować \Tpl na \Shared\Tpl\Tpl.
|
||||
*/
|
||||
class Tpl extends \Shared\Tpl\Tpl
|
||||
{
|
||||
protected $dir = 'templates/';
|
||||
protected $vars = array();
|
||||
|
||||
function __construct( $dir = null )
|
||||
{
|
||||
if ( $dir !== null )
|
||||
$this -> dir = $dir;
|
||||
}
|
||||
|
||||
public static function view( $file, $values = '' )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
if ( is_array( $values ) ) foreach ( $values as $key => $val )
|
||||
$tpl -> $key = $val;
|
||||
return $tpl -> render( $file );
|
||||
}
|
||||
|
||||
public function secureHTML( $val )
|
||||
{
|
||||
$out = stripslashes( $val );
|
||||
$out = str_replace( "'", "'", $out );
|
||||
$out = str_replace( '"', """, $out );
|
||||
$out = str_replace( "<", "<", $out );
|
||||
$out = str_replace( ">", ">", $out );
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function render( $file )
|
||||
{
|
||||
if ( file_exists( 'templates_user/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates_user/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( 'templates/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else
|
||||
return '<div class="alert alert-danger" role="alert">Nie znaleziono pliku widoku: <b>' . $this -> dir . $file . '.php</b>';
|
||||
}
|
||||
|
||||
public function __set( $name, $value )
|
||||
{
|
||||
$this -> vars[ $name ] = $value;
|
||||
}
|
||||
|
||||
public function __get( $name )
|
||||
{
|
||||
return $this -> vars[ $name ];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,34 @@
|
||||
<?php
|
||||
namespace front\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Languages\LanguagesRepository przez DI.
|
||||
*/
|
||||
class Languages
|
||||
{
|
||||
public static function default_domain()
|
||||
private static function repo(): \Domain\Languages\LanguagesRepository
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> query( 'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL AND main_domain = 1' ) -> fetchAll();
|
||||
return $default_domain = $results[0][0];
|
||||
return new \Domain\Languages\LanguagesRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function default_language( $domain = '' )
|
||||
|
||||
public static function default_domain(): ?string
|
||||
{
|
||||
global $mdb;
|
||||
if ( !$default_language = \Cache::fetch( "default_language:$domain" ) )
|
||||
{
|
||||
if ( $domain )
|
||||
$results = $mdb -> query( 'SELECT id FROM pp_langs WHERE status = 1 AND domain = \'' . $domain . '\' ORDER BY start DESC, o ASC LIMIT 1' ) -> fetchAll();
|
||||
if ( !$domain or !\front\factory\Languages::default_domain() )
|
||||
$results = $mdb -> query( 'SELECT id FROM pp_langs WHERE status = 1 AND domain IS NULL ORDER BY start DESC, o ASC LIMIT 1' ) -> fetchAll();
|
||||
$default_language = $results[0][0];
|
||||
|
||||
\Cache::store( "default_language:$domain", $default_language );
|
||||
}
|
||||
return $default_language;
|
||||
return self::repo()->defaultDomain();
|
||||
}
|
||||
|
||||
public static function active_languages()
|
||||
|
||||
public static function default_language( $domain = '' ): ?string
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$active_languages = \Cache::fetch( 'active_languages' ) )
|
||||
{
|
||||
$active_languages = $mdb -> select( 'pp_langs', [ 'id', 'name', 'domain' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
\Cache::store( 'active_languages', $active_languages );
|
||||
}
|
||||
return $active_languages;
|
||||
return self::repo()->defaultLanguage( (string)$domain );
|
||||
}
|
||||
|
||||
public static function lang_translations( $language = 'pl' )
|
||||
|
||||
public static function active_languages(): array
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$translations = \Cache::fetch( "lang_translations:$language" ) )
|
||||
{
|
||||
$translations[ '0' ] = $language;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs_translations', [ 'text', $language ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$translations[ $row['text'] ] = $row[ $language ];
|
||||
|
||||
\Cache::store( "lang_translations:$language", $translations );
|
||||
}
|
||||
|
||||
return $translations;
|
||||
return self::repo()->activeLanguages();
|
||||
}
|
||||
|
||||
public static function lang_translations( $language = 'pl' ): array
|
||||
{
|
||||
return self::repo()->langTranslations( (string)$language );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
<?php
|
||||
namespace front\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Settings\SettingsRepository przez DI.
|
||||
*/
|
||||
class Settings
|
||||
{
|
||||
public static function settings_details()
|
||||
private static function repo(): \Domain\Settings\SettingsRepository
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$settings = \Cache::fetch( 'settings_details' ) )
|
||||
{
|
||||
$results = $mdb -> select( 'pp_settings', '*' );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$settings[ $row['param'] ] = $row['value'];
|
||||
|
||||
\Cache::store( 'settings_details', $settings );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
return new \Domain\Settings\SettingsRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function visit_counter()
|
||||
public static function settings_details(): array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_settings', 'value', [ 'param' => 'visits'] );
|
||||
return self::repo()->allSettings();
|
||||
}
|
||||
|
||||
public static function visit_counter(): ?string
|
||||
{
|
||||
return self::repo()->visitCounter();
|
||||
}
|
||||
}
|
||||
|
||||
10
cron.php
@@ -4,10 +4,14 @@ function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
178
docs/FORM_EDIT_SYSTEM.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Form Edit System - Dokumentacja użycia
|
||||
|
||||
## Architektura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Controller │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ edit() │ │ save() │ │
|
||||
│ │ - buduje VM │ │ - walidacja │ │
|
||||
│ │ - renderuje │ │ - zapis │ │
|
||||
│ └────────┬────────┘ └─────────────────┘ │
|
||||
└───────────┼─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FormEditViewModel │
|
||||
│ - title, formId, data, fields, tabs, actions │
|
||||
│ - validationErrors, persist, languages │
|
||||
└───────────┬─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ components/form-edit.php (szablon) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ FormFieldRenderer - renderuje każde pole │ │
|
||||
│ │ ├─ input, select, textarea, switch │ │
|
||||
│ │ ├─ date, datetime, editor, image │ │
|
||||
│ │ └─ lang_section (zagnieżdżone pola) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Pliki systemu
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| `autoload/admin/ViewModels/Forms/FormFieldType.php` | Stale typow pol |
|
||||
| `autoload/admin/ViewModels/Forms/FormField.php` | Factory methods per typ |
|
||||
| `autoload/admin/ViewModels/Forms/FormTab.php` | Zakladki |
|
||||
| `autoload/admin/ViewModels/Forms/FormAction.php` | Akcje (zapisz, anuluj) |
|
||||
| `autoload/admin/ViewModels/Forms/FormEditViewModel.php` | ViewModel formularza |
|
||||
| `autoload/admin/Support/Forms/FormValidator.php` | Walidacja pol |
|
||||
| `autoload/admin/Support/Forms/FormRequestHandler.php` | Obsluga POST + persist |
|
||||
| `autoload/admin/Support/Forms/FormFieldRenderer.php` | Renderowanie HTML |
|
||||
| `admin/templates/components/form-edit.php` | Uniwersalny szablon |
|
||||
|
||||
## Przykład użycia w kontrolerze
|
||||
|
||||
```php
|
||||
use admin\ViewModels\Forms\FormEditViewModel;
|
||||
use admin\ViewModels\Forms\FormField;
|
||||
use admin\ViewModels\Forms\FormTab;
|
||||
use admin\ViewModels\Forms\FormAction;
|
||||
use admin\Support\Forms\FormRequestHandler;
|
||||
|
||||
class BannerController
|
||||
{
|
||||
public function edit(): string
|
||||
{
|
||||
$banner = $this->repository->find($id);
|
||||
$languages = \admin\factory\Languages::languages_list();
|
||||
|
||||
$viewModel = new FormEditViewModel(
|
||||
formId: 'banner-edit',
|
||||
title: 'Edycja banera',
|
||||
data: $banner,
|
||||
tabs: [
|
||||
new FormTab('settings', 'Ustawienia', 'fa-wrench'),
|
||||
new FormTab('content', 'Zawartość', 'fa-file'),
|
||||
],
|
||||
fields: [
|
||||
// Zakładka Ustawienia
|
||||
FormField::text('name', [
|
||||
'label' => 'Nazwa',
|
||||
'tab' => 'settings',
|
||||
'required' => true,
|
||||
]),
|
||||
FormField::switch('status', [
|
||||
'label' => 'Aktywny',
|
||||
'tab' => 'settings',
|
||||
]),
|
||||
FormField::date('date_start', [
|
||||
'label' => 'Data rozpoczęcia',
|
||||
'tab' => 'settings',
|
||||
]),
|
||||
|
||||
// Sekcja językowa w zakładce Zawartość
|
||||
FormField::langSection('translations', 'content', [
|
||||
FormField::image('src', ['label' => 'Obraz']),
|
||||
FormField::text('url', ['label' => 'Url']),
|
||||
FormField::editor('text', ['label' => 'Treść']),
|
||||
]),
|
||||
],
|
||||
actions: [
|
||||
FormAction::save('/admin/banners/save', '/admin/banners'),
|
||||
FormAction::cancel('/admin/banners'),
|
||||
],
|
||||
languages: $languages,
|
||||
persist: true,
|
||||
);
|
||||
|
||||
return \Tpl::view('components/form-edit', ['form' => $viewModel]);
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$formHandler = new FormRequestHandler();
|
||||
$viewModel = $this->buildFormViewModel(); // jak w edit()
|
||||
|
||||
$result = $formHandler->handleSubmit($viewModel, $_POST);
|
||||
|
||||
if (!$result['success']) {
|
||||
// Błędy walidacji - zapisane automatycznie do sesji
|
||||
echo json_encode(['success' => false, 'errors' => $result['errors']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Sukces - persist wyczyszczony automatycznie
|
||||
$this->repository->save($result['data']);
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dostępne typy pól
|
||||
|
||||
| Typ | Metoda | Opcje |
|
||||
|-----|--------|-------|
|
||||
| `text` | `FormField::text(name, ['label' => '...', 'required' => true])` | placeholder, help |
|
||||
| `number` | `FormField::number(name, [...])` | - |
|
||||
| `email` | `FormField::email(name, [...])` | walidacja formatu |
|
||||
| `password` | `FormField::password(name, [...])` | - |
|
||||
| `date` | `FormField::date(name, [...])` | datetimepicker |
|
||||
| `datetime` | `FormField::datetime(name, [...])` | datetimepicker z czasem |
|
||||
| `switch` | `FormField::switch(name, [...])` | checked (bool) |
|
||||
| `select` | `FormField::select(name, ['options' => [...]])` | options: [key => label] |
|
||||
| `textarea` | `FormField::textarea(name, ['rows' => 4])` | rows |
|
||||
| `editor` | `FormField::editor(name, ['toolbar' => 'MyTool'])` | CKEditor |
|
||||
| `image` | `FormField::image(name, ['filemanager' => true])` | filemanager URL |
|
||||
| `file` | `FormField::file(name, [...])` | filemanager |
|
||||
| `hidden` | `FormField::hidden(name, value)` | - |
|
||||
| `color` | `FormField::color(name, ['label' => '...'])` | HTML5 color picker + text input |
|
||||
| `lang_section` | `FormField::langSection(name, 'tab', [fields])` | pola per język |
|
||||
|
||||
## Walidacja
|
||||
|
||||
Walidacja jest automatyczna na podstawie właściwości pól:
|
||||
- `required` - pole wymagane
|
||||
- `type` = `email` - walidacja formatu e-mail
|
||||
- `type` = `number` - walidacja liczby
|
||||
- `type` = `date` - walidacja formatu YYYY-MM-DD
|
||||
|
||||
Dla sekcji językowych walidacja jest powtarzana dla każdego aktywnego języka.
|
||||
|
||||
## Persist (zapamiętywanie danych)
|
||||
|
||||
Gdy `persist = true`:
|
||||
1. Przy błędzie walidacji dane są zapisywane w `$_SESSION['form_persist'][$formId]`
|
||||
2. Formularz automatycznie przywraca dane z sesji przy ponownym wyświetleniu
|
||||
3. Po udanym zapisie sesja jest czyszczona automatycznie przez `FormRequestHandler`
|
||||
|
||||
## Przerabianie istniejących formularzy
|
||||
|
||||
1. **Kontroler** - zamień `view\Xxx::edit()` na `FormEditViewModel`
|
||||
2. **Repository** - dostosuj `save()` do formatu z `FormRequestHandler` (lub dodaj wsparcie dla obu formatów)
|
||||
3. **Szablon** - usuń stary szablon lub zostaw jako fallback
|
||||
4. **Testy** - zaktualizuj testy jeśli zmienił się format danych
|
||||
|
||||
## Aktualizacja 2026-02-15 (ver. 0.275)
|
||||
|
||||
- Modul `ShopCategory` zostal zmigrowany do warstwy Domain + DI, ale formularz kategorii nadal korzysta z legacy `gridEdit`.
|
||||
- W ramach migracji wydzielono skrypty UI do osobnych partiali `*-custom-script.php` (lista, browse, edycja, produkty), co upraszcza dalsze przepiecie formularza na `components/form-edit`.
|
||||
- Po migracji `ShopCategory` kolejnym kandydatem do pelnej migracji formularza na Form Edit System pozostaje modul `Order` (zgodnie z `REFACTORING_PLAN.md`).
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-15*
|
||||
24
docs/MEMORY.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Pamięć projektu cnsPRO
|
||||
|
||||
Notatki i wnioski zebrane podczas pracy z kodem. Aktualizowane na bieżąco.
|
||||
|
||||
---
|
||||
|
||||
## Serwer produkcyjny
|
||||
|
||||
- PHP < 8.0 — unikać `match`, named arguments, union types, `str_contains()` itp.
|
||||
- Zamiast `match` używać operatorów trójargumentowych (ternary) lub `if/else`
|
||||
|
||||
## Redis cache — konwencje
|
||||
|
||||
- TTL domyślnie 86400 (24h)
|
||||
- Klucze produktów: `shop\product:{id}:{lang}:{permutation_hash}`
|
||||
- Wzorzec czyszczenia: `CacheHandler::deletePattern("shop\\product:{$id}:*")`
|
||||
- Dane w cache są serializowane — wymagają `unserialize()` po `get()`
|
||||
|
||||
## Aktualizacje klienckie
|
||||
|
||||
- Pliki `*.md` NIGDY nie trafiają do ZIP aktualizacji
|
||||
- `updates/changelog.php` to plik serwisowy repozytorium, nie runtime klienta
|
||||
- Główny `.htaccess` wdrażany osobno, poza ZIP aktualizacji
|
||||
- W archiwum ZIP NIE powinno być folderu z nazwą wersji — struktura zaczyna się od katalogów projektu
|
||||
120
docs/PROJECT_STRUCTURE.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Struktura projektu cmsPRO
|
||||
|
||||
## Punkty wejścia
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| `index.php` | Router frontendu |
|
||||
| `admin/index.php` | Router panelu admina |
|
||||
| `ajax.php` | AJAX frontend |
|
||||
| `admin/ajax.php` | AJAX admin |
|
||||
| `api.php` | Publiczne API |
|
||||
| `cron.php` | Zadania cykliczne (newsletter) |
|
||||
| `download.php` | Chronione pobieranie plików |
|
||||
|
||||
Każdy punkt wejścia ładuje dwa autoloadery (PSR-4 + legacy):
|
||||
```php
|
||||
spl_autoload_register(function($class) { /* PSR-4: src/ → autoload/ */ });
|
||||
spl_autoload_register(function($class) { /* legacy: class.{Name}.php */ });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wzorzec architektoniczny — Static Factory (MVCish)
|
||||
|
||||
```
|
||||
autoload/{admin|front}/
|
||||
├── controls/class.{Module}.php ← obsługa requestów
|
||||
├── factory/class.{Module}.php ← logika biznesowa + DB
|
||||
└── view/class.{Module}.php ← generowanie HTML
|
||||
```
|
||||
|
||||
Przestrzenie nazw: `\admin\controls`, `\admin\factory`, `\admin\view`,
|
||||
`\front\controls`, `\front\factory`, `\front\view`
|
||||
|
||||
---
|
||||
|
||||
## Refaktoryzacja DDD — stan aktualny
|
||||
|
||||
Projekt migruje stopniowo do architektury DDD. Stare klasy stają się
|
||||
cienkimi wrapperami delegującymi do nowych klas w `Shared\` i `Domain\`.
|
||||
|
||||
### Faza 0 ✓ — Autoloader PSR-4
|
||||
Dodany do wszystkich 6 punktów wejścia. Mapowanie: namespace → `autoload/`.
|
||||
|
||||
### Faza 1 ✓ — Shared utilities (`autoload/Shared/`)
|
||||
|
||||
```
|
||||
autoload/Shared/
|
||||
├── Cache/CacheHandler.php ← \Shared\Cache\CacheHandler
|
||||
├── Email/ ← \Shared\Email\*
|
||||
├── Helpers/Helpers.php ← \Shared\Helpers\Helpers
|
||||
├── Html/Html.php ← \Shared\Html\Html
|
||||
├── Image/ImageManipulator.php ← \Shared\Image\ImageManipulator
|
||||
└── Tpl/Tpl.php ← \Shared\Tpl\Tpl
|
||||
```
|
||||
|
||||
Stare klasy (`class.S.php`, `class.Cache.php`, itd.) są teraz cienkimi
|
||||
wrapperami — zachowana pełna kompatybilność wsteczna.
|
||||
|
||||
### Faza 2 (w toku) — Domain Repositories (`autoload/Domain/`)
|
||||
|
||||
```
|
||||
autoload/Domain/
|
||||
├── Languages/LanguagesRepository.php ← \Domain\Languages\LanguagesRepository ✓
|
||||
├── Settings/SettingsRepository.php ← \Domain\Settings\SettingsRepository ✓
|
||||
└── User/UserRepository.php ← \Domain\User\UserRepository ✓
|
||||
```
|
||||
|
||||
Następne: `Domain\Pages`, `Domain\Layouts`, `Domain\Articles`, ...
|
||||
|
||||
---
|
||||
|
||||
## Katalogi
|
||||
|
||||
| Katalog | Zawartość |
|
||||
|---------|-----------|
|
||||
| `autoload/` | Klasy PHP (modele, kontrolery, fabryki, widoki, Shared, Domain) |
|
||||
| `admin/templates/` | Szablony panelu admina (17 modułów) |
|
||||
| `templates/` | Szablony frontendu (systemowe, tylko do odczytu) |
|
||||
| `templates_user/` | Szablony frontendu (nadpisywalne przez użytkownika) |
|
||||
| `layout/` | SCSS → CSS (style.scss → style.css) |
|
||||
| `upload/` | Pliki użytkownika (article_images/, article_files/, filemanager/) |
|
||||
| `libraries/` | Zewnętrzne biblioteki (Medoo, CKEditor, Bootstrap, jQuery…) |
|
||||
| `plugins/` | Hooki (special-actions.php, -middle.php, -end.php) |
|
||||
| `migrations/` | Pliki SQL per wersja (np. `0.304.sql`) |
|
||||
| `updates/` | Paczki ZIP aktualizacji |
|
||||
| `temp/` | Cache plikowy (gzip, 24h, generowany automatycznie) |
|
||||
| `docs/` | Dokumentacja techniczna |
|
||||
|
||||
---
|
||||
|
||||
## Kluczowe klasy
|
||||
|
||||
| Klasa | Opis |
|
||||
|-------|------|
|
||||
| `\Shared\Helpers\Helpers` (`class.S.php`) | Megautylita: sesja, cookie, email, SEO, detekcja botów |
|
||||
| `\Shared\Tpl\Tpl` (`class.Tpl.php`) | Silnik szablonów |
|
||||
| `\Shared\Cache\CacheHandler` (`class.Cache.php`) | Cache plikowy |
|
||||
| `\Shared\Html\Html` (`class.Html.php`) | Builder komponentów formularzy |
|
||||
| `\Shared\Image\ImageManipulator` (`class.Image.php`) | Manipulacja obrazami + WebP |
|
||||
| `class.Article.php` | Model artykułu (ArrayAccess, lazy multilang) |
|
||||
|
||||
---
|
||||
|
||||
## Baza danych
|
||||
|
||||
Prefiks tabel: `pp_`. ORM: Medoo (globalny `$mdb`). Konfiguracja: `config.php`.
|
||||
|
||||
Główne tabele: `pp_users`, `pp_articles`, `pp_articles_langs`, `pp_pages`,
|
||||
`pp_pages_langs`, `pp_languages`, `pp_settings`, `pp_newsletter`,
|
||||
`pp_newsletter_users`, `pp_tags`, `pp_banners`, `pp_layouts`, `pp_backups`.
|
||||
|
||||
---
|
||||
|
||||
## System wielojęzyczny
|
||||
|
||||
- Sesja: `$_SESSION['current-lang']`
|
||||
- Tabela: `pp_languages`
|
||||
- Składnia w treści: `[LANG:klucz]`
|
||||
- Cache tłumaczeń: `$_SESSION['lang-{lang_id}']`
|
||||
126
docs/TESTING.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Testowanie shopPRO
|
||||
|
||||
## Szybki start
|
||||
|
||||
```bash
|
||||
# Pelny suite (PowerShell — rekomendowane)
|
||||
./test.ps1
|
||||
|
||||
# Konkretny plik
|
||||
./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||
|
||||
# Konkretny test
|
||||
./test.ps1 --filter testGetQuantityReturnsCorrectValue
|
||||
|
||||
# Alternatywne
|
||||
composer test # standard
|
||||
./test.bat # testdox (czytelna lista)
|
||||
./test-simple.bat # kropki
|
||||
./test-debug.bat # debug
|
||||
./test.sh # Git Bash
|
||||
```
|
||||
|
||||
## Aktualny stan
|
||||
|
||||
```text
|
||||
OK (805 tests, 2253 assertions)
|
||||
```
|
||||
|
||||
Zweryfikowano: 2026-02-24 (ver. 0.318)
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
- **PHPUnit 9.6** via `phpunit.phar`
|
||||
- **Bootstrap:** `tests/bootstrap.php`
|
||||
- **Config:** `phpunit.xml`
|
||||
|
||||
## Struktura testow
|
||||
|
||||
```
|
||||
tests/
|
||||
|-- bootstrap.php
|
||||
|-- stubs/
|
||||
| |-- CacheHandler.php (inline w bootstrap)
|
||||
| |-- Helpers.php (Shared\Helpers\Helpers stub)
|
||||
| `-- ShopProduct.php (shop\Product stub)
|
||||
|-- Unit/
|
||||
| |-- Domain/
|
||||
| | |-- Article/ArticleRepositoryTest.php
|
||||
| | |-- Attribute/AttributeRepositoryTest.php
|
||||
| | |-- Banner/BannerRepositoryTest.php
|
||||
| | |-- Basket/BasketCalculatorTest.php
|
||||
| | |-- Cache/CacheRepositoryTest.php
|
||||
| | |-- Category/CategoryRepositoryTest.php
|
||||
| | |-- Coupon/CouponRepositoryTest.php
|
||||
| | |-- CronJob/CronJobTypeTest.php
|
||||
| | |-- CronJob/CronJobRepositoryTest.php
|
||||
| | |-- CronJob/CronJobProcessorTest.php
|
||||
| | |-- Dictionaries/DictionariesRepositoryTest.php
|
||||
| | |-- Integrations/IntegrationsRepositoryTest.php
|
||||
| | |-- Languages/LanguagesRepositoryTest.php
|
||||
| | |-- Layouts/LayoutsRepositoryTest.php
|
||||
| | |-- Newsletter/NewsletterRepositoryTest.php
|
||||
| | |-- Pages/PagesRepositoryTest.php
|
||||
| | |-- PaymentMethod/PaymentMethodRepositoryTest.php
|
||||
| | |-- Producer/ProducerRepositoryTest.php
|
||||
| | |-- Product/ProductRepositoryTest.php
|
||||
| | |-- ProductSet/ProductSetRepositoryTest.php
|
||||
| | |-- Promotion/PromotionRepositoryTest.php
|
||||
| | |-- Settings/SettingsRepositoryTest.php
|
||||
| | |-- ShopStatus/ShopStatusRepositoryTest.php
|
||||
| | |-- Transport/TransportRepositoryTest.php
|
||||
| | |-- Update/UpdateRepositoryTest.php
|
||||
| | `-- User/UserRepositoryTest.php
|
||||
| `-- admin/
|
||||
| `-- Controllers/
|
||||
| |-- ArticlesControllerTest.php
|
||||
| |-- DictionariesControllerTest.php
|
||||
| |-- IntegrationsControllerTest.php
|
||||
| |-- ProductArchiveControllerTest.php
|
||||
| |-- SettingsControllerTest.php
|
||||
| |-- ShopAttributeControllerTest.php
|
||||
| |-- ShopCategoryControllerTest.php
|
||||
| |-- ShopCouponControllerTest.php
|
||||
| |-- ShopPaymentMethodControllerTest.php
|
||||
| |-- ShopProducerControllerTest.php
|
||||
| |-- ShopProductControllerTest.php
|
||||
| |-- ShopProductSetsControllerTest.php
|
||||
| |-- ShopPromotionControllerTest.php
|
||||
| |-- ShopStatusesControllerTest.php
|
||||
| |-- ShopTransportControllerTest.php
|
||||
| `-- UsersControllerTest.php
|
||||
| `-- api/
|
||||
| |-- ApiRouterTest.php
|
||||
| `-- Controllers/
|
||||
| |-- OrdersApiControllerTest.php
|
||||
| |-- ProductsApiControllerTest.php
|
||||
| `-- DictionariesApiControllerTest.php
|
||||
`-- Integration/ (puste — zarezerwowane)
|
||||
```
|
||||
|
||||
## Dodawanie nowych testow
|
||||
|
||||
1. Plik w `tests/Unit/Domain/<Module>/<Class>Test.php`, `tests/Unit/admin/Controllers/<Class>Test.php` lub `tests/Unit/api/Controllers/<Class>Test.php`.
|
||||
2. Rozszerz `PHPUnit\Framework\TestCase`.
|
||||
3. Nazwy metod zaczynaj od `test`.
|
||||
4. Wzorzec AAA: Arrange, Act, Assert.
|
||||
|
||||
## Mockowanie Medoo
|
||||
|
||||
```php
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('get')->willReturn(42);
|
||||
|
||||
$repo = new ProductRepository($mockDb);
|
||||
$value = $repo->getQuantity(123);
|
||||
|
||||
$this->assertEquals(42, $value);
|
||||
```
|
||||
|
||||
## Bootstrap — stuby
|
||||
|
||||
`tests/bootstrap.php` rejestruje autoloader i definiuje stuby:
|
||||
- `Redis`, `RedisConnection` — klasy Redis (aby nie wymagac rozszerzenia)
|
||||
- `Shared\Cache\CacheHandler` — inline stub z `get()`/`set()`/`exists()`/`delete()`/`deletePattern()`
|
||||
- `Shared\Helpers\Helpers` — z `tests/stubs/Helpers.php`
|
||||
- `shop\Product` — z `tests/stubs/ShopProduct.php`
|
||||
73
docs/UPDATE_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Instrukcja tworzenia aktualizacji shopPRO
|
||||
|
||||
## 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`).
|
||||
|
||||
### INFO
|
||||
pamiętaj że push czasem zwraca błąd autoryzacji, wtedy spróbuj ponownie
|
||||
10
index.php
@@ -4,10 +4,14 @@ function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\', $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
96
log.txt
@@ -1,96 +0,0 @@
|
||||
Array
|
||||
(
|
||||
)
|
||||
Array
|
||||
(
|
||||
[title] => "Udar mózgu: Kluczowe znaczenie wczesnej diagnostyki i interwencji medycznej"
|
||||
[entry] => Udar mózgu stanowi jedno z najpoważniejszych zagrożeń dla zdrowia, dotykając każdego roku wiele osób w Polsce. Wczesne rozpoznanie jego objawów oraz niezwłoczna interwencja medyczna mogą uratować życie i zminimalizować ryzyko trwałych uszkodzeń neurologicznych.
|
||||
[text] => <p><span style="font-weight:bold;">Udar mózgu</span> to jedno z najgroźniejszych zagrożeń dla zdrowia człowieka. Każdego roku dotyka on tysiące Polaków, a prawidłowa i szybka reakcja może uratować życie oraz zapobiec trwałym uszkodzeniom neurologicznym. Niestety, wiele osób nie rozpoznaje wczesnych objawów, które wysyła nasz organizm przed udarem. <span style="font-style:italic;">Neurologowie</span> ostrzegają, że pierwsze symptomy mogą być bardzo subtelne, lecz ich zignorowanie niesie poważne konsekwencje.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Czym jest udar mózgu?</span> Udar mózgu to nagłe zaburzenie krążenia krwi w obrębie mózgu. Może on mieć charakter niedokrwienny (najczęstszy, spowodowany zatkaniem naczynia krwionośnego) lub krwotoczny (wynikający z pęknięcia naczynia i krwawienia do mózgu). Obie formy prowadzą do niedotlenienia obszaru mózgu i szybkiego obumierania komórek nerwowych.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Najczęściej występujące czynniki ryzyka udaru:</span></p>
|
||||
<ul>
|
||||
<li>nadciśnienie tętnicze</li>
|
||||
<li>cukrzyca</li>
|
||||
<li>choroby serca, w szczególności migotanie przedsionków</li>
|
||||
<li>palenie papierosów</li>
|
||||
<li>otyłość</li>
|
||||
<li>niezdrowa dieta i brak aktywności fizycznej</li>
|
||||
<li>przewlekły stres</li>
|
||||
<li>nadużywanie alkoholu</li>
|
||||
<li>podeszły wiek</li>
|
||||
<li>czynniki genetyczne</li>
|
||||
</ul>
|
||||
|
||||
<p><span style="font-weight:bold;">Organizm ostrzega przed udarem – na jakie symptomy uważać?</span> Wiele osób sądzi, że udar pojawia się nagle i bez zapowiedzi. Tymczasem ciało często wysyła sygnały ostrzegawcze. Wczesne rozpoznanie pozwala szybko zareagować i zapobiec powikłaniom.</p>
|
||||
|
||||
<p><span style="text-decoration:underline;">Najczęściej występujące symptomy zwiastujące udar:</span></p>
|
||||
<ol>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Nagła, jednostronna słabość lub drętwienie:</span>
|
||||
<span>Dochodzi do niej najczęściej w obrębie twarzy, ramienia bądź nogi. Uczucie osłabienia lub brak czucia po jednej stronie ciała to klasyczny znak ostrzegawczy. Zdarza się też opadanie kącika ust.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Problemy z mową i rozumieniem:</span>
|
||||
<span>Chory ma trudności z wypowiadaniem słów, zrozumieniem tego, co się do niego mówi, lub doświadcza bełkotliwej mowy. Może mieć również trudności ze znalezieniem właściwych słów.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Nagłe zaburzenia widzenia:</span>
|
||||
<span>Pojawia się pogorszenie ostrości wzroku, podwójne widzenie lub częściowa utrata wzroku – najczęściej w jednym oku lub po jednej stronie pola widzenia.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Silny, nagły ból głowy:</span>
|
||||
<span>Zwłaszcza jeśli towarzyszą mu nudności, wymioty, zaburzenia świadomości lub sztywność karku. Takie bóle głowy mogą wskazywać na udar krwotoczny.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Zaburzenia równowagi, zawroty głowy, trudności w chodzeniu:</span>
|
||||
<span>Osoba może mieć uczucie wirowania, trudności z koordynacją, traci równowagę oraz przewraca się bez wyraźnej przyczyny.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Nagła utrata przytomności lub splątanie:</span>
|
||||
<span>Osoba staje się zdezorientowana, trudna do nawiązania kontaktu, a nawet może zemdleć.</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p>Nie należy lekceważyć nawet przejściowych objawów, które same ustępują. Mogą one oznaczać tzw. przejściowy atak niedokrwienny (TIA), będący <span style="font-weight:bold;">ostrzegawczym sygnałem</span> przed pełnoobjawowym udarem.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Jak zachować się, gdy pojawią się objawy udaru?</span></p>
|
||||
<ul>
|
||||
<li>Niezwłocznie wezwij pogotowie ratunkowe (numer 112 lub 999).</li>
|
||||
<li>Nie próbuj samodzielnie dojechać do szpitala – leczenie musi rozpocząć się jak najszybciej, najlepiej w ambulansie, gdzie podejmowane są pierwsze działania ratujące życie.</li>
|
||||
<li>Osoba z podejrzeniem udaru powinna być ułożona w bezpiecznej pozycji, nie podawać jej jedzenia ani picia.</li>
|
||||
<li>Poinformuj ratowników o wszystkich zaobserwowanych objawach i ich czasie wystąpienia.</li>
|
||||
</ul>
|
||||
|
||||
<p><span style="font-weight:bold;">Leczenie i rehabilitacja</span></p>
|
||||
<p>Najważniejsze jest rozpoczęcie terapii jak najszybciej po wystąpieniu objawów – istnieje tzw. „<span style="font-style:italic;">złota godzina</span>”, w której wdrożenie leczenia trombolitycznego (rozpuszczającego zakrzepy) istotnie poprawia rokowanie pacjenta. Rehabilitacja neurologiczna powinna rozpocząć się jak najszybciej, co sprzyja powrotowi sprawności i minimalizuje skutki udaru.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Profilaktyka udaru – co możemy zrobić?</span></p>
|
||||
<ul>
|
||||
<li>Kontroluj ciśnienie tętnicze krwi i regularnie przyjmuj leki przepisane przez lekarza.</li>
|
||||
<li>Wyeliminuj czynniki ryzyka: rzuć palenie, unikaj nadmiernego spożycia alkoholu, dbaj o prawidłową masę ciała.</li>
|
||||
<li>Zadbaj o zbilansowaną dietę bogatą w warzywa, owoce, pełnoziarniste produkty i zdrowe tłuszcze.</li>
|
||||
<li>Systematycznie uprawiaj aktywność fizyczną.</li>
|
||||
<li>Regularnie badaj poziom cholesterolu i cukru we krwi.</li>
|
||||
<li>Lecz choroby przewlekłe takie jak cukrzyca i schorzenia serca.</li>
|
||||
<li>Unikaj przewlekłego stresu i zadbaj o higienę snu.</li>
|
||||
</ul>
|
||||
|
||||
<p><span style="font-weight:bold;">Podsumowanie</span></p>
|
||||
<p>Udar mózgu może dotknąć każdego, szczególnie osoby z grup ryzyka. Wczesne rozpoznanie objawów i szybkie udzielenie pomocy daje największą szansę na przeżycie i powrót do sprawności. Nie lekceważ nawet krótkotrwałych symptomów – każdy z nich może być ostrzeżeniem wysyłanym przez Twój organizm. Edukacja i profilaktyka to najskuteczniejsze narzędzia w walce ze skutkami udaru.</p>
|
||||
[page_id] => 41
|
||||
[action] => add_article
|
||||
)
|
||||
Array
|
||||
(
|
||||
[main_image] => Array
|
||||
(
|
||||
[name] => FLUX.1-dev
|
||||
[type] => image/jpeg
|
||||
[tmp_name] => /tmp/phpafhT1D
|
||||
[error] => 0
|
||||
[size] => 115301
|
||||
)
|
||||
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://cmsen.project-dc.pl</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmsen.project-dc.pl/home</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/home</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/s-49-test</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/strona-testowa</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/s-42-strona-druga-2</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/s-48-prowadzenie-spraw-przed-prezesem-urzedu-ochrony-danych-osobowych-prezesem-urzedu-konkurencji-i-konsumentow-oraz-krajowa-izba-odwolawcza</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/a-11-test</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,4 +0,0 @@
|
||||
]ŽÑ
|
||||
Â0Eÿ%_°ÎIÝíoø.QË,tE–„±7Õ*Ó<<3C>œ$·e´X`¬5öз½-<2D>c˜÷„±Ç"ºGáJNTSK®L>,è@‰G_ñè%Ó+dÓ
|
||||
zPä4Ì<x!<21>µ?±
|
||||
¦+`‹9•ͪï±^«>sJ~¢ÍÓ;<3B>L—B
|
||||
@@ -1 +0,0 @@
|
||||
Kエ2イェホエ2ーホエ2477エ07166qャ<71>ャk
|
||||
BIN
temp/2moto.jpg
|
Before Width: | Height: | Size: 3.8 KiB |
BIN
temp/2moto.png
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,4 +0,0 @@
|
||||
ŤTËŽÓJÝ#ń–¬ c;ďžÁ!BH<42>tĹEČŞt—ížŘݦ»3ĆFlŘđ
|
||||
<EFBFBD>_ąKćż(;ńŤÍ‰HQuęqR}ęt‹ŘgÉ‚KÉÂĺ2\®ÖŃzŮţ¸6]łĎ–™źHSÄ
|
||||
|
||||
ô/-<0B>ßš0<ůA´v™3„<>Njy,U˘[w´bţ¦ÜľőôősÖ=˛ÎhUŘ\”Űű÷şŇ<C59F>ů\+Üʼn6Eçc~ŘŮé9†ČĽť1żŞŞ'Ľ°ĄŃú^#wŹź”]<¤žşä8ÓÖuÔ]{Ăĺ<šNÚŁz˘ĐÝ))µ9–0?š÷w>†rťJőW
|
||||
@@ -1 +0,0 @@
|
||||
K<EFBFBD>2<EFBFBD><EFBFBD>δ2<EFBFBD>δ2477<EFBFBD>07166q<><71><EFBFBD><0C><><0C><>k
|
||||
|
Before Width: | Height: | Size: 13 KiB |
@@ -1 +0,0 @@
|
||||
e<EFBFBD><EFBFBD>
|
||||
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
@@ -430,6 +430,10 @@ $license['32764e943176bdd2563547046e2745cf']['domain'] = 'se.min-pan.
|
||||
$license['32764e943176bdd2563547046e2745cf']['valid_to_date'] = '';
|
||||
$license['32764e943176bdd2563547046e2745cf']['valid_to_version'] = '';
|
||||
|
||||
$license['1802633572f03a814b86189b711d6979']['domain'] = 'kontrans.pl';
|
||||
$license['1802633572f03a814b86189b711d6979']['valid_to_date'] = '';
|
||||
$license['1802633572f03a814b86189b711d6979']['valid_to_version'] = '';
|
||||
|
||||
$update_key = $_GET['key'];
|
||||
if (!isset($license[$_GET['key']]))
|
||||
die();
|
||||
|
||||