diff --git a/.serena/project.yml b/.serena/project.yml
index 83717c5..4eb1cd9 100644
--- a/.serena/project.yml
+++ b/.serena/project.yml
@@ -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:
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..ad33427
--- /dev/null
+++ b/CLAUDE.md
@@ -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.
\ No newline at end of file
diff --git a/admin/ajax.php b/admin/ajax.php
index 61fff4e..a115fab 100644
--- a/admin/ajax.php
+++ b/admin/ajax.php
@@ -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' );
diff --git a/admin/index.php b/admin/index.php
index a29ddc4..4568517 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -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' );
diff --git a/ajax.php b/ajax.php
index 78caebb..b37fbd8 100644
--- a/ajax.php
+++ b/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' );
diff --git a/api.php b/api.php
index 5fdb974..722fe2b 100644
--- a/api.php
+++ b/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');
diff --git a/autoload/Domain/Languages/LanguagesRepository.php b/autoload/Domain/Languages/LanguagesRepository.php
new file mode 100644
index 0000000..9f6fe89
--- /dev/null
+++ b/autoload/Domain/Languages/LanguagesRepository.php
@@ -0,0 +1,213 @@
+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 ] );
+ }
+}
diff --git a/autoload/Domain/Settings/SettingsRepository.php b/autoload/Domain/Settings/SettingsRepository.php
new file mode 100644
index 0000000..6105c2d
--- /dev/null
+++ b/autoload/Domain/Settings/SettingsRepository.php
@@ -0,0 +1,72 @@
+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;
+ }
+}
diff --git a/autoload/Domain/User/UserRepository.php b/autoload/Domain/User/UserRepository.php
new file mode 100644
index 0000000..46d7556
--- /dev/null
+++ b/autoload/Domain/User/UserRepository.php
@@ -0,0 +1,235 @@
+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 ] );
+ }
+}
diff --git a/autoload/Shared/Cache/CacheHandler.php b/autoload/Shared/Cache/CacheHandler.php
new file mode 100644
index 0000000..d58ab33
--- /dev/null
+++ b/autoload/Shared/Cache/CacheHandler.php
@@ -0,0 +1,47 @@
+ $data[0] )
+ {
+ if ( file_exists( $filename ) )
+ unlink( $filename );
+ return false;
+ }
+
+ return $data[1];
+ }
+}
diff --git a/autoload/Shared/Helpers/Helpers.php b/autoload/Shared/Helpers/Helpers.php
new file mode 100644
index 0000000..e4a4399
--- /dev/null
+++ b/autoload/Shared/Helpers/Helpers.php
@@ -0,0 +1,1281 @@
+readImage($file);
+
+ if ($file_type === 'png')
+ {
+ $image->setImageFormat('webp');
+ $image->setImageCompressionQuality($compression_quality);
+ $image->setOption('webp:lossless', 'true');
+ }
+
+ $image->writeImage($output_file);
+ return $output_file;
+ }
+
+ return false;
+ }
+
+ static public function is_array_fix($value)
+ {
+ if (is_array($value) and count($value))
+ return true;
+ return false;
+ }
+
+ public static function suAppendHtmlById(&$s, $sId, $sHtml, &$oDoc = null)
+ {
+ return self::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, true, false);
+ }
+
+ public static function suInsertHtmlById(&$s, $sId, $sHtml, &$oDoc = null)
+ {
+ return self::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, false, true);
+ }
+
+ public static function suAddHtmlBeforeById(&$s, $sId, $sHtml, &$oDoc = null)
+ {
+ return self::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, false, true, true);
+ }
+
+ public static function suAddHtmlAfterById(&$s, $sId, $sHtml, &$oDoc = null)
+ {
+ return self::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, true, false, true);
+ }
+
+ public static function suSetHtmlById(&$s, $sId, $sHtml, &$oDoc = null)
+ {
+ return self::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, true, true);
+ }
+
+ public static function suReplaceHtmlElementById(&$s, $sId, $sHtml, &$oDoc = null)
+ {
+ return self::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, false, false);
+ }
+
+ public static function suRemoveHtmlElementById(&$s, $sId, &$oDoc = null)
+ {
+ return self::suSetHtmlElementById($oDoc, $s, $sId, null, false, false);
+ }
+
+ public static function suSetHtmlElementById(&$oDoc, &$s, $sId, $sHtml, $bAppend = false, $bInsert = false, $bAddToOuter = false)
+ {
+ if (self::suIsValidString($s) && self::suIsValidString($sId))
+ {
+ $bCreate = true;
+ if (is_object($oDoc))
+ {
+ if (!($oDoc instanceof \DOMDocument))
+ {
+ return false;
+ }
+ $bCreate = false;
+ }
+
+ if ($bCreate)
+ {
+ $oDoc = new \DOMDocument();
+ }
+
+ libxml_use_internal_errors(true);
+ $oDoc->loadHTML($s, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
+ libxml_use_internal_errors(false);
+ $oNode = $oDoc->getElementById($sId);
+
+ if (is_object($oNode))
+ {
+ $bReplaceOuter = (!$bAppend && !$bInsert);
+
+ $sId = uniqid('SHEBI-');
+ $aId = array("", "");
+
+ if ($bReplaceOuter)
+ {
+ if (self::suIsValidString($sHtml))
+ {
+ $oNode->parentNode->replaceChild($oDoc->createComment($sId), $oNode);
+ $s = $oDoc->saveHtml();
+ $s = str_replace($aId, $sHtml, $oDoc->saveHtml());
+ }
+ else
+ {
+ $oNode->parentNode->removeChild($oNode);
+ $s = $oDoc->saveHtml();
+ }
+ return true;
+ }
+
+ $bReplaceInner = ($bAppend && $bInsert);
+ $sThis = null;
+
+ if (!$bReplaceInner)
+ {
+ $sThis = $oDoc->saveHTML($oNode);
+ $sThis = ($bInsert ? $sHtml : '') . ($bAddToOuter ? $sThis : (substr($sThis, strpos($sThis, '>') + 1, - (strlen($oNode->nodeName) + 3)))) . ($bAppend ? $sHtml : '');
+ }
+
+ if (!$bReplaceInner && $bAddToOuter)
+ {
+ $oNode->parentNode->replaceChild($oDoc->createComment($sId), $oNode);
+ $sId = &$aId;
+ }
+ else
+ {
+ $oNode->nodeValue = $sId;
+ }
+
+ $s = str_replace($sId, $bReplaceInner ? $sHtml : $sThis, $oDoc->saveHtml());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static function suIsValidString(&$s, &$iLen = null, $minLen = null, $maxLen = null)
+ {
+ if (!is_string($s) || !isset($s[0]))
+ {
+ return false;
+ }
+
+ if ($iLen !== null)
+ {
+ $iLen = strlen($s);
+ }
+
+ return (($minLen === null ? true : ($minLen > 0 && isset($s[$minLen - 1]))) &&
+ $maxLen === null ? true : ($maxLen >= $minLen && !isset($s[$maxLen])));
+ }
+
+ public static function log_db_error($db_tmp, $debug)
+ {
+ global $settings;
+
+ if ($settings['mysql_debug'])
+ {
+ $out .= date('Y-m-d H:i:s') . PHP_EOL;
+ $out .= $db_tmp->error()[2] . PHP_EOL;
+ $out .= $db_tmp->last() . PHP_EOL;
+ $out .= $debug[0]['file'] . ' | ' . $debug[0]['line'] . PHP_EOL;
+ $out .= '--------------------------------------------------------------------------------------------' . PHP_EOL;
+
+ if (!is_dir('logs'))
+ mkdir('logs', 0755, true);
+
+ $content = file_get_contents('logs/' . date('Y-m-d') . '.txt');
+ $content = $out . $content;
+ file_put_contents('logs/' . date('Y-m-d') . '.txt', $content);
+ die('Blad bazy danych.');
+ }
+ }
+
+ public static function lang($text)
+ {
+ global $lang;
+ return $lang[$text] ? $lang[$text] : 'LANG-' . $text;
+ }
+
+ public static function cache_read($path)
+ {
+ $f = fopen($path, 'r');
+ $buffer = '';
+ while (!feof($f))
+ {
+ $buffer .= fread($f, 2048);
+ }
+ fclose($f);
+ return $buffer;
+ }
+
+ public static function cache_write($cache_url, $cache_file, $html)
+ {
+ $dir = md5($cache_url);
+ $dir = 'cache/' . $dir[0] . '/' . $dir[1] . '/';
+
+ if (!is_dir($dir))
+ mkdir($dir, 0755, true);
+
+ $f = fopen($cache_file, 'w');
+ fwrite($f, $html, strlen($html));
+ fclose($f);
+ return true;
+ }
+
+ public static function cache_file_url($cache_url)
+ {
+ $cache = md5($cache_url);
+ return 'cache/' . $cache[1] . '/' . $cache[2] . '/' . $cache;
+ }
+
+ public static function date_diff($data1, $data2, $rodz = '60')
+ {
+ $data1 = date('Y-m-d H:i:s', strtotime($data1));
+ $data2 = date('Y-m-d H:i:s', strtotime($data2));
+
+ $d1_t = explode(' ', $data1);
+ $d1_tt = explode('-', $d1_t[0]);
+ $rok1 = $d1_tt[0];
+ $mc1 = $d1_tt[1];
+ $d1 = $d1_tt[2];
+ $d1_tt = explode(':', $d1_t[1]);
+ $g1 = $d1_tt[0];
+ $m1 = $d1_tt[1];
+ $s1 = $d1_tt[2];
+
+ $d2_t = explode(' ', $data2);
+ $d2_tt = explode('-', $d2_t[0]);
+ $rok2 = $d2_tt[0];
+ $mc2 = $d2_tt[1];
+ $d2 = $d2_tt[2];
+ $d2_tt = explode(':', $d2_t[1]);
+ $g2 = $d2_tt[0];
+ $m2 = $d2_tt[1];
+ $s2 = $d2_tt[2];
+
+ $lt = mktime($g2, $m2, $s2, $mc2, $d2, $rok2);
+ $st = mktime($g1, $m1, $s1, $mc1, $d1, $rok1);
+
+ return round(($lt - $st) / $rodz);
+ }
+
+ public static function is_token_valid($token)
+ {
+ if (!empty($_SESSION['tokens'][$token]))
+ {
+ unset($_SESSION['tokens'][$token]);
+ return true;
+ }
+ return false;
+ }
+
+ public static function get_token()
+ {
+ $token = sha1(mt_rand());
+ if (!isset($_SESSION['tokens']))
+ $_SESSION['tokens'] = [$token => 1];
+ else
+ $_SESSION['tokens'][$token] = 1;
+ return $token;
+ }
+
+ public static function get_domain($url)
+ {
+ $parseUrl = parse_url(trim($url));
+ return trim($parseUrl[host] ? str_replace('www.', '', $parseUrl[host]) : str_replace('www.', '', array_shift(explode('/', $parseUrl[path], 2))));
+ }
+
+ public static function get_domain_url($url)
+ {
+ global $settings;
+
+ $settings['link_version'] ? $www = 'www.' : $www = '';
+ $settings['ssl'] == true ? $domain_prefix = 'https' : $domain_prefix = 'http';
+ return $domain_prefix . '://' . $www . self::get_domain($url);
+ }
+
+ public static function max_db_value($table, $column)
+ {
+ global $mdb;
+ $results = $mdb->query('SELECT MAX(' . $column . ') FROM ' . $table)->fetchAll();
+ return $results[0][0];
+ }
+
+ public static function shuffle_assoc($list)
+ {
+ if (!is_array($list))
+ return $list;
+
+ $keys = array_keys($list);
+ shuffle($keys);
+ $random = array();
+ foreach ($keys as $key)
+ $random[$key] = $list[$key];
+ return $random;
+ }
+
+ public static function escape($value)
+ {
+ $return = '';
+ for ($i = 0; $i < strlen($value); ++$i)
+ {
+ $char = $value[$i];
+ $ord = ord($char);
+ if ($char !== "'" && $char !== "\"" && $char !== '\\' && $ord >= 32 && $ord <= 126)
+ $return .= $char;
+ else
+ $return .= '\\x' . dechex($ord);
+ }
+ return $return;
+ }
+
+ public static function is_bot()
+ {
+ $bots = ["Slurp", "Scooter", "URL_Spider_SQL", "Googlebot", "Firefly", "WebBug", "WebFindBot", "crawler", "appie", "msnbot", "InfoSeek", "FAST", "Spade", "NationalDirectory"];
+ $agent = strtolower($_SERVER['HTTP_USER_AGENT']);
+ foreach ($bots as $bot)
+ if (stripos($agent, $bot) !== false)
+ return true;
+ return false;
+ }
+
+ public static function months()
+ {
+ return array(1 => 'Styczeń', 2 => 'Luty', 3 => 'Marzec', 4 => 'Kwiecień', 5 => 'Maj', 6 => 'Czerwiec', 7 => 'Lipiec', 8 => 'Sierpień', 9 => 'Wrzesień', 10 => 'Październik', 11 => 'Listopad', 12 => 'Grudzień');
+ }
+
+ public static function months_short()
+ {
+ return [1 => 'sty', 2 => 'lut', 3 => 'mar', 4 => 'kwi', 5 => 'maj', 6 => 'cze', 7 => 'lip', 8 => 'sie', 9 => 'wrz', 10 => 'paź', 11 => 'lis', 12 => 'gru'];
+ }
+
+ public static function chmod_r($path, $chmod = 0755)
+ {
+ $dir = new \DirectoryIterator($path);
+ foreach ($dir as $item)
+ {
+ chmod($item->getPathname(), $chmod);
+ if ($item->isDir() && !$item->isDot())
+ self::chmod_r($item->getPathname());
+ }
+ }
+
+ public static function rrmdir($dir)
+ {
+ if (is_dir($dir))
+ {
+ $files = scandir($dir);
+ foreach ($files as $file)
+ if ($file != "." && $file != "..")
+ self::rrmdir("$dir/$file");
+ rmdir($dir);
+ }
+ else if (file_exists($dir))
+ unlink($dir);
+ }
+
+ public static function rcopy($src, $dst)
+ {
+ if (is_dir($src))
+ {
+ mkdir($dst, 0755);
+ $files = scandir($src);
+ foreach ($files as $file)
+ if ($file != "." && $file != "..")
+ self::rcopy("$src/$file", "$dst/$file");
+ }
+ else if (file_exists($src))
+ copy($src, $dst);
+
+ self::rrmdir($src);
+ }
+
+ public static function is_mobile()
+ {
+ $detect = new \Mobile_Detect;
+ return $detect->isMobile();
+ }
+
+ public static function get_new_version()
+ {
+ global $settings;
+
+ if ($version = self::get_session('new-version'))
+ return $version;
+
+ $versions = file_get_contents('http://www.cmspro.project-dc.pl/updates/versions.php?key=' . $settings['update_key']);
+ $versions = explode(PHP_EOL, $versions);
+ $version = str_replace(',', '.', max($versions));
+
+ self::set_session('new-version', $version);
+
+ return $version;
+ }
+
+ public static function get_version()
+ {
+ return str_replace(',', '.', @file_get_contents('../libraries/version.ini'));
+ }
+
+ public static function pre($data, $type = '')
+ {
+ $data = str_replace('Array
+ (', '', $data);
+ $data = str_replace(')', '', $data);
+
+ echo '
Nie znaleziono pliku widoku:
' . $this->dir . $file . '.php';
+ }
+
+ public function __set( $name, $value )
+ {
+ $this->vars[ $name ] = $value;
+ }
+
+ public function __get( $name )
+ {
+ return $this->vars[ $name ];
+ }
+}
diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php
index 71527ae..1634249 100644
--- a/autoload/admin/class.Site.php
+++ b/autoload/admin/class.Site.php
@@ -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;
}
diff --git a/autoload/admin/factory/class.Languages.php b/autoload/admin/factory/class.Languages.php
index 403fcbd..d97696c 100644
--- a/autoload/admin/factory/class.Languages.php
+++ b/autoload/admin/factory/class.Languages.php
@@ -1,181 +1,64 @@
-
+ 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();
}
}
-?>
\ No newline at end of file
diff --git a/autoload/admin/factory/class.Settings.php b/autoload/admin/factory/class.Settings.php
index 15f608a..827c457 100644
--- a/autoload/admin/factory/class.Settings.php
+++ b/autoload/admin/factory/class.Settings.php
@@ -1,147 +1,73 @@
-
+ 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;
- }
-
}
-?>
\ No newline at end of file
diff --git a/autoload/admin/factory/class.Users.php b/autoload/admin/factory/class.Users.php
index 8e5f105..85f6389 100644
--- a/autoload/admin/factory/class.Users.php
+++ b/autoload/admin/factory/class.Users.php
@@ -1,306 +1,83 @@
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 );
}
}
diff --git a/autoload/class.Cache.php b/autoload/class.Cache.php
index d268fcb..a8f0a09 100644
--- a/autoload/class.Cache.php
+++ b/autoload/class.Cache.php
@@ -1,46 +1,8 @@
$data[0] )
- {
- if ( file_exists( $filename ) )
- unlink( $filename );
- return false;
- }
-
- return $data[1];
- }
}
-?>
\ No newline at end of file
diff --git a/autoload/class.Html.php b/autoload/class.Html.php
index d2500c9..1bbde05 100644
--- a/autoload/class.Html.php
+++ b/autoload/class.Html.php
@@ -1,91 +1,8 @@
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' );
- }
}
diff --git a/autoload/class.Image.php b/autoload/class.Image.php
index 0322890..6b1a239 100644
--- a/autoload/class.Image.php
+++ b/autoload/class.Image.php
@@ -1,312 +1,8 @@
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;
- }
-}
\ No newline at end of file
+}
diff --git a/autoload/class.S.php b/autoload/class.S.php
index 1a875d8..993822f 100644
--- a/autoload/class.S.php
+++ b/autoload/class.S.php
@@ -1,1282 +1,61 @@
readImage($file);
-
- if ($file_type === 'png')
- {
- $image->setImageFormat('webp');
- $image->setImageCompressionQuality($compression_quality);
- $image->setOption('webp:lossless', 'true');
- }
-
- $image->writeImage($output_file);
- return $output_file;
- }
-
- return false;
- }
-
- static public function is_array_fix($value)
- {
- if (is_array($value) and count($value))
- return true;
- return false;
- }
+ // Metody z parametrami by-reference — jawne delegacje
public static function suAppendHtmlById(&$s, $sId, $sHtml, &$oDoc = null)
{
- return \S::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, true, false);
+ return \Shared\Helpers\Helpers::suAppendHtmlById($s, $sId, $sHtml, $oDoc);
}
public static function suInsertHtmlById(&$s, $sId, $sHtml, &$oDoc = null)
{
- return \S::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, false, true);
+ return \Shared\Helpers\Helpers::suInsertHtmlById($s, $sId, $sHtml, $oDoc);
}
public static function suAddHtmlBeforeById(&$s, $sId, $sHtml, &$oDoc = null)
{
- return \S::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, false, true, true);
+ return \Shared\Helpers\Helpers::suAddHtmlBeforeById($s, $sId, $sHtml, $oDoc);
}
public static function suAddHtmlAfterById(&$s, $sId, $sHtml, &$oDoc = null)
{
- return \S::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, true, false, true);
+ return \Shared\Helpers\Helpers::suAddHtmlAfterById($s, $sId, $sHtml, $oDoc);
}
public static function suSetHtmlById(&$s, $sId, $sHtml, &$oDoc = null)
{
- return \S::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, true, true);
+ return \Shared\Helpers\Helpers::suSetHtmlById($s, $sId, $sHtml, $oDoc);
}
public static function suReplaceHtmlElementById(&$s, $sId, $sHtml, &$oDoc = null)
{
- return \S::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, false, false);
+ return \Shared\Helpers\Helpers::suReplaceHtmlElementById($s, $sId, $sHtml, $oDoc);
}
public static function suRemoveHtmlElementById(&$s, $sId, &$oDoc = null)
{
- return \S::suSetHtmlElementById($oDoc, $s, $sId, null, false, false);
+ return \Shared\Helpers\Helpers::suRemoveHtmlElementById($s, $sId, $oDoc);
}
public static function suSetHtmlElementById(&$oDoc, &$s, $sId, $sHtml, $bAppend = false, $bInsert = false, $bAddToOuter = false)
{
- if (\S::suIsValidString($s) && \S::suIsValidString($sId))
- {
- $bCreate = true;
- if (is_object($oDoc))
- {
- if (!($oDoc instanceof DOMDocument))
- {
- return false;
- }
- $bCreate = false;
- }
-
- if ($bCreate)
- {
- $oDoc = new DOMDocument();
- }
-
- libxml_use_internal_errors(true);
- $oDoc->loadHTML($s, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
- libxml_use_internal_errors(false);
- $oNode = $oDoc->getElementById($sId);
-
- if (is_object($oNode))
- {
- $bReplaceOuter = (!$bAppend && !$bInsert);
-
- $sId = uniqid('SHEBI-');
- $aId = array("", "");
-
- if ($bReplaceOuter)
- {
- if (\S::suIsValidString($sHtml))
- {
- $oNode->parentNode->replaceChild($oDoc->createComment($sId), $oNode);
- $s = $oDoc->saveHtml();
- $s = str_replace($aId, $sHtml, $oDoc->saveHtml());
- }
- else
- {
- $oNode->parentNode->removeChild($oNode);
- $s = $oDoc->saveHtml();
- }
- return true;
- }
-
- $bReplaceInner = ($bAppend && $bInsert);
- $sThis = null;
-
- if (!$bReplaceInner)
- {
- $sThis = $oDoc->saveHTML($oNode);
- $sThis = ($bInsert ? $sHtml : '') . ($bAddToOuter ? $sThis : (substr($sThis, strpos($sThis, '>') + 1, - (strlen($oNode->nodeName) + 3)))) . ($bAppend ? $sHtml : '');
- }
-
- if (!$bReplaceInner && $bAddToOuter)
- {
- $oNode->parentNode->replaceChild($oDoc->createComment($sId), $oNode);
- $sId = &$aId;
- }
- else
- {
- $oNode->nodeValue = $sId;
- }
-
- $s = str_replace($sId, $bReplaceInner ? $sHtml : $sThis, $oDoc->saveHtml());
- return true;
- }
- }
- return false;
+ return \Shared\Helpers\Helpers::suSetHtmlElementById($oDoc, $s, $sId, $sHtml, $bAppend, $bInsert, $bAddToOuter);
}
public static function suIsValidString(&$s, &$iLen = null, $minLen = null, $maxLen = null)
{
- if (!is_string($s) || !isset($s{
- 0}))
- {
- return false;
- }
-
- if ($iLen !== null)
- {
- $iLen = strlen($s);
- }
-
- return (($minLen === null ? true : ($minLen > 0 && isset($s{
- $minLen - 1}))) &&
- $maxLen === null ? true : ($maxLen >= $minLen && !isset($s{
- $maxLen})));
+ return \Shared\Helpers\Helpers::suIsValidString($s, $iLen, $minLen, $maxLen);
}
- public static function log_db_error($db_tmp, $debug)
+ // Pozostałe metody — delegacja przez __callStatic
+
+ public static function __callStatic($name, $args)
{
- global $settings;
-
- if ($settings['mysql_debug'])
- {
- $out .= date('Y-m-d H:i:s') . PHP_EOL;
- $out .= $db_tmp->error()[2] . PHP_EOL;
- $out .= $db_tmp->last() . PHP_EOL;
- $out .= $debug[0]['file'] . ' | ' . $debug[0]['line'] . PHP_EOL;
- $out .= '--------------------------------------------------------------------------------------------' . PHP_EOL;
-
- if (!is_dir('logs'))
- mkdir('logs', 0755, true);
-
- $content = file_get_contents('logs/' . date('Y-m-d') . '.txt');
- $content = $out . $content;
- file_put_contents('logs/' . date('Y-m-d') . '.txt', $content);
- die('Blad bazy danych.');
- }
- }
-
- public static function lang($text)
- {
- global $lang;
- return $lang[$text] ? $lang[$text] : 'LANG-' . $text;
- }
-
- public static function cache_read($path)
- {
- $f = fopen($path, 'r');
- $buffer = '';
- while (!feof($f))
- {
- $buffer .= fread($f, 2048);
- }
- fclose($f);
- return $buffer;
- }
-
- public static function cache_write($cache_url, $cache_file, $html)
- {
- $dir = md5($cache_url);
- $dir = 'cache/' . $dir[0] . '/' . $dir[1] . '/';
-
- if (!is_dir($dir))
- mkdir($dir, 0755, true);
-
- $f = fopen($cache_file, 'w');
- fwrite($f, $html, strlen($html));
- fclose($f);
- return true;
- }
-
- public static function cache_file_url($cache_url)
- {
- $cache = md5($cache_url);
- return 'cache/' . $cache{
- 1} . '/' . $cache{
- 2} . '/' . $cache;
- }
-
- public static function date_diff($data1, $data2, $rodz = '60')
- {
- $data1 = date('Y-m-d H:i:s', strtotime($data1));
- $data2 = date('Y-m-d H:i:s', strtotime($data2));
-
- $d1_t = explode(' ', $data1);
- $d1_tt = explode('-', $d1_t[0]);
- $rok1 = $d1_tt[0];
- $mc1 = $d1_tt[1];
- $d1 = $d1_tt[2];
- $d1_tt = explode(':', $d1_t[1]);
- $g1 = $d1_tt[0];
- $m1 = $d1_tt[1];
- $s1 = $d1_tt[2];
-
- $d2_t = explode(' ', $data2);
- $d2_tt = explode('-', $d2_t[0]);
- $rok2 = $d2_tt[0];
- $mc2 = $d2_tt[1];
- $d2 = $d2_tt[2];
- $d2_tt = explode(':', $d2_t[1]);
- $g2 = $d2_tt[0];
- $m2 = $d2_tt[1];
- $s2 = $d2_tt[2];
-
- $lt = mktime($g2, $m2, $s2, $mc2, $d2, $rok2);
- $st = mktime($g1, $m1, $s1, $mc1, $d1, $rok1);
-
- return round(($lt - $st) / $rodz);
- }
-
- public static function is_token_valid($token)
- {
- if (!empty($_SESSION['tokens'][$token]))
- {
- unset($_SESSION['tokens'][$token]);
- return true;
- }
- return false;
- }
-
- public static function get_token()
- {
- $token = sha1(mt_rand());
- if (!isset($_SESSION['tokens']))
- $_SESSION['tokens'] = [$token => 1];
- else
- $_SESSION['tokens'][$token] = 1;
- return $token;
- }
-
- public static function get_domain($url)
- {
- $parseUrl = parse_url(trim($url));
- return trim($parseUrl[host] ? str_replace('www.', '', $parseUrl[host]) : str_replace('www.', '', array_shift(explode('/', $parseUrl[path], 2))));
- }
-
- public static function get_domain_url($url)
- {
- global $settings;
-
- $settings['link_version'] ? $www = 'www.' : $www = '';
- $settings['ssl'] == true ? $domain_prefix = 'https' : $domain_prefix = 'http';
- return $domain_prefix . '://' . $www . \S::get_domain($url);
- }
-
- public static function max_db_value($table, $column)
- {
- global $mdb;
- $results = $mdb->query('SELECT MAX(' . $column . ') FROM ' . $table)->fetchAll();
- return $results[0][0];
- }
-
- public static function shuffle_assoc($list)
- {
- if (!is_array($list))
- return $list;
-
- $keys = array_keys($list);
- shuffle($keys);
- $random = array();
- foreach ($keys as $key)
- $random[$key] = $list[$key];
- return $random;
- }
-
- public static function escape($value)
- {
- $return = '';
- for ($i = 0; $i < strlen($value); ++$i)
- {
- $char = $value[$i];
- $ord = ord($char);
- if ($char !== "'" && $char !== "\"" && $char !== '\\' && $ord >= 32 && $ord <= 126)
- $return .= $char;
- else
- $return .= '\\x' . dechex($ord);
- }
- return $return;
- }
-
- public static function is_bot()
- {
- $bots = ["Slurp", "Scooter", "URL_Spider_SQL", "Googlebot", "Firefly", "WebBug", "WebFindBot", "crawler", "appie", "msnbot", "InfoSeek", "FAST", "Spade", "NationalDirectory"];
- $agent = strtolower($_SERVER['HTTP_USER_AGENT']);
- foreach ($bots as $bot)
- if (stripos($agent, $bot) !== false)
- return true;
- return false;
- }
-
- public static function months()
- {
- return array(1 => 'Styczeń', 2 => 'Luty', 3 => 'Marzec', 4 => 'Kwiecień', 5 => 'Maj', 6 => 'Czerwiec', 7 => 'Lipiec', 8 => 'Sierpień', 9 => 'Wrzesień', 10 => 'Październik', 11 => 'Listopad', 12 => 'Grudzień');
- }
-
- public static function months_short()
- {
- return [1 => 'sty', 2 => 'lut', 3 => 'mar', 4 => 'kwi', 5 => 'maj', 6 => 'cze', 7 => 'lip', 8 => 'sie', 9 => 'wrz', 10 => 'paź', 11 => 'lis', 12 => 'gru'];
- }
-
- public static function chmod_r($path, $chmod = 0755)
- {
- $dir = new DirectoryIterator($path);
- foreach ($dir as $item)
- {
- chmod($item->getPathname(), $chmod);
- if ($item->isDir() && !$item->isDot())
- self::chmod_r($item->getPathname());
- }
- }
-
- public static function rrmdir($dir)
- {
- if (is_dir($dir))
- {
- $files = scandir($dir);
- foreach ($files as $file)
- if ($file != "." && $file != "..")
- \S::rrmdir("$dir/$file");
- rmdir($dir);
- }
- else if (file_exists($dir))
- unlink($dir);
- }
-
- public static function rcopy($src, $dst)
- {
- if (is_dir($src))
- {
- mkdir($dst, 0755);
- $files = scandir($src);
- foreach ($files as $file)
- if ($file != "." && $file != "..")
- \S::rcopy("$src/$file", "$dst/$file");
- }
- else if (file_exists($src))
- copy($src, $dst);
-
- \S::rrmdir($src);
- }
-
- public static function is_mobile()
- {
- $detect = new \Mobile_Detect;
- return $detect->isMobile();
- }
-
- public static function get_new_version()
- {
- global $settings;
-
- if ($version = \S::get_session('new-version'))
- return $version;
-
- $versions = file_get_contents('http://www.cmspro.project-dc.pl/updates/versions.php?key=' . $settings['update_key']);
- $versions = explode(PHP_EOL, $versions);
- $version = str_replace(',', '.', max($versions));
-
- \S::set_session('new-version', $version);
-
- return $version;
- }
-
- public static function get_version()
- {
- return str_replace(',', '.', @file_get_contents('../libraries/version.ini'));
- }
-
- public static function pre($data, $type = '')
- {
- $data = str_replace('Array
- (', '', $data);
- $data = str_replace(')', '', $data);
-
- echo '
' . print_r($data, true) . '
';
- }
-
- public static function json_to_array($json)
- {
- $values_tmp = json_decode($json, true);
-
- if (is_array($values_tmp))
- foreach ($values_tmp as $val)
- {
- if (isset($values[$val['name']]))
- {
- if (is_array($values[$val['name']]))
- $values[$val['name']][] = $val['value'];
- else
- $values[$val['name']] = array($values[$val['name']], $val['value']);
- }
- else
- $values[$val['name']] = $val['value'];
- }
- return $values;
- }
-
- public static function set_session($var, $val)
- {
- $_SESSION[$var] = $val;
- }
-
- public static function get_session($var)
- {
- return $_SESSION[$var];
- }
-
- public static function delete_session($var)
- {
- unset($_SESSION[$var]);
- }
-
- public static function get($var, $strip_tags = false)
- {
- if (isset($_POST[$var]))
- {
- if (is_string($_POST[$var]))
- {
- if ($strip_tags)
- return trim(strip_tags($_POST[$var]));
- else
- return trim($_POST[$var]);
- }
- else
- return $_POST[$var];
- }
- else
- {
- if (isset($_GET[$var]))
- {
- if (is_string($_GET[$var]))
- {
- if ($strip_tags)
- return trim(strip_tags($_GET[$var]));
- else
- return trim($_GET[$var]);
- }
- else
- return $_GET[$var];
- }
- }
- }
-
- public static function set_message($text)
- {
- self::set_session('message', $text);
- }
-
- public static function alert($text, $class = 'alert-success')
- {
- self::set_session('alert', $text);
- self::set_session('alert-class', $class);
- }
-
- static public function get_language_domain($lang_id)
- {
- global $mdb;
-
- $settings = \front\factory\Settings::settings_details();
- $default_domain = \admin\factory\Languages::default_domain();
- $settings['link_version'] ? $www = 'www.' : $www = '';
-
- $domain = $mdb->get('pp_langs', 'domain', ['id' => $lang_id]);
- if (!$domain)
- {
- if ($default_domain)
- return $www . $default_domain;
- else
- return $www . preg_replace('#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME']);
- }
- else
- {
- return $www . $domain;
- }
- }
-
- public static function htacces($dir = '../')
- {
- global $mdb;
-
- $settings = \front\factory\Settings::settings_details();
- $default_domain = \admin\factory\Languages::default_domain();
- $available_domains = \admin\factory\Languages::available_domains();
-
- $settings['link_version'] ? $www = 'www.' : $www = '';
-
- $settings['ssl'] == true ? $domain_prefix = 'https' : $domain_prefix = 'http';
-
- $default_domain ? $url = $default_domain : $url = preg_replace('#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME']);
-
- $robots = 'User-agent: *' . PHP_EOL;
- $robots .= 'Allow: /' . PHP_EOL;
-
- unlink('../sitemap.xml');
- if (is_array($available_domains) and !empty($available_domains))
- {
- foreach ($available_domains as $domain)
- {
- $site_map[$domain['domain']] = '' . PHP_EOL;
- $site_map[$domain['domain']] .= '
' . PHP_EOL;
- $site_map[$domain['domain']] .= '' . PHP_EOL;
- $site_map[$domain['domain']] .= '' . $domain_prefix . '://' . $www . $domain['domain'] . '' . PHP_EOL;
- $site_map[$domain['domain']] .= '' . date('Y-m-d') . '' . PHP_EOL;
- $site_map[$domain['domain']] .= 'daily' . PHP_EOL;
- $site_map[$domain['domain']] .= '1' . PHP_EOL;
- $site_map[$domain['domain']] .= '' . PHP_EOL;
- }
- }
- else
- {
- $site_map[$url] = '' . PHP_EOL;
- $site_map[$url] .= '' . PHP_EOL;
- $site_map[$url] .= '' . PHP_EOL;
- $site_map[$url] .= '' . $domain_prefix . '://' . $www . $url . '' . PHP_EOL;
- $site_map[$url] .= '' . date('Y-m-d') . '' . PHP_EOL;
- $site_map[$url] .= 'daily' . PHP_EOL;
- $site_map[$url] .= '1' . PHP_EOL;
- $site_map[$url] .= '' . PHP_EOL;
- }
-
- $htaccess_data = file_get_contents($dir . 'libraries/htaccess.conf');
-
- /* cache */
- if ($settings['htaccess_cache'])
- {
- $htaccess_data = str_replace(
- '{HTACCESS_CACHE}',
- '' . PHP_EOL
- . 'AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/css text/javascript application/javascript application/x-javascript' . PHP_EOL
- . '' . PHP_EOL
- . '' . PHP_EOL
- . 'ExpiresActive on' . PHP_EOL
- . 'ExpiresDefault "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType text/css "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType application/json "access plus 0 seconds"' . PHP_EOL
- . 'ExpiresByType application/xml "access plus 0 seconds"' . PHP_EOL
- . 'ExpiresByType text/xml "access plus 0 seconds"' . PHP_EOL
- . 'ExpiresByType image/x-icon "access plus 1 week"' . PHP_EOL
- . 'ExpiresByType text/x-component "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType text/html "access plus 0 seconds"' . PHP_EOL
- . 'ExpiresByType application/javascript "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"' . PHP_EOL
- . 'ExpiresByType text/cache-manifest "access plus 0 seconds"' . PHP_EOL
- . 'ExpiresByType audio/ogg "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType image/gif "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType image/jpeg "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType image/webp "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType image/png "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType video/mp4 "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType video/ogg "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType video/webm "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType application/atom+xml "access plus 1 hour"' . PHP_EOL
- . 'ExpiresByType application/rss+xml "access plus 1 hour"' . PHP_EOL
- . 'ExpiresByType application/font-woff "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType application/vnd.ms-fontobject "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType application/x-font-ttf "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType font/opentype "access plus 1 year"' . PHP_EOL
- . 'ExpiresByType image/svg+xml "access plus 1 year"' . PHP_EOL
- . '',
- $htaccess_data
- );
- }
- else
- {
- $htaccess_data = str_replace(
- '{HTACCESS_CACHE}',
- '' . PHP_EOL
- . 'Header set Cache-Control "no-cache, no-store, must-revalidate"' . PHP_EOL
- . 'Header set Pragma "no-cache"' . PHP_EOL
- . 'Header set Expires 0' . PHP_EOL
- . '',
- $htaccess_data
- );
- }
-
- /* języki w domenie głównej */
- $results = $mdb->select('pp_langs', ['id'], ['AND' => ['status' => 1, 'domain' => null], 'ORDER' => ['o' => 'ASC']]);
- if (is_array($results)) foreach ($results as $row)
- {
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $row['id'] . '/$ index.php?a=change_language&id=' . $row['id'] . ' [L]';
- }
-
- $htaccess_data .= PHP_EOL;
-
- $results = $mdb->select('pp_langs', ['id', 'start', 'domain', 'main_domain'], ['status' => 1, 'ORDER' => ['o' => 'ASC']]);
- if (is_array($results)) foreach ($results as $row)
- {
- $row['domain'] ? $url_tmp = $row['domain'] : $url_tmp = $url;
-
- !$row['start'] ? $language_link = $row['id'] . '/' : $language_link = '';
-
- $results2 = $mdb->select(
- 'pp_pages_langs',
- ['[><]pp_pages' => ['page_id' => 'id']],
- ['seo_link', 'title', 'page_id', 'noindex', 'start', 'page_type'],
- ['AND' => ['status' => 1, 'lang_id' => $row['id'], 'block_direct_access' => 0], 'ORDER' => ['start' => 'DESC', 'o' => 'ASC']]
- );
- if (is_array($results2)) foreach ($results2 as $row2)
- {
- if ($row2['title'])
- {
- /* sitemap.xml */
- if ($row2['page_type'] != 3 and !$row2['noindex'])
- {
- $site_map[$url_tmp] .= '' . PHP_EOL;
-
- if ($row2['seo_link'])
- {
- if ($settings['links_structure'])
- $seo = \admin\factory\Pages::google_url_preview($row2['page_id'], $row2['title'], $row[id], 0, 0, $row2['seo_link'], $language_link);
- else
- $seo = $language_link . \S::seo($row2['seo_link']);
-
- $site_map[$url_tmp] .= '' . $domain_prefix . '://' . $www . $url_tmp . '/' . $seo . '' . PHP_EOL;
- }
- else
- {
- if ($settings['links_structure'])
- $seo = \admin\factory\Pages::google_url_preview($row2['page_id'], $row2['title'], $row['id'], 0, 0, $row2['seo_link'], $language_link);
- else
- $seo = $language_link . 's-' . $row2['page_id'] . '-' . \S::seo($row2['title']);
-
- $site_map[$url_tmp] .= '' . $domain_prefix . '://' . $www . $url_tmp . '/' . $seo . '' . PHP_EOL;
- }
-
- $site_map[$url_tmp] .= '' . date('Y-m-d') . '' . PHP_EOL;
- $site_map[$url_tmp] .= 'daily' . PHP_EOL;
-
- $row['start'] ? $priority = 1 : $priority = 0.8;
-
- $site_map[$url_tmp] .= '' . $priority . '' . PHP_EOL;
- $site_map[$url_tmp] .= '' . PHP_EOL;
- }
-
- /* robotx.txt */
- if ($row2['noindex'] and $row2['page_type'] != 3)
- {
- $robots .= 'User-agent: GoogleBot' . PHP_EOL;
-
- if ($row2['seo_link'])
- {
- if ($settings['links_structure'])
- $seo = \admin\factory\Pages::google_url_preview($row2['page_id'], $row2['title'], $row[id], 0, 0, $row2['seo_link'], $language_link);
- else
- $seo = $language_link . \S::seo($row2['seo_link']);
-
- $robots .= 'Disallow: /' . $seo . '$' . PHP_EOL;
- $robots .= 'Disallow: /' . $seo . '/s/*' . PHP_EOL;
- }
- else
- {
- if ($settings['links_structure'])
- $seo = \admin\factory\Pages::google_url_preview($row2['page_id'], $row2['title'], $row['id'], 0, 0, $row2['seo_link'], $language_link);
- else
- $seo = $language_link . 's-' . $row2['page_id'] . '-' . \S::seo($row2['title']);
-
- $robots .= 'Disallow: /' . $seo . '$' . PHP_EOL;
- $robots .= 'Disallow: /' . $seo . '/s/*$' . PHP_EOL;
- }
- }
-
- /* htaccess */
- if ($row2['page_type'] != 3)
- {
- if ( $row['start'] and $row2['start'] )
- {
- $htaccess_data .= PHP_EOL . 'RewriteRule ^$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]' . PHP_EOL;
-
- if ( $row2['seo_link'] )
- {
- $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/' . \S::seo( $row2['seo_link'] ) . '(|/)$';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ ' . $domain_prefix . '://' . $www . $url_tmp . '/' . $language_link . ' [R=301,L]';
-
- $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/' . \S::seo($row2['seo_link']) . '/s/1$';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ ' . $domain_prefix . '://' . $www . $url_tmp . '/' . $language_link . ' [R=301,L]';
- }
- else
- {
- $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/s-' . $row2['page_id'] . '-' . \S::seo($row2['title']) . '$';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ ' . $domain_prefix . '://' . $www . $url_tmp . '/' . $language_link . ' [R=301,L]';
-
- $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/s-' . $row2['page_id'] . '-' . \S::seo($row2['title']) . '-s-1$';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ ' . $domain_prefix . '://' . $www . $url_tmp . '/' . $language_link . ' [R=301,L]';
- }
-
- $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} "^/$"';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . ' [L]';
-
- $htaccess_data .= PHP_EOL;
- }
-
- if ($row2['seo_link'])
- {
- if ($settings['links_structure'])
- $seo = \admin\factory\Pages::google_url_preview($row2['page_id'], $row2['title'], $row[id], 0, 0, $row2['seo_link'], $language_link);
- else
- $seo = $language_link . \S::seo($row2['seo_link']);
-
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $seo . '(|/)$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $seo . '/s/1(|/)$ ' . $seo . ' [R=301,L]';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $seo . '/s/([0-9]+)(|/)$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&bs=$1&%{QUERY_STRING} [L]';
- }
- else
- {
- if ($settings['links_structure'])
- $seo = \admin\factory\Pages::google_url_preview($row2['page_id'], $row2['title'], $row['id'], 0, 0, $row2['seo_link'], $language_link);
- else
- $seo = $language_link . 's-' . $row2['page_id'] . '-' . \S::seo($row2['title']);
-
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $seo . '(|/)$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $seo . '/s/1(|/)$ ' . $seo . ' [R=301,L]';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $seo . '/s/([0-9]+)(|/)$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&bs=$1&%{QUERY_STRING} [L]';
- }
- $htaccess_data .= PHP_EOL;
- }
- }
- }
-
- $results2 = $mdb->select(
- 'pp_articles_langs',
- ['[><]pp_articles' => ['article_id' => 'id']],
- ['seo_link', 'title', 'article_id', 'noindex', 'copy_from', 'block_direct_access'],
- ['AND' => ['status' => 1, 'lang_id' => $row['id']]]
- );
- if (is_array($results2)) foreach ($results2 as $row2)
- {
- $domain = \S::get_language_domain($row['id']);
-
- if ($row2['copy_from'] != null)
- {
- $results_tmp = $mdb->get(
- 'pp_articles_langs',
- [
- 'seo_link',
- 'title'
- ],
- [
- 'AND' => [
- 'article_id' => $row2['article_id'],
- 'lang_id' => $row2['copy_from']
- ]
- ]
- );
- $row2['seo_link'] = $results_tmp['seo_link'];
- $row2['title'] = $results_tmp['title'];
- }
-
- /* sitemap */
- if (!$row2['block_direct_access'] and $row2['title'])
- {
- $site_map[$url_tmp] .= '' . PHP_EOL;
- if ($row2['seo_link'])
- $site_map[$url_tmp] .= '' . $domain_prefix . '://' . $www . $url_tmp . '/' . $language_link . \S::seo($row2['seo_link']) . '' . PHP_EOL;
- else
- $site_map[$url_tmp] .= '' . $domain_prefix . '://' . $www . $url_tmp . '/' . $language_link . 'a-' . $row2['article_id'] . '-' . self::seo($row2['title']) . '' . PHP_EOL;
- $site_map[$url_tmp] .= '' . date('Y-m-d') . '' . PHP_EOL;
- $site_map[$url_tmp] .= 'daily' . PHP_EOL;
- $site_map[$url_tmp] .= '0.6' . PHP_EOL;
- $site_map[$url_tmp] .= '' . PHP_EOL;
- }
-
- /* robots.txt */
- if ($row2['noindex'])
- {
- $robots .= 'User-agent: GoogleBot' . PHP_EOL;
- if ($row2['seo_link'])
- $robots .= 'Disallow: /' . $row2['seo_link'] . '$' . PHP_EOL;
- else
- $robots .= 'Disallow: /a-' . $row2['article_id'] . '-' . self::seo($row2['title']) . '$' . PHP_EOL;
- }
-
- if (!$row2['block_direct_access'])
- {
- if ($row2['seo_link'])
- {
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . \S::seo($row2['seo_link']) . '(|/)$ index.php?article=' . $row2['article_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
- }
- else if ($row2['title'] != null)
- {
- $htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 'a-' . $row2['article_id'] . '-' . \S::seo($row2['title']) . '(|/)$ index.php?article=' . $row2['article_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
- }
- $htaccess_data .= PHP_EOL;
- }
- }
- }
-
- $results = $mdb->query('SELECT '
- . 'name, tag_id '
- . 'FROM '
- . 'pp_tags AS pt '
- . 'INNER JOIN '
- . 'pp_articles_tags AS pat ON pat.tag_id = pt.id '
- . 'GROUP BY '
- . 'tag_id')->fetchAll();
- if (is_array($results) and !empty($results)) foreach ($results as $row)
- {
- $htaccess_data .= PHP_EOL . 'RewriteCond %{QUERY_STRING} !=""';
- $htaccess_data .= PHP_EOL . 'RewriteRule tag/' . \S::seo( $row['name'] ) . '(|/) %{REQUEST_URI}? [R=301,L]';
- $htaccess_data .= PHP_EOL . 'RewriteRule ^tag/' . \S::seo( $row['name'] ) . '(|/)$ index.php?tag=' . $row['tag_id'] . ' [L]';
- }
-
- $results = $mdb->get('pp_settings', 'value', ['param' => 'htaccess']);
- if ($results)
- $htaccess_data .= PHP_EOL . $results;
-
- if (file_exists('../libraries/htaccess.ini'))
- $htaccess_data .= PHP_EOL . file_get_contents('../libraries/htaccess.ini');
-
- $results = $mdb->get('pp_settings', 'value', ['param' => 'robots']);
- if ($results)
- $robots .= PHP_EOL . $results;
-
- if (is_array($available_domains) and !empty($available_domains))
- {
- foreach ($available_domains as $domain)
- $site_map[$domain['domain']] .= '';
- }
- else
- $site_map[$url] .= '';
-
- $redirect = 'RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$'. PHP_EOL;
- if ( $settings['ssl'] )
- {
- $redirect .= 'RewriteCond %{HTTPS} off' . PHP_EOL
- . 'RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]';
- }
- else
- {
- $redirect .= 'RewriteCond %{HTTPS} on' . PHP_EOL
- . 'RewriteRule ^ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]';
- }
-
- $redirect .= 'RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$'. PHP_EOL;
- if ( $settings['link_version'] )
- {
- $redirect .= 'RewriteCond %{HTTP_HOST} !^www\. [NC]' . PHP_EOL
- . 'RewriteRule ^ %{REQUEST_SCHEME}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]' . PHP_EOL;
- }
- else
- {
- $redirect .= 'RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]' . PHP_EOL
- . 'RewriteRule ^ %{REQUEST_SCHEME}://%1%{REQUEST_URI} [L,R=301]' . PHP_EOL;
- }
-
- $redirect .= 'RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$'. PHP_EOL;
- if ( $settings['url_version'] )
- {
- $redirect .= 'RewriteCond %{REQUEST_URI} !^/admin(?:/.*)?$ [NC]' . PHP_EOL
- . 'RewriteRule ^(.+)/$ %{REQUEST_SCHEME}://%{HTTP_HOST}/$1 [L,R=301]' . PHP_EOL;
- }
- else
- {
- $redirect .= 'RewriteCond %{REQUEST_URI} !^/admin(/|$) [NC]' . PHP_EOL
- . 'RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL
- . 'RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL
- . 'RewriteCond %{REQUEST_URI} !/$' . PHP_EOL
- . 'RewriteRule ^(.+)$ %{REQUEST_SCHEME}://%{HTTP_HOST}/$1/ [L,R=301]' . PHP_EOL;
- }
-
- $htaccess_data = str_replace( '{REDIRECT}', $redirect, $htaccess_data );
-
- $additional_classes = file_get_contents('../libraries/additional-classes.ini');
- $additional_classes = explode(PHP_EOL, $additional_classes);
- $additional_classes = array_filter($additional_classes);
- if (is_array($additional_classes) and !empty($additional_classes))
- {
- foreach ($additional_classes as $class)
- {
- $classes .= 'RewriteCond %{REQUEST_URI} ^/' . trim($class) . '/(.*) [NC]' . PHP_EOL;
- $classes .= 'RewriteRule ^([^/]*)/([^/]*)(|/([^/]*))$ index.php?module=$1&action=$2&$4 [L]' . PHP_EOL;
- }
- }
- $htaccess_data = str_replace('{ADDITIONAL_CLASSES}', $classes, $htaccess_data);
-
- /* pozostałe linki */
- $htaccess_data .= PHP_EOL;
- $htaccess_data .= 'RewriteRule ^newsletter/signin$ index.php?module=newsletter&action=signin [L]' . PHP_EOL;
- $htaccess_data .= 'RewriteRule ^newsletter/confirm/hash=(.*)$ index.php?module=newsletter&action=confirm&hash=$1 [L]' . PHP_EOL;
- $htaccess_data .= 'RewriteRule ^newsletter/unsubscribe/hash=(.*)$ index.php?module=newsletter&action=unsubscribe&hash=$1 [L]' . PHP_EOL;
-
- /* pixieset */
- $results = $mdb->select('pp_articles', 'id', ['pixieset' => 1]);
- if (is_array($results) and count($results))
- {
- $pixieset = 'RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?' . $url_tmp . ' [NC]' . PHP_EOL;
- $pixieset .= 'RewriteCond %{REQUEST_URI} ^(';
- foreach ($results as $row)
- {
- $pixieset .= '/upload/article_images/article_' . $row . '/';
- if ($row != end($results))
- $pixieset .= '|';
- }
- $pixieset .= ') [NC]' . PHP_EOL . 'RewriteRule \.(jpg|jpeg|png|gif)$ - [NC,F,L]' . PHP_EOL;
-
- $htaccess_data = str_replace('{PIXIESET]', $pixieset, $htaccess_data);
- }
- else
- {
- $htaccess_data = str_replace('{PIXIESET]', '', $htaccess_data);
- }
-
- $fp = fopen($dir . '.htaccess', 'w');
- fwrite($fp, $htaccess_data);
- fclose($fp);
-
- $class = '\admin\factory\Sitemap';
- $action = 'sitemap';
-
- if (class_exists($class) and method_exists(new $class, $action))
- $site_map = call_user_func_array(array($class, $action), array($site_map, $available_domains, $domain_prefix, $www, $url));
-
- if (is_array($available_domains) and !empty($available_domains))
- {
- foreach ($available_domains as $domain)
- {
- $fp = fopen($dir . 'sitemap_' . \S::seo($domain['domain']) . '.xml', 'w');
- fwrite($fp, $site_map[$domain['domain']]);
- fclose($fp);
- }
- }
- else
- {
- $fp = fopen($dir . 'sitemap.xml', 'w');
- fwrite($fp, $site_map[$url]);
- fclose($fp);
- }
-
- $fp = fopen($dir . 'robots.txt', 'w');
- fwrite($fp, $robots);
- fclose($fp);
- }
-
- public static function seo( $val, $delete_rhombs = false )
- {
- $array_rep1 = array('*', '_', ' ', '+', '"', "'", '?', '-', ',', '!', '~', '<', '>', '@', '#', '$', '%', '^', '&', '*' . '(', ')' . '-', '=', '\\', '|', '[', ']', ':', '(', ')');
- $array_rep2 = array('-', '-', '-', '-', '', '', '', '-', '-', '', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '', '-', '-', '-', '-', '-', '-', '-', '-');
- $val = self::noPl($val);
- $val = str_replace($array_rep1, $array_rep2, $val);
- if ($delete_rhombs)
- $val = str_replace('/', '', $val);
-
- $val = strtolower($val);
- $val = preg_replace('/(-){2,}/', '-', $val);
- $val = ltrim($val, '-');
- $val = rtrim($val, '-');
- return $val;
- }
-
- public static function noPL($string)
- {
- $chars = array( // Decompositions for Latin-1 Supplement chr(195).chr(128)=> 'A', chr(195).chr(129) => 'A',
- chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A',
- chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A',
- chr(195) . chr(135) => 'C', chr(195) . chr(136) => 'E',
- chr(195) . chr(137) => 'E', chr(195) . chr(138) => 'E',
- chr(195) . chr(139) => 'E', chr(195) . chr(140) => 'I',
- chr(195) . chr(141) => 'I', chr(195) . chr(142) => 'I',
- chr(195) . chr(143) => 'I', chr(195) . chr(145) => 'N',
- chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O',
- chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O',
- chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U',
- chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U',
- chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y',
- chr(195) . chr(159) => 's', chr(195) . chr(160) => 'a',
- chr(195) . chr(161) => 'a', chr(195) . chr(162) => 'a',
- chr(195) . chr(163) => 'a', chr(195) . chr(164) => 'a',
- chr(195) . chr(165) => 'a', chr(195) . chr(167) => 'c',
- chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e',
- chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e',
- chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i',
- chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i',
- chr(195) . chr(177) => 'n', chr(195) . chr(178) => 'o',
- chr(195) . chr(179) => 'o', chr(195) . chr(180) => 'o',
- chr(195) . chr(181) => 'o', chr(195) . chr(182) => 'o',
- chr(195) . chr(182) => 'o', chr(195) . chr(185) => 'u',
- chr(195) . chr(186) => 'u', chr(195) . chr(187) => 'u',
- chr(195) . chr(188) => 'u', chr(195) . chr(189) => 'y',
- chr(195) . chr(191) => 'y',
- // Decompositions for Latin Extended-A
- chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a',
- chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a',
- chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a',
- chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c',
- chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c',
- chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c',
- chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c',
- chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd',
- chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd',
- chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e',
- chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e',
- chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e',
- chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e',
- chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e',
- chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g',
- chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g',
- chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g',
- chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g',
- chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h',
- chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h',
- chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i',
- chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i',
- chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i',
- chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i',
- chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i',
- chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij',
- chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j',
- chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k',
- chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L',
- chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L',
- chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L',
- chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L',
- chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L',
- chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N',
- chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N',
- chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N',
- chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N',
- chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N',
- chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o',
- chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o',
- chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o',
- chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe',
- chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r',
- chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r',
- chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r',
- chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's',
- chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's',
- chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's',
- chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's',
- chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't',
- chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't',
- chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't',
- chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u',
- chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u',
- chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u',
- chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u',
- chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u',
- chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u',
- chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w',
- chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y',
- chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z',
- chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z',
- chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z',
- chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's'
- );
-
- $string = strtr($string, $chars);
-
- $table = array(
- "А" => "a", "Б" => "b", "В" => "v", "Г" => "g", "Д" => "d",
- "Е" => "e", "Ё" => "yo", "Ж" => "zh", "З" => "z", "И" => "i",
- "Й" => "j", "К" => "k", "Л" => "l", "М" => "m", "Н" => "n",
- "О" => "o", "П" => "p", "Р" => "r", "С" => "s", "Т" => "t",
- "У" => "u", "Ф" => "f", "Х" => "kh", "Ц" => "ts", "Ч" => "ch",
- "Ш" => "sh", "Щ" => "sch", "Ъ" => "", "Ы" => "y", "Ь" => "",
- "Э" => "e", "Ю" => "yu", "Я" => "ya", "а" => "a", "б" => "b",
- "в" => "v", "г" => "g", "д" => "d", "е" => "e", "ё" => "yo",
- "ж" => "zh", "з" => "z", "и" => "i", "й" => "j", "к" => "k",
- "л" => "l", "м" => "m", "н" => "n", "о" => "o", "п" => "p",
- "р" => "r", "с" => "s", "т" => "t", "у" => "u", "ф" => "f",
- "х" => "kh", "ц" => "ts", "ч" => "ch", "ш" => "sh", "щ" => "sch",
- "ъ" => "", "ы" => "y", "ь" => "", "э" => "e", "ю" => "yu",
- "я" => "ya", " " => "-", "." => "", "," => "",
- ":" => "", ";" => "", "—" => "", "–" => "-"
- );
-
- $string = strtr($string, $table);
-
- return $string;
- }
-
- public static function delete_cache()
- {
- \S::delete_dir('../cache/');
- \S::delete_dir('../temp/');
- \S::delete_dir('temp/');
- }
-
- public static function delete_dir($dir)
- {
- if (is_file($dir))
- return @unlink($dir);
-
- else if (is_dir($dir))
- {
- $scan = glob(rtrim($dir, '/') . '/*');
-
- if (is_array($scan))
- foreach ($scan as $index => $path)
- self::delete_dir($path);
-
- if (is_dir($dir) && self::is_empty_dir($dir) and $dir != '../temp/')
- return @rmdir($dir);
- }
- }
-
- public static function is_empty_dir($dir)
- {
- return (($files = @scandir($dir)) && count($files) <= 2);
- }
- public static function email_check($email)
- {
- return filter_var($email, FILTER_VALIDATE_EMAIL);
- }
- public static function send_email( $email, $subject, $text, $replay = '', $file = '' )
- {
- global $settings;
-
- if ( file_exists('libraries/phpmailer/class.phpmailer.php') ) require_once 'libraries/phpmailer/class.phpmailer.php';
- if ( file_exists('libraries/phpmailer/class.smtp.php') ) require_once 'libraries/phpmailer/class.smtp.php';
- if ( file_exists('../libraries/phpmailer/class.phpmailer.php') ) require_once '../libraries/phpmailer/class.phpmailer.php';
- if ( file_exists('../libraries/phpmailer/class.smtp.php') ) require_once '../libraries/phpmailer/class.smtp.php';
- if ( $email and $subject )
- {
- $mail = new PHPMailer();
- $mail->IsSMTP();
- $mail->SMTPAuth = true;
- $mail->Host = $settings['email_host'];
- $mail->Port = $settings['email_port'];
- $mail->Username = $settings['email_login'];
- $mail->Password = $settings['email_password'];
- $mail->CharSet = "UTF-8";
- $mail->SMTPOptions = array(
- 'ssl' => array(
- 'verify_peer' => false,
- 'verify_peer_name' => false,
- 'allow_self_signed' => true
- )
- );
-
- if (self::email_check($replay))
- {
- $mail->AddReplyTo($replay, $replay);
- $mail->SetFrom($settings['contact_email'], $settings['contact_email']);
- }
- else
- {
- $mail->AddReplyTo($settings['contact_email'], $settings['firm_name']);
- $mail->SetFrom($settings['contact_email'], $settings['firm_name']);
- }
-
- $mail->AddAddress($email, '');
- $mail->Subject = $subject;
- $mail->Body = $text;
- if (is_array($file))
- {
- foreach ($file as $file_tmp)
- {
- if (file_exists($file_tmp))
- $mail->AddAttachment($file_tmp);
- }
- }
- else
- {
- if (file_exists($file))
- $mail->AddAttachment($file);
- }
- $mail->IsHTML(true);
- return $mail -> Send();
- }
- return true;
+ return call_user_func_array(['\Shared\Helpers\Helpers', $name], $args);
}
}
diff --git a/autoload/class.Tpl.php b/autoload/class.Tpl.php
index 1227612..fb8681b 100644
--- a/autoload/class.Tpl.php
+++ b/autoload/class.Tpl.php
@@ -1,73 +1,8 @@
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 '
Nie znaleziono pliku widoku:
' . $this -> dir . $file . '.php';
- }
-
- public function __set( $name, $value )
- {
- $this -> vars[ $name ] = $value;
- }
-
- public function __get( $name )
- {
- return $this -> vars[ $name ];
- }
}
diff --git a/autoload/front/factory/class.Languages.php b/autoload/front/factory/class.Languages.php
index 5ed6351..bf45e1f 100644
--- a/autoload/front/factory/class.Languages.php
+++ b/autoload/front/factory/class.Languages.php
@@ -1,58 +1,34 @@
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 );
}
}
diff --git a/autoload/front/factory/class.Settings.php b/autoload/front/factory/class.Settings.php
index 7a04550..ba49783 100644
--- a/autoload/front/factory/class.Settings.php
+++ b/autoload/front/factory/class.Settings.php
@@ -1,27 +1,24 @@
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();
}
}
diff --git a/cron.php b/cron.php
index dcbbcac..5bd8dae 100644
--- a/cron.php
+++ b/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' );
diff --git a/docs/FORM_EDIT_SYSTEM.md b/docs/FORM_EDIT_SYSTEM.md
new file mode 100644
index 0000000..61b09d1
--- /dev/null
+++ b/docs/FORM_EDIT_SYSTEM.md
@@ -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*
diff --git a/docs/MEMORY.md b/docs/MEMORY.md
new file mode 100644
index 0000000..442a779
--- /dev/null
+++ b/docs/MEMORY.md
@@ -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
diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md
new file mode 100644
index 0000000..143fcab
--- /dev/null
+++ b/docs/PROJECT_STRUCTURE.md
@@ -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}']`
diff --git a/docs/TESTING.md b/docs/TESTING.md
new file mode 100644
index 0000000..2b56703
--- /dev/null
+++ b/docs/TESTING.md
@@ -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/
/Test.php`, `tests/Unit/admin/Controllers/Test.php` lub `tests/Unit/api/Controllers/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`
diff --git a/docs/UPDATE_INSTRUCTIONS.md b/docs/UPDATE_INSTRUCTIONS.md
new file mode 100644
index 0000000..2ed4a2b
--- /dev/null
+++ b/docs/UPDATE_INSTRUCTIONS.md
@@ -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
\ No newline at end of file
diff --git a/index.php b/index.php
index 5c2debf..f92d73a 100644
--- a/index.php
+++ b/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' );
diff --git a/temp/0/0/s_cache_009a93317a248d0fbcd664b6fa5e79e8 b/temp/0/0/s_cache_009a93317a248d0fbcd664b6fa5e79e8
deleted file mode 100644
index 5856156..0000000
--- a/temp/0/0/s_cache_009a93317a248d0fbcd664b6fa5e79e8
+++ /dev/null
@@ -1,4 +0,0 @@
-]
-0E%_Io.Q,tE7*<$eX`5з-c"GJNTSKL>,@G_%+d
-zP4sJ~;LB
y_d_j=
\ No newline at end of file
diff --git a/temp/2/e/s_cache_2e377effb2e8dee00915d10bc41c4828 b/temp/2/e/s_cache_2e377effb2e8dee00915d10bc41c4828
deleted file mode 100644
index 129af51..0000000
Binary files a/temp/2/e/s_cache_2e377effb2e8dee00915d10bc41c4828 and /dev/null differ
diff --git a/temp/2/e/s_cache_2ee377301adb607837cf36dd0be1d55d b/temp/2/e/s_cache_2ee377301adb607837cf36dd0be1d55d
deleted file mode 100644
index 77100a5..0000000
--- a/temp/2/e/s_cache_2ee377301adb607837cf36dd0be1d55d
+++ /dev/null
@@ -1 +0,0 @@
-K2δ2δ247707166qk
\ No newline at end of file
diff --git a/temp/2moto.jpg b/temp/2moto.jpg
deleted file mode 100644
index bd81444..0000000
Binary files a/temp/2moto.jpg and /dev/null differ
diff --git a/temp/2moto.png b/temp/2moto.png
deleted file mode 100644
index b2e463f..0000000
Binary files a/temp/2moto.png and /dev/null differ
diff --git a/temp/3/a/s_cache_3af17bb640bbb102a77a6031d37e93bf b/temp/3/a/s_cache_3af17bb640bbb102a77a6031d37e93bf
deleted file mode 100644
index dd979b0..0000000
Binary files a/temp/3/a/s_cache_3af17bb640bbb102a77a6031d37e93bf and /dev/null differ
diff --git a/temp/4/b/s_cache_4b916ae533b4ded88ddc3edfe99de1be b/temp/4/b/s_cache_4b916ae533b4ded88ddc3edfe99de1be
deleted file mode 100644
index aa39ca4..0000000
--- a/temp/4/b/s_cache_4b916ae533b4ded88ddc3edfe99de1be
+++ /dev/null
@@ -1,4 +0,0 @@
-TˎJ# c;!BHtEȪtݦ3Fl
_K(;HQuqR}tgɂK2\z6]ϖHS
-
-/-ߚ0rJW
Gk+mD}sL%EjP pisC :pncpLDzF7zhlK+lΡ3
^Cz~pxY;'qm2]l;-Ѻ5 -^Hrwuķ`ނWIз?~~+~%gm(J`M%ȥ$z<#v㵧iꢮP\ͳ3,=2c?7z9r|7:Ui<{ٽK~-cN
L+:k"9|(8>#"_
棧i&e
5AV:B?(wCIv!>X;G:wt$Y1_~㴏ƴQc8Aci)X%E..3uAO`O$jf|L VH,'jd)%Q0[vrhjZ0 C4PCa0
-ƛ+w&*
\ No newline at end of file
diff --git a/temp/6/f/s_cache_6f95fe93af62cc2a989cde74cdcc06e8 b/temp/6/f/s_cache_6f95fe93af62cc2a989cde74cdcc06e8
deleted file mode 100644
index 3b9b786..0000000
--- a/temp/6/f/s_cache_6f95fe93af62cc2a989cde74cdcc06e8
+++ /dev/null
@@ -1 +0,0 @@
-K2δ2δ247707166qk
\ No newline at end of file
diff --git a/temp/61371df8cb979c85ab030f1a_img-linkedin-accountavatar-p-500.png b/temp/61371df8cb979c85ab030f1a_img-linkedin-accountavatar-p-500.png
deleted file mode 100644
index 612e1c6..0000000
Binary files a/temp/61371df8cb979c85ab030f1a_img-linkedin-accountavatar-p-500.png and /dev/null differ
diff --git a/temp/7/0/s_cache_70c889178cded5f9ed5b07860de4998a b/temp/7/0/s_cache_70c889178cded5f9ed5b07860de4998a
deleted file mode 100644
index a3a0785..0000000
Binary files a/temp/7/0/s_cache_70c889178cded5f9ed5b07860de4998a and /dev/null differ
diff --git a/temp/8/d/s_cache_8dbda3c46cb28c6c99ef9bfb465a670a b/temp/8/d/s_cache_8dbda3c46cb28c6c99ef9bfb465a670a
deleted file mode 100644
index 9a0504e..0000000
Binary files a/temp/8/d/s_cache_8dbda3c46cb28c6c99ef9bfb465a670a and /dev/null differ
diff --git a/temp/9/9/s_cache_9904a85ac0d682c47f2ed349b55e5573 b/temp/9/9/s_cache_9904a85ac0d682c47f2ed349b55e5573
deleted file mode 100644
index edf3bc8..0000000
Binary files a/temp/9/9/s_cache_9904a85ac0d682c47f2ed349b55e5573 and /dev/null differ
diff --git a/temp/b/e/s_cache_be865270a0fc2db1dd05b600a33e26b0 b/temp/b/e/s_cache_be865270a0fc2db1dd05b600a33e26b0
deleted file mode 100644
index bb65784..0000000
--- a/temp/b/e/s_cache_be865270a0fc2db1dd05b600a33e26b0
+++ /dev/null
@@ -1 +0,0 @@
-e
0w
Bd,%FUU{*Χ\*XklVK`J+z5ys?l@-냧%F>Mć[c`-r)̥Cȭ"؏Pa
\ No newline at end of file
diff --git a/temp/c/4/s_cache_c49fe1b5271400c65d78f2acc23561a7 b/temp/c/4/s_cache_c49fe1b5271400c65d78f2acc23561a7
deleted file mode 100644
index 29eb5d0..0000000
Binary files a/temp/c/4/s_cache_c49fe1b5271400c65d78f2acc23561a7 and /dev/null differ
diff --git a/temp/d/5/s_cache_d57ac45256849d9b13e2422d91580fb9 b/temp/d/5/s_cache_d57ac45256849d9b13e2422d91580fb9
deleted file mode 100644
index 590a406..0000000
Binary files a/temp/d/5/s_cache_d57ac45256849d9b13e2422d91580fb9 and /dev/null differ
diff --git a/temp/d/a/s_cache_da88fdad0e74f1220f1f34acd2b5ee9b b/temp/d/a/s_cache_da88fdad0e74f1220f1f34acd2b5ee9b
deleted file mode 100644
index 2eb01a1..0000000
Binary files a/temp/d/a/s_cache_da88fdad0e74f1220f1f34acd2b5ee9b and /dev/null differ
diff --git a/temp/d/f/s_cache_df8e16882b112ce2c0739934bd19b578 b/temp/d/f/s_cache_df8e16882b112ce2c0739934bd19b578
deleted file mode 100644
index 9a0504e..0000000
Binary files a/temp/d/f/s_cache_df8e16882b112ce2c0739934bd19b578 and /dev/null differ
diff --git a/temp/f/a/s_cache_faebd22e7d407d96f5a31f0ef85ea81c b/temp/f/a/s_cache_faebd22e7d407d96f5a31f0ef85ea81c
deleted file mode 100644
index d433d97..0000000
Binary files a/temp/f/a/s_cache_faebd22e7d407d96f5a31f0ef85ea81c and /dev/null differ
diff --git a/temp/kalkulator-kalorii-zdjecie.png b/temp/kalkulator-kalorii-zdjecie.png
deleted file mode 100644
index ba8b5fb..0000000
Binary files a/temp/kalkulator-kalorii-zdjecie.png and /dev/null differ
diff --git a/temp/slide-01.jpg b/temp/slide-01.jpg
deleted file mode 100644
index 047a8dc..0000000
Binary files a/temp/slide-01.jpg and /dev/null differ
diff --git a/temp/slide-02.jpg b/temp/slide-02.jpg
deleted file mode 100644
index 0d5a3f2..0000000
Binary files a/temp/slide-02.jpg and /dev/null differ
diff --git a/temp/special-strategy.png b/temp/special-strategy.png
deleted file mode 100644
index 4643274..0000000
Binary files a/temp/special-strategy.png and /dev/null differ
diff --git a/updates/versions.php b/updates/versions.php
index 3405105..2b32f1e 100644
--- a/updates/versions.php
+++ b/updates/versions.php
@@ -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();