diff --git a/admin/templates/shop-producer/edit.php b/admin/templates/shop-producer/edit.php deleted file mode 100644 index 6a03486..0000000 --- a/admin/templates/shop-producer/edit.php +++ /dev/null @@ -1,180 +0,0 @@ - - - -
- -
-
- 'Nazwa', - 'class' => 'require', - 'name' => 'name', - 'id' => 'name', - 'value' => $this -> producer['name'] - ] );?> - 'Aktywny', - 'name' => 'status', - 'checked' => $this -> producer['status'] == 1 ? true : false - ] );?> - 'Logo', - 'name' => 'img', - 'id' => 'img', - 'value' => $this -> producer['img'], - 'icon_content' => 'przeglądaj', - 'icon_js' => "window.open ( '/libraries/filemanager-9.14.2/dialog.php?type=1&popup=1&field_id=img&akey=" . $rfmAkeyJS . "', 'mywindow', 'location=1,status=1,scrollbars=1, width=1100,height=700');" - ] ); - ?> -
-
-
-
    - languages ) ): foreach ( $this -> languages as $lg ):?> - -
  • ';?>
  • - - -
-
- languages ) ): foreach ( $this -> languages as $lg ):?> - languages ) ) foreach ( $this -> languages as $lg_tmp ) - { - if ( $lg_tmp[ 'id' ] != $lg[ 'id' ] ) - $languages[ $lg_tmp[ 'id' ] ] = $lg_tmp[ 'name' ]; - } - ?> - -
- 'Opis', - 'name' => 'description[' . $lg['id'] . ']', - 'id' => 'description_' . $lg['id'], - 'value' => $this -> producer['languages'][$lg[ 'id']]['description'], - 'inline' => true - ] );?> - 'Dane producenta', - 'name' => 'data[' . $lg['id'] . ']', - 'id' => 'data_' . $lg['id'], - 'value' => $this -> producer['languages'][$lg[ 'id']]['data'], - 'inline' => true - ] );?> - -
- - -
-
-
-
-
-
-
    - languages ) ): foreach ( $this -> languages as $lg ):?> - -
  • ';?>
  • - - -
-
- languages ) ): foreach ( $this -> languages as $lg ):?> - languages ) ) foreach ( $this -> languages as $lg_tmp ) - { - if ( $lg_tmp[ 'id' ] != $lg[ 'id' ] ) - $languages[ $lg_tmp[ 'id' ] ] = $lg_tmp[ 'name' ]; - } - ?> - -
- 'Meta title', - 'name' => 'meta_title[' . $lg['id'] . ']', - 'id' => 'meta_title_' . $lg[ 'id' ], - 'value' => $this -> producer['languages'][$lg['id']]['meta_title'] - ] );?> -
- - -
-
-
-
-
-
- id = 'producer-edit'; -$grid -> gdb_opt = $gdb; -$grid -> include_plugins = true; -$grid -> title = 'Edycja producenta'; -$grid -> fields = [ - [ - 'db' => 'id', - 'type' => 'hidden', - 'value' => $this -> producer['id'] - ] - ]; -$grid -> actions = [ - 'save' => [ 'url' => '/admin/shop_producer/save/', 'back_url' => '/admin/shop_producer/list/' ], - 'cancel' => [ 'url' => '/admin/shop_producer/list/' ] - ]; -$grid -> external_code = $out; -$grid -> persist_edit = true; -$grid -> id_param = 'id'; -echo $grid -> draw(); -?> - diff --git a/admin/templates/shop-producer/list.php b/admin/templates/shop-producer/list.php deleted file mode 100644 index 11dd1b1..0000000 --- a/admin/templates/shop-producer/list.php +++ /dev/null @@ -1,73 +0,0 @@ - gdb_opt = $gdb; -$grid -> sql = 'SELECT *' - . 'FROM ( ' - . 'SELECT ' - . 'id, name, status, img ' - . 'FROM ' - . 'pp_shop_producer ' - . ') AS q1 ' - . 'WHERE ' - . '1=1 [where] ' - . 'ORDER BY ' - . '[order_p1] [order_p2]'; -$grid -> sql_count = 'SELECT ' - . 'COUNT(0) FROM ( ' - . 'SELECT ' - . 'id, name, status, img ' - . 'FROM ' - . 'pp_shop_producer ' - . ') AS q1 ' - . 'WHERE ' - . '1=1 [where] '; -$grid -> debug = true; -$grid -> order = [ 'column' => 'name', 'type' => 'ASC' ]; -$grid -> search = [ - [ 'name' => 'Nazwa', 'db' => 'name', 'type' => 'text' ], - [ 'name' => 'Aktywny', 'db' => 'status', 'type' => 'select', 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ] ] - ]; -$grid -> columns_view = [ - [ - 'name' => 'Lp.', - 'th' => [ 'class' => 'g-lp' ], - 'td' => [ 'class' => 'g-center' ], - 'autoincrement' => true - ], [ - 'name' => 'Nazwa', - 'db' => 'name', - 'sort' => true, - 'php' => 'echo "[name]";' - ], [ - 'name' => 'Logo', - 'db' => 'img' - ], [ - 'name' => 'Aktywny', - 'db' => 'status', - 'replace' => [ 'array' => [ 0 => 'nie', 1 => 'tak' ] ], - 'td' => [ 'class' => 'g-center' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ], - 'sort' => true - ], [ - 'name' => 'Edytuj', - 'action' => [ 'type' => 'edit', 'url' => '/admin/shop_producer/edit/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ], [ - 'name' => 'Usuń', - 'action' => [ 'type' => 'delete', 'url' => '/admin/shop_producer/delete/id=[id]' ], - 'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ], - 'td' => [ 'class' => 'g-center' ] - ] - ]; -$grid -> buttons = [ - [ - 'label' => 'Dodaj producenta', - 'url' => '/admin/shop_producer/edit/', - 'icon' => 'fa-plus-circle', - 'class' => 'btn-success' - ] - ]; -echo $grid -> draw(); \ No newline at end of file diff --git a/admin/templates/shop-producer/producer-edit.php b/admin/templates/shop-producer/producer-edit.php new file mode 100644 index 0000000..5fe1611 --- /dev/null +++ b/admin/templates/shop-producer/producer-edit.php @@ -0,0 +1 @@ + $this->form]); ?> diff --git a/admin/templates/shop-producer/producers-list.php b/admin/templates/shop-producer/producers-list.php new file mode 100644 index 0000000..0f89c5b --- /dev/null +++ b/admin/templates/shop-producer/producers-list.php @@ -0,0 +1 @@ + $this->viewModel]); ?> diff --git a/autoload/Domain/Producer/ProducerRepository.php b/autoload/Domain/Producer/ProducerRepository.php new file mode 100644 index 0000000..1e3cf09 --- /dev/null +++ b/autoload/Domain/Producer/ProducerRepository.php @@ -0,0 +1,332 @@ +db = $db; + } + + /** + * Lista producentów dla panelu admin (paginowana, filtrowalna, sortowalna). + * + * @return array{items: array>, total: int} + */ + public function listForAdmin( + array $filters, + string $sortColumn = 'name', + string $sortDir = 'ASC', + int $page = 1, + int $perPage = 15 + ): array { + $allowedSortColumns = [ + 'id' => 'p.id', + 'name' => 'p.name', + 'status' => 'p.status', + ]; + + $sortSql = $allowedSortColumns[$sortColumn] ?? 'p.name'; + $sortDir = strtoupper(trim($sortDir)) === 'DESC' ? 'DESC' : 'ASC'; + $page = max(1, $page); + $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); + $offset = ($page - 1) * $perPage; + + $where = ['1 = 1']; + $params = []; + + $name = trim((string)($filters['name'] ?? '')); + if ($name !== '') { + if (strlen($name) > 255) { + $name = substr($name, 0, 255); + } + $where[] = 'p.name LIKE :name'; + $params[':name'] = '%' . $name . '%'; + } + + $status = trim((string)($filters['status'] ?? '')); + if ($status === '0' || $status === '1') { + $where[] = 'p.status = :status'; + $params[':status'] = (int)$status; + } + + $whereSql = implode(' AND ', $where); + + $sqlCount = " + SELECT COUNT(0) + FROM pp_shop_producer AS p + WHERE {$whereSql} + "; + + $stmtCount = $this->db->query($sqlCount, $params); + $countRows = $stmtCount ? $stmtCount->fetchAll() : []; + $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; + + $sql = " + SELECT + p.id, + p.name, + p.status, + p.img + FROM pp_shop_producer AS p + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir}, p.id DESC + LIMIT {$perPage} OFFSET {$offset} + "; + + $stmt = $this->db->query($sql, $params); + $items = $stmt ? $stmt->fetchAll() : []; + + if (!is_array($items)) { + $items = []; + } + + foreach ($items as &$item) { + $item['id'] = (int)($item['id'] ?? 0); + $item['status'] = $this->toSwitchValue($item['status'] ?? 0); + } + unset($item); + + return [ + 'items' => $items, + 'total' => $total, + ]; + } + + /** + * Pobiera producenta z tłumaczeniami. + */ + public function find(int $id): array + { + if ($id <= 0) { + return $this->defaultProducer(); + } + + $producer = $this->db->get('pp_shop_producer', '*', ['id' => $id]); + if (!is_array($producer)) { + return $this->defaultProducer(); + } + + $producer['id'] = (int)($producer['id'] ?? 0); + $producer['status'] = $this->toSwitchValue($producer['status'] ?? 0); + + // Tłumaczenia + $rows = $this->db->select('pp_shop_producer_lang', '*', ['producer_id' => $id]); + $languages = []; + if (is_array($rows)) { + foreach ($rows as $row) { + $langId = $row['lang_id'] ?? ''; + $languages[$langId] = [ + 'description' => $row['description'] ?? null, + 'data' => $row['data'] ?? null, + 'meta_title' => $row['meta_title'] ?? null, + ]; + } + } + $producer['languages'] = $languages; + + return $producer; + } + + /** + * Zapisuje producenta (insert / update) wraz z tłumaczeniami. + * + * @return int|null ID producenta lub null przy błędzie + */ + public function save( + int $id, + string $name, + int $status, + ?string $img, + array $description, + array $data, + array $metaTitle, + array $langs + ): ?int { + $row = [ + 'name' => trim($name), + 'status' => $status === 1 ? 1 : 0, + 'img' => $img, + ]; + + if ($id <= 0) { + $this->db->insert('pp_shop_producer', $row); + $id = (int)$this->db->id(); + if ($id <= 0) { + return null; + } + } else { + $this->db->update('pp_shop_producer', $row, ['id' => $id]); + } + + // Tłumaczenia + foreach ($langs as $lg) { + $langId = $lg['id'] ?? ''; + $translationData = [ + 'description' => $description[$langId] ?? null, + 'data' => $data[$langId] ?? null, + 'meta_title' => $metaTitle[$langId] ?? null, + ]; + + $translationId = $this->db->get( + 'pp_shop_producer_lang', + 'id', + ['AND' => ['producer_id' => $id, 'lang_id' => $langId]] + ); + + if ($translationId) { + $this->db->update('pp_shop_producer_lang', $translationData, ['id' => $translationId]); + } else { + $this->db->insert('pp_shop_producer_lang', array_merge($translationData, [ + 'producer_id' => $id, + 'lang_id' => $langId, + ])); + } + } + + return $id; + } + + /** + * Usuwa producenta (kaskadowo z pp_shop_producer_lang przez FK). + */ + public function delete(int $id): bool + { + if ($id <= 0) { + return false; + } + + $result = (bool)$this->db->delete('pp_shop_producer', ['id' => $id]); + + return $result; + } + + /** + * Wszystkie producenty (do select w edycji produktu). + * + * @return array + */ + public function allProducers(): array + { + $rows = $this->db->select('pp_shop_producer', ['id', 'name'], ['ORDER' => ['name' => 'ASC']]); + if (!is_array($rows)) { + return []; + } + + $producers = []; + foreach ($rows as $row) { + $producers[] = [ + 'id' => (int)($row['id'] ?? 0), + 'name' => (string)($row['name'] ?? ''), + ]; + } + + return $producers; + } + + /** + * Pobiera producenta z tłumaczeniami dla danego języka (frontend). + */ + public function findForFrontend(int $id, string $langId): ?array + { + if ($id <= 0) { + return null; + } + + $producer = $this->db->get('pp_shop_producer', '*', ['id' => $id]); + if (!is_array($producer)) { + return null; + } + + $producer['id'] = (int)($producer['id'] ?? 0); + + $langRow = $this->db->get('pp_shop_producer_lang', '*', [ + 'AND' => ['producer_id' => $id, 'lang_id' => $langId], + ]); + + $producer['languages'] = []; + if (is_array($langRow)) { + $producer['languages'][$langId] = [ + 'description' => $langRow['description'] ?? null, + 'data' => $langRow['data'] ?? null, + 'meta_title' => $langRow['meta_title'] ?? null, + ]; + } + + return $producer; + } + + /** + * Produkty producenta (paginowane) — frontend. + * + * @return array{products: array, ls: int} + */ + public function producerProducts(int $producerId, int $perPage = 12, int $page = 1): array + { + $count = $this->db->count('pp_shop_products', [ + 'AND' => ['producer_id' => $producerId, 'status' => 1], + ]); + + $totalPages = max(1, (int)ceil($count / $perPage)); + $page = max(1, min($page, $totalPages)); + $offset = $perPage * ($page - 1); + + $products = $this->db->select('pp_shop_products', 'id', [ + 'AND' => ['producer_id' => $producerId, 'status' => 1], + 'LIMIT' => [$offset, $perPage], + ]); + + return [ + 'products' => is_array($products) ? $products : [], + 'ls' => $totalPages, + ]; + } + + /** + * Aktywni producenci (frontend lista). + * + * @return array + */ + public function allActiveIds(): array + { + $rows = $this->db->select('pp_shop_producer', 'id', [ + 'status' => 1, + 'ORDER' => ['name' => 'ASC'], + ]); + + return is_array($rows) ? array_map('intval', $rows) : []; + } + + private function defaultProducer(): array + { + return [ + 'id' => 0, + 'name' => '', + 'status' => 1, + 'img' => null, + 'languages' => [], + ]; + } + + private function toSwitchValue($value): int + { + if (is_bool($value)) { + return $value ? 1 : 0; + } + + if (is_numeric($value)) { + return ((int)$value) === 1 ? 1 : 0; + } + + if (is_string($value)) { + $normalized = strtolower(trim($value)); + return in_array($normalized, ['1', 'on', 'true', 'yes'], true) ? 1 : 0; + } + + return 0; + } +} diff --git a/autoload/admin/Controllers/ShopProducerController.php b/autoload/admin/Controllers/ShopProducerController.php new file mode 100644 index 0000000..58e9f8d --- /dev/null +++ b/autoload/admin/Controllers/ShopProducerController.php @@ -0,0 +1,379 @@ +repository = $repository; + $this->languagesRepository = $languagesRepository; + $this->formHandler = new FormRequestHandler(); + } + + public function list(): string + { + $sortableColumns = ['id', 'name', 'status']; + $filterDefinitions = [ + [ + 'key' => 'name', + 'label' => 'Nazwa', + 'type' => 'text', + ], + [ + 'key' => 'status', + 'label' => 'Aktywny', + 'type' => 'select', + 'options' => [ + '' => '- aktywny -', + '1' => 'tak', + '0' => 'nie', + ], + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'name' + ); + + $sortDir = $listRequest['sortDir']; + if (trim((string)\S::get('sort')) === '') { + $sortDir = 'ASC'; + } + + $result = $this->repository->listForAdmin( + $listRequest['filters'], + $listRequest['sortColumn'], + $sortDir, + $listRequest['page'], + $listRequest['perPage'] + ); + + $rows = []; + $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; + foreach ($result['items'] as $item) { + $id = (int)($item['id'] ?? 0); + $name = trim((string)($item['name'] ?? '')); + $status = (int)($item['status'] ?? 0); + $img = trim((string)($item['img'] ?? '')); + + $imgHtml = ''; + if ($img !== '') { + $imgHtml = ''; + } + + $rows[] = [ + 'lp' => $lp++ . '.', + 'name' => '' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '', + 'img' => $imgHtml, + 'status' => $status === 1 ? 'tak' : 'nie', + '_actions' => [ + [ + 'label' => 'Edytuj', + 'url' => '/admin/shop_producer/edit/id=' . $id, + 'class' => 'btn btn-xs btn-primary', + ], + [ + 'label' => 'Usun', + 'url' => '/admin/shop_producer/delete/id=' . $id, + 'class' => 'btn btn-xs btn-danger', + 'confirm' => 'Na pewno chcesz usunac wybranego producenta?', + 'confirm_ok' => 'Usun', + 'confirm_cancel' => 'Anuluj', + ], + ], + ]; + } + + $total = (int)$result['total']; + $totalPages = max(1, (int)ceil($total / $listRequest['perPage'])); + + $viewModel = new PaginatedTableViewModel( + [ + ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], + ['key' => 'name', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true], + ['key' => 'img', 'label' => 'Logo', 'sortable' => false, 'raw' => true], + ['key' => 'status', 'sort_key' => 'status', 'label' => 'Aktywny', 'class' => 'text-center', 'sortable' => true, 'raw' => true], + ], + $rows, + $listRequest['viewFilters'], + [ + 'column' => $listRequest['sortColumn'], + 'dir' => $sortDir, + ], + [ + 'page' => $listRequest['page'], + 'per_page' => $listRequest['perPage'], + 'total' => $total, + 'total_pages' => $totalPages, + ], + array_merge($listRequest['queryFilters'], [ + 'sort' => $listRequest['sortColumn'], + 'dir' => $sortDir, + 'per_page' => $listRequest['perPage'], + ]), + $listRequest['perPageOptions'], + $sortableColumns, + '/admin/shop_producer/list/', + 'Brak danych w tabeli.', + '/admin/shop_producer/edit/', + 'Dodaj producenta' + ); + + return \Tpl::view('shop-producer/producers-list', [ + 'viewModel' => $viewModel, + ]); + } + + public function view_list(): string + { + return $this->list(); + } + + public function edit(): string + { + $producer = $this->repository->find((int)\S::get('id')); + $languages = $this->languagesRepository->languagesList(); + $validationErrors = $_SESSION['form_errors'][$this->formId()] ?? null; + if ($validationErrors) { + unset($_SESSION['form_errors'][$this->formId()]); + } + + return \Tpl::view('shop-producer/producer-edit', [ + 'form' => $this->buildFormViewModel($producer, $languages, $validationErrors), + ]); + } + + public function producer_edit(): string + { + return $this->edit(); + } + + public function save(): void + { + // Legacy JSON (gridEdit) + $legacyValues = \S::get('values'); + if ($legacyValues) { + $values = json_decode((string)$legacyValues, true); + $response = [ + 'status' => 'error', + 'msg' => 'Podczas zapisywania producenta wystapil blad. Prosze sprobowac ponownie.', + ]; + + if (is_array($values)) { + $langs = $this->languagesRepository->languagesList(true); + + $id = $this->repository->save( + (int)($values['id'] ?? 0), + (string)($values['name'] ?? ''), + $this->toSwitchValue($values['status'] ?? 0), + $values['img'] ?? null, + $values['description'] ?? [], + $values['data'] ?? [], + $values['meta_title'] ?? [], + $langs + ); + + if (!empty($id)) { + \S::htacces(); + \S::delete_dir('../temp/'); + $response = [ + 'status' => 'ok', + 'msg' => 'Producent zostal zapisany.', + 'id' => (int)$id, + ]; + } + } + + echo json_encode($response); + exit; + } + + // Nowy flow (form-edit) + $producer = $this->repository->find((int)\S::get('id')); + $languages = $this->languagesRepository->languagesList(); + $form = $this->buildFormViewModel($producer, $languages); + + $result = $this->formHandler->handleSubmit($form, $_POST); + if (!$result['success']) { + $_SESSION['form_errors'][$this->formId()] = $result['errors']; + echo json_encode(['success' => false, 'errors' => $result['errors']]); + exit; + } + + $data = $result['data']; + $langs = $this->languagesRepository->languagesList(true); + + $translations = $data['translations'] ?? []; + $description = []; + $metaData = []; + $metaTitle = []; + foreach ($translations as $langId => $fields) { + $description[$langId] = $fields['description'] ?? null; + $metaData[$langId] = $fields['data'] ?? null; + $metaTitle[$langId] = $fields['meta_title'] ?? null; + } + + $savedId = $this->repository->save( + (int)($data['id'] ?? 0), + (string)($data['name'] ?? ''), + $this->toSwitchValue($data['status'] ?? 0), + $data['img'] ?? null, + $description, + $metaData, + $metaTitle, + $langs + ); + + if ($savedId) { + \S::htacces(); + \S::delete_dir('../temp/'); + echo json_encode([ + 'success' => true, + 'id' => $savedId, + 'message' => 'Producent zostal zapisany.', + ]); + exit; + } + + echo json_encode([ + 'success' => false, + 'errors' => ['general' => 'Podczas zapisywania producenta wystapil blad.'], + ]); + exit; + } + + public function producer_save(): void + { + $this->save(); + } + + public function delete(): void + { + if ($this->repository->delete((int)\S::get('id'))) { + \S::htacces(); + \S::delete_dir('../temp/'); + \S::alert('Producent zostal usuniety.'); + } + + header('Location: /admin/shop_producer/list/'); + exit; + } + + public function producer_delete(): void + { + $this->delete(); + } + + private function buildFormViewModel(array $producer, array $languages, ?array $errors = null): FormEditViewModel + { + $id = (int)($producer['id'] ?? 0); + $isNew = $id <= 0; + + $data = [ + 'id' => $id, + 'name' => (string)($producer['name'] ?? ''), + 'status' => (int)($producer['status'] ?? 1), + 'img' => $producer['img'] ?? null, + 'languages' => is_array($producer['languages'] ?? null) ? $producer['languages'] : [], + ]; + + $fields = [ + FormField::hidden('id', $id), + FormField::text('name', [ + 'label' => 'Nazwa', + 'required' => true, + 'tab' => 'general', + ]), + FormField::switch('status', [ + 'label' => 'Aktywny', + 'tab' => 'general', + 'value' => true, + ]), + FormField::image('img', [ + 'label' => 'Logo', + 'tab' => 'general', + ]), + FormField::langSection('translations', 'description', [ + FormField::editor('description', [ + 'label' => 'Opis', + 'height' => 250, + ]), + FormField::editor('data', [ + 'label' => 'Dane producenta', + 'height' => 250, + ]), + ]), + FormField::langSection('translations', 'seo', [ + FormField::text('meta_title', [ + 'label' => 'Meta title', + ]), + ]), + ]; + + $tabs = [ + new FormTab('general', 'Ogolne', 'fa-file'), + new FormTab('description', 'Opis', 'fa-file'), + new FormTab('seo', 'SEO', 'fa-globe'), + ]; + + $actionUrl = '/admin/shop_producer/save/' . ($isNew ? '' : ('id=' . $id)); + $actions = [ + FormAction::save($actionUrl, '/admin/shop_producer/list/'), + FormAction::cancel('/admin/shop_producer/list/'), + ]; + + return new FormEditViewModel( + $this->formId(), + 'Edycja producenta', + $data, + $fields, + $tabs, + $actions, + 'POST', + $actionUrl, + '/admin/shop_producer/list/', + true, + [], + $languages, + $errors + ); + } + + private function formId(): string + { + return 'shop-producer-edit'; + } + + private function toSwitchValue($value): int + { + if (is_bool($value)) { + return $value ? 1 : 0; + } + + if (is_numeric($value)) { + return ((int)$value) === 1 ? 1 : 0; + } + + if (is_string($value)) { + $normalized = strtolower(trim($value)); + return in_array($normalized, ['1', 'on', 'true', 'yes'], true) ? 1 : 0; + } + + return 0; + } +} diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php index 72e2da8..9dadd20 100644 --- a/autoload/admin/class.Site.php +++ b/autoload/admin/class.Site.php @@ -369,6 +369,14 @@ class Site new \Domain\ProductSet\ProductSetRepository( $mdb ) ); }, + 'ShopProducer' => function() { + global $mdb; + + return new \admin\Controllers\ShopProducerController( + new \Domain\Producer\ProducerRepository( $mdb ), + new \Domain\Languages\LanguagesRepository( $mdb ) + ); + }, ]; return self::$newControllers; diff --git a/autoload/admin/controls/class.ShopProducer.php b/autoload/admin/controls/class.ShopProducer.php deleted file mode 100644 index 815e695..0000000 --- a/autoload/admin/controls/class.ShopProducer.php +++ /dev/null @@ -1,37 +0,0 @@ - 'error', 'msg' => 'Podczas zapisywania producenta wystąpił błąd. Proszę spróbować ponownie.' ]; - $values = json_decode( \S::get( 'values' ), true ); - - if ( $producer_id = \admin\factory\ShopProducer::save( $values['id'], $values['name'], $values['status'] == 'on' ? 1 : 0, $values['img'], $values['description'], $values['data'], $values['meta_title'] ) ) - $response = [ 'status' => 'ok', 'msg' => 'Producent został zapisany.', 'id' => $producer_id ]; - - echo json_encode( $response ); - exit; - } - - static public function edit() - { - return \Tpl::view( 'shop-producer/edit', [ - 'producer' => \S::get( 'id' ) ? new \shop\Producer( \S::get( 'id' ) ) : null, - 'languages' => ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->languagesList() - ] ); - } - - static public function list() - { - return \Tpl::view( 'shop-producer/list' ); - } -} diff --git a/autoload/admin/controls/class.ShopProduct.php b/autoload/admin/controls/class.ShopProduct.php index e65be73..f7dba45 100644 --- a/autoload/admin/controls/class.ShopProduct.php +++ b/autoload/admin/controls/class.ShopProduct.php @@ -248,7 +248,7 @@ class ShopProduct 'products' => \admin\factory\ShopProduct::products_list(), 'dlang' => \front\factory\Languages::default_language(), 'sets' => \shop\ProductSet::sets_list(), - 'producers' => \admin\factory\ShopProducer::all(), + 'producers' => ( new \Domain\Producer\ProducerRepository( $mdb ) )->allProducers(), 'units' => ( new \Domain\Dictionaries\DictionariesRepository( $mdb ) ) -> allUnits(), 'user' => $user ] ); @@ -262,12 +262,6 @@ class ShopProduct return is_array( $rows ) ? $rows : []; } - if ( class_exists( '\admin\factory\Layouts' ) ) - { - $rows = \admin\factory\Layouts::layouts_list(); - return is_array( $rows ) ? $rows : []; - } - return []; } diff --git a/autoload/admin/factory/class.Languages.php b/autoload/admin/factory/class.Languages.php deleted file mode 100644 index 3e46b4f..0000000 --- a/autoload/admin/factory/class.Languages.php +++ /dev/null @@ -1,51 +0,0 @@ -deleteTranslation((int)$translation_id); - } - - public static function translation_save($translation_id, $text, $languages) - { - return self::repository()->saveTranslation((int)$translation_id, (string)$text, is_array($languages) ? $languages : []); - } - - public static function translation_details($translation_id) - { - return self::repository()->translationDetails((int)$translation_id); - } - - public static function language_delete($language_id) - { - return self::repository()->deleteLanguage((string)$language_id); - } - - public static function max_order() - { - return self::repository()->maxOrder(); - } - - public static function language_save($language_id, $name, $status, $start, $o) - { - return self::repository()->saveLanguage((string)$language_id, (string)$name, $status, $start, (int)$o); - } - - public static function language_details($language_id) - { - return self::repository()->languageDetails((string)$language_id); - } - - public static function languages_list($only_active = false) - { - return self::repository()->languagesList((bool)$only_active); - } -} diff --git a/autoload/admin/factory/class.Layouts.php b/autoload/admin/factory/class.Layouts.php deleted file mode 100644 index 51e52bf..0000000 --- a/autoload/admin/factory/class.Layouts.php +++ /dev/null @@ -1,65 +0,0 @@ -delete((int)$layout_id); - } - - public static function layout_details($layout_id) - { - return self::repository()->find((int)$layout_id); - } - - public static function layout_save( - $layout_id, - $name, - $status, - $pages, - $html, - $css, - $js, - $m_html, - $m_css, - $m_js, - $categories, - $categories_default - ) { - return self::repository()->save([ - 'id' => $layout_id, - 'name' => $name, - 'status' => $status, - 'pages' => $pages, - 'html' => $html, - 'css' => $css, - 'js' => $js, - 'm_html' => $m_html, - 'm_css' => $m_css, - 'm_js' => $m_js, - 'categories' => $categories, - 'categories_default' => $categories_default, - ]); - } - - public static function menus_list() - { - global $mdb; - return (new PagesRepository($mdb))->menusWithPages(); - } - - public static function layouts_list() - { - return self::repository()->listAll(); - } - - private static function repository(): LayoutsRepository - { - global $mdb; - return new LayoutsRepository($mdb); - } -} diff --git a/autoload/admin/factory/class.Newsletter.php b/autoload/admin/factory/class.Newsletter.php deleted file mode 100644 index ddd14f7..0000000 --- a/autoload/admin/factory/class.Newsletter.php +++ /dev/null @@ -1,48 +0,0 @@ - isAdminTemplate( (int)$template_id ); - } - - public static function newsletter_template_delete( $template_id ) - { - return self::repository() -> deleteTemplate( (int)$template_id ); - } - - public static function send( $dates, $template ) - { - return self::repository() -> queueSend( (string)$dates, (int)$template ); - } - - public static function email_template_detalis( $id_template ) - { - return self::repository() -> templateDetails( (int)$id_template ); - } - - public static function template_save( $id, $name, $text ) - { - return self::repository() -> saveTemplate( (int)$id, (string)$name, (string)$text ); - } - - public static function templates_list() - { - return self::repository() -> listTemplatesSimple( false ); - } - - private static function repository(): NewsletterRepository - { - global $mdb; - - return new NewsletterRepository( - $mdb, - new SettingsRepository($mdb) - ); - } -} \ No newline at end of file diff --git a/autoload/admin/factory/class.Scontainers.php b/autoload/admin/factory/class.Scontainers.php deleted file mode 100644 index 3f1d819..0000000 --- a/autoload/admin/factory/class.Scontainers.php +++ /dev/null @@ -1,32 +0,0 @@ -delete((int)$container_id); - } - - public static function container_save($container_id, $title, $text, $status, $show_title) - { - return self::repository()->save([ - 'id' => (int)$container_id, - 'title' => is_array($title) ? $title : [], - 'text' => is_array($text) ? $text : [], - 'status' => $status, - 'show_title' => $show_title, - ]); - } - - public static function container_details($container_id) - { - return self::repository()->find((int)$container_id); - } -} \ No newline at end of file diff --git a/autoload/admin/factory/class.ShopProducer.php b/autoload/admin/factory/class.ShopProducer.php deleted file mode 100644 index 6c3aae5..0000000 --- a/autoload/admin/factory/class.ShopProducer.php +++ /dev/null @@ -1,89 +0,0 @@ - select( 'pp_shop_producer', '*', [ 'ORDER' => [ 'name' => 'ASC' ] ] ); - } - - static public function delete( int $producer_id ) - { - global $mdb; - return $mdb -> delete( 'pp_shop_producer', [ 'id' => $producer_id ] ); - } - - static public function save( $producer_id, $name, int $status, $img, $description, $data, $meta_title ) - { - global $mdb; - - if ( !$producer_id ) - { - $mdb -> insert( 'pp_shop_producer', [ - 'name' => $name, - 'status' => $status, - 'img' => $img - ] ); - - $id = $mdb -> id(); - - $langs = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->languagesList( true ); - foreach ( $langs as $lg ) - { - $mdb -> insert( 'pp_shop_producer_lang', [ - 'producer_id' => $id, - 'lang_id' => $lg['id'], - 'description' => $description[ $lg['id'] ] ?? null, - 'data' => $data[ $lg['id'] ] ?? null, - 'meta_title' => $meta_title[ $lg['id'] ] ?? null - ] ); - } - - \S::htacces(); - \S::delete_dir( '../temp/' ); - - return $id; - } - else - { - $mdb -> update( 'pp_shop_producer', [ - 'name' => $name, - 'status' => $status, - 'img' => $img - ], [ - 'id' => (int) $producer_id - ] ); - - $langs = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->languagesList( true ); - foreach ( $langs as $lg ) - { - if ( $translation_id = $mdb -> get( 'pp_shop_producer_lang', 'id', [ 'AND' => [ 'producer_id' => $producer_id, 'lang_id' => $lg['id'] ] ] ) ) - { - $mdb -> update( 'pp_shop_producer_lang', [ - 'description' => $description[ $lg['id'] ] ?? null, - 'meta_title' => $meta_title[ $lg['id'] ] ?? null, - 'data' => $data[ $lg['id'] ] ?? null - ], [ - 'id' => $translation_id - ] ); - } - else - { - $mdb -> insert( 'pp_shop_producer_lang', [ - 'producer_id' => $producer_id, - 'lang_id' => $lg['id'], - 'description' => $description[ $lg['id'] ] ?? null, - 'data' => $data[ $lg['id'] ] ?? null, - 'meta_title' => $meta_title[ $lg['id'] ] ?? null - ] ); - } - } - - \S::htacces(); - \S::delete_dir( '../temp/' ); - return $producer_id; - } - return false; - } -} diff --git a/autoload/admin/factory/class.ShopProduct.php b/autoload/admin/factory/class.ShopProduct.php index 196d655..5fd0a9e 100644 --- a/autoload/admin/factory/class.ShopProduct.php +++ b/autoload/admin/factory/class.ShopProduct.php @@ -397,7 +397,7 @@ class ShopProduct $p_gshipping = $itemNode -> appendChild( $doc -> createElement( 'g:shipping' ) ); $p_gcountry = $p_gshipping -> appendChild( $doc -> createElement( 'g:country', 'PL' ) ); $p_gservice = $p_gshipping -> appendChild( $doc -> createElement( 'g:service', '1 dzień roboczy' ) ); - $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', \admin\factory\ShopTransport::lowest_transport_price( (int) $product -> wp ) . ' PLN' ) ); + $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', ( new \Domain\Transport\TransportRepository( $mdb ) )->lowestTransportPrice( (int) $product -> wp ) . ' PLN' ) ); } } } @@ -484,7 +484,7 @@ class ShopProduct $p_gshipping = $itemNode -> appendChild( $doc -> createElement( 'g:shipping' ) ); $p_gcountry = $p_gshipping -> appendChild( $doc -> createElement( 'g:country', 'PL' ) ); $p_gservice = $p_gshipping -> appendChild( $doc -> createElement( 'g:service', '1 dzień roboczy' ) ); - $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', \admin\factory\ShopTransport::lowest_transport_price( (int) $product -> wp ) . ' PLN' ) ); + $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', ( new \Domain\Transport\TransportRepository( $mdb ) )->lowestTransportPrice( (int) $product -> wp ) . ' PLN' ) ); } } file_put_contents('../google-feed.xml', $doc -> saveXML()); diff --git a/autoload/admin/factory/class.ShopTransport.php b/autoload/admin/factory/class.ShopTransport.php deleted file mode 100644 index 5c00ae2..0000000 --- a/autoload/admin/factory/class.ShopTransport.php +++ /dev/null @@ -1,10 +0,0 @@ -lowestTransportPrice($wp); - } -} diff --git a/autoload/shop/class.Producer.php b/autoload/shop/class.Producer.php index 16315b9..430ff82 100644 --- a/autoload/shop/class.Producer.php +++ b/autoload/shop/class.Producer.php @@ -1,4 +1,4 @@ - get( 'pp_shop_producer', '*', [ 'id' => $producer_id ] ); - foreach ( $result as $key => $val ) - $this -> $key = $val; + $repo = new \Domain\Producer\ProducerRepository( $mdb ); + $data = $repo->find( $producer_id ); - $rows = $mdb -> select( 'pp_shop_producer_lang', '*', [ 'producer_id' => $producer_id ] ); - foreach ( $rows as $row ) - { - $languages[ $row['lang_id'] ]['description'] = $row['description']; - $languages[ $row['lang_id'] ]['data'] = $row['data']; - $languages[ $row['lang_id'] ]['meta_title'] = $row['meta_title']; - } - - $this -> languages = $languages; + foreach ( $data as $key => $val ) + $this->$key = $val; } static public function producer_products( $producer_id, $lang_id, $bs ) { global $mdb; - $count = $mdb -> count( 'pp_shop_products', [ 'AND' => [ 'producer_id' => $producer_id, 'status' => 1 ] ] ); - $ls = ceil( $count / 12 ); - - if ( $bs < 1 ) - $bs = 1; - else if ( $bs > $ls ) - $bs = $ls; - - $from = 12 * ( $bs - 1 ); - - if ( $from < 0 ) - $from = 0; - - $results['products'] = $mdb -> select( 'pp_shop_products', 'id', [ 'AND' => [ 'producer_id' => $producer_id, 'status' => 1 ], 'LIMIT' => [ $from, 12 ] ] ); - - $results['ls'] = $ls; - - return $results; + $repo = new \Domain\Producer\ProducerRepository( $mdb ); + return $repo->producerProducts( (int) $producer_id, 12, (int) $bs ); } public function __get( $variable ) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 354840a..b952091 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,23 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- +## ver. 0.273 (2026-02-15) - ShopProducer + +- **ShopProducer** - migracja `/admin/shop_producer` na Domain + DI + nowe widoki + - NOWE: `Domain\Producer\ProducerRepository` (`listForAdmin`, `find`, `save`, `delete`, `allProducers`, `findForFrontend`, `producerProducts`, `allActiveIds`) + - NOWE: `admin\Controllers\ShopProducerController` (DI) z akcjami `list`, `edit`, `save`, `delete` + - UPDATE: modul `/admin/shop_producer/*` dziala na `components/table-list` i `components/form-edit` z zakladkami jezykowymi (Opis + SEO) + - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_producer/list/` + - UPDATE: `shop\Producer` przepiety na fasade do `Domain\Producer\ProducerRepository` + - UPDATE: `admin\factory\ShopProduct` - 2 wywolania `admin\factory\ShopTransport` przepiete na `Domain\Transport\TransportRepository` + - UPDATE: `admin\controls\ShopProduct` - usuniety fallback do `admin\factory\Layouts` + - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopProducer.php`, `admin/templates/shop-producer/list.php`, `admin/templates/shop-producer/edit.php` + - CLEANUP: usuniete 6 pustych factory facades: `admin\factory\Languages`, `admin\factory\Newsletter`, `admin\factory\Scontainers`, `admin\factory\ShopProducer`, `admin\factory\ShopTransport`, `admin\factory\Layouts` + - TEST: dodane `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` i `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` +- Testy: **OK (338 tests, 1063 assertions)** + +--- + ## ver. 0.272 (2026-02-15) - ShopProductSets - **ShopProductSets** - migracja `/admin/shop_product_sets` na Domain + DI + nowe widoki diff --git a/docs/DATABASE_STRUCTURE.md b/docs/DATABASE_STRUCTURE.md index 5ca1d44..69bdb6f 100644 --- a/docs/DATABASE_STRUCTURE.md +++ b/docs/DATABASE_STRUCTURE.md @@ -199,7 +199,7 @@ Jezyki panelu i frontendu. | start | 1 = domyslny jezyk | | o | Kolejnosc | -**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `admin\\factory\\Languages`, `front\\factory\\Languages` +**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `front\\factory\\Languages` ## pp_langs_translations Slownik tlumaczen panelu/frontendu. @@ -462,7 +462,7 @@ Rodzaje transportu sklepu (modul `/admin/shop_transport`). | apilo_carrier_account_id | ID konta przewoznika w Apilo (NULL gdy brak mapowania) | | o | Kolejnosc wyswietlania | -**Uzywane w:** `Domain\Transport\TransportRepository`, `admin\Controllers\ShopTransportController`, `front\factory\ShopTransport`, `admin\factory\ShopTransport` +**Uzywane w:** `Domain\Transport\TransportRepository`, `admin\Controllers\ShopTransportController`, `front\factory\ShopTransport` ## pp_shop_transport_payment_methods Powiazanie metod transportu z metodami platnosci (tabela lacznikowa). @@ -540,3 +540,31 @@ Powiazanie kompletow z produktami (tabela lacznikowa). **Uzywane w:** `Domain\ProductSet\ProductSetRepository`, `shop\Product`, `front\factory\ShopProduct`, `admin\factory\ShopProduct` **Aktualizacja 2026-02-15 (ver. 0.272):** modul `/admin/shop_product_sets` korzysta z `Domain\ProductSet\ProductSetRepository` przez `admin\Controllers\ShopProductSetsController`. Usunieto legacy klasy `admin\controls\ShopProductSets` i `admin\factory\ShopProductSet`. `shop\ProductSet` dziala jako fasada do repozytorium. + +## pp_shop_producer +Producenci produktow (modul `/admin/shop_producer`). + +| Kolumna | Opis | +|---------|------| +| id | PK | +| name | Nazwa producenta | +| status | Status: 1 = aktywny, 0 = nieaktywny | +| img | Sciezka do logo producenta (NULL gdy brak) | + +**Uzywane w:** `Domain\Producer\ProducerRepository`, `admin\Controllers\ShopProducerController`, `shop\Producer`, `shop\Product`, `front\controls\ShopProducer` + +## pp_shop_producer_lang +Tlumaczenia producentow (per jezyk). FK kaskadowe ON DELETE CASCADE. + +| Kolumna | Opis | +|---------|------| +| id | PK | +| producer_id | FK do pp_shop_producer | +| lang_id | ID jezyka (np. pl, en) | +| description | Opis producenta (TEXT) | +| data | Dane producenta (TEXT, HTML) | +| meta_title | Meta title SEO (VARCHAR 255) | + +**Uzywane w:** `Domain\Producer\ProducerRepository`, `shop\Producer`, `shop\Product` + +**Aktualizacja 2026-02-15 (ver. 0.273):** modul `/admin/shop_producer` korzysta z `Domain\Producer\ProducerRepository` przez `admin\Controllers\ShopProducerController`. Usunieto legacy `admin\controls\ShopProducer` i `admin\factory\ShopProducer`. `shop\Producer` dziala jako fasada do repozytorium. diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index 915fe3c..88e05bc 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -222,6 +222,8 @@ autoload/ │ │ └── TransportRepository.php │ ├── ProductSet/ │ │ └── ProductSetRepository.php +│ ├── Producer/ +│ │ └── ProducerRepository.php │ └── ... ├── admin/ │ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\) @@ -294,5 +296,16 @@ Pelna dokumentacja testow: `TESTING.md` - Usunieto legacy: `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php`. - `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository`. +## Dodatkowa aktualizacja 2026-02-15 (ver. 0.273) +- Dodano modul domenowy `Domain/Producer/ProducerRepository.php`. +- Dodano kontroler DI `admin/Controllers/ShopProducerController.php`. +- Modul `/admin/shop_producer/*` dziala na nowych widokach (`producers-list`, `producer-edit`). +- Usunieto legacy: `autoload/admin/controls/class.ShopProducer.php`, `admin/templates/shop-producer/list.php`, `admin/templates/shop-producer/edit.php`. +- `shop\Producer` przepiety na fasade do `Domain\Producer\ProducerRepository`. +- `admin\controls\ShopProduct` uzywa `ProducerRepository::allProducers()`. +- Usunieto 6 pustych factory facades: `admin\factory\Languages`, `admin\factory\Newsletter`, `admin\factory\Scontainers`, `admin\factory\ShopProducer`, `admin\factory\ShopTransport`, `admin\factory\Layouts`. +- Przepieto 2 wywolania `admin\factory\ShopTransport` w `admin\factory\ShopProduct` na `Domain\Transport\TransportRepository`. +- Usuniety fallback do `admin\factory\Layouts` w `admin\controls\ShopProduct`. + --- *Dokument aktualizowany: 2026-02-15* diff --git a/docs/REFACTORING_PLAN.md b/docs/REFACTORING_PLAN.md index 564c71b..f8125f2 100644 --- a/docs/REFACTORING_PLAN.md +++ b/docs/REFACTORING_PLAN.md @@ -152,6 +152,7 @@ grep -r "Product::getQuantity" . | 21 | ShopTransport | 0.269 | listForAdmin, find, save, allActive, allForAdmin, findActiveById, getTransportCost, lowestTransportPrice, getApiloCarrierAccountId, powiazanie z PaymentMethod, DI kontroler | | 22 | ShopAttribute | 0.271 | list/edit/save/delete/values, nowy edytor wartosci, cleanup legacy, przepiecie zaleznosci kombinacji | | 23 | ShopProductSets | 0.272 | listForAdmin, find, save, delete, allSets, allProductsMap, multi-select Selectize, DI kontroler | +| 24 | ShopProducer | 0.273 | listForAdmin, find, save, delete, allProducers, producerProducts, fasada shop\Producer, DI kontroler | ### Product - szczegolowy status - ✅ getQuantity (ver. 0.238) @@ -169,11 +170,11 @@ grep -r "Product::getQuantity" . ## Kolejność refaktoryzacji (priorytet) -1-23: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets +1-24: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer Nastepne: -24. **Order** -25. **Category** +25. **Order** +26. **Category** ## Form Edit System @@ -251,7 +252,9 @@ tests/ │ │ ├── Dictionaries/DictionariesRepositoryTest.php │ │ ├── Integrations/IntegrationsRepositoryTest.php │ │ ├── PaymentMethod/PaymentMethodRepositoryTest.php +│ │ ├── Producer/ProducerRepositoryTest.php │ │ ├── Product/ProductRepositoryTest.php +│ │ ├── ProductSet/ProductSetRepositoryTest.php │ │ ├── Promotion/PromotionRepositoryTest.php │ │ ├── Settings/SettingsRepositoryTest.php │ │ ├── ShopStatus/ShopStatusRepositoryTest.php @@ -265,12 +268,18 @@ tests/ │ ├── SettingsControllerTest.php │ ├── ShopCouponControllerTest.php │ ├── ShopPaymentMethodControllerTest.php +│ ├── ShopProducerControllerTest.php +│ ├── ShopProductSetsControllerTest.php │ ├── ShopPromotionControllerTest.php │ ├── ShopStatusesControllerTest.php │ └── UsersControllerTest.php └── Integration/ ``` -**Lacznie: 312 testow, 948 asercji** +**Lacznie: 338 testow, 1063 asercji** + +Aktualizacja 2026-02-15 (ver. 0.273): +- dodano testy `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` +- dodano testy `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` Aktualizacja 2026-02-14 (ver. 0.271): - dodano testy `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` diff --git a/docs/TESTING.md b/docs/TESTING.md index 5489e5e..07f5d6b 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -36,7 +36,7 @@ Alternatywnie (Git Bash): Ostatnio zweryfikowano: 2026-02-15 ```text -OK (324 tests, 1000 assertions) +OK (338 tests, 1063 assertions) ``` ## Struktura testow @@ -54,6 +54,7 @@ tests/ | | |-- Dictionaries/DictionariesRepositoryTest.php | | |-- Integrations/IntegrationsRepositoryTest.php | | |-- PaymentMethod/PaymentMethodRepositoryTest.php +| | |-- Producer/ProducerRepositoryTest.php | | |-- Product/ProductRepositoryTest.php | | |-- ProductSet/ProductSetRepositoryTest.php | | |-- Promotion/PromotionRepositoryTest.php @@ -71,6 +72,7 @@ tests/ | |-- ShopAttributeControllerTest.php | |-- ShopCouponControllerTest.php | |-- ShopPaymentMethodControllerTest.php +| |-- ShopProducerControllerTest.php | |-- ShopProductSetsControllerTest.php | |-- ShopPromotionControllerTest.php | |-- ShopStatusesControllerTest.php @@ -411,3 +413,14 @@ OK (324 tests, 1000 assertions) Nowe testy dodane 2026-02-15: - `tests/Unit/Domain/ProductSet/ProductSetRepositoryTest.php` (7 testow: find default/normalize, save insert/update, delete invalid, whitelist sortowania/paginacji, allSets) - `tests/Unit/admin/Controllers/ShopProductSetsControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora) + +## Aktualizacja suite (ShopProducer refactor, ver. 0.273) +Ostatnio zweryfikowano: 2026-02-15 + +```text +OK (338 tests, 1063 assertions) +``` + +Nowe testy dodane 2026-02-15: +- `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` (9 testow: find default/normalize, save insert/update, delete invalid/success, whitelist sortowania/paginacji, allProducers, producerProducts) +- `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora) diff --git a/temp/update_build/delete_files_0.273.txt b/temp/update_build/delete_files_0.273.txt new file mode 100644 index 0000000..2f1a8af --- /dev/null +++ b/temp/update_build/delete_files_0.273.txt @@ -0,0 +1,9 @@ +admin/templates/shop-producer/edit.php +admin/templates/shop-producer/list.php +autoload/admin/controls/class.ShopProducer.php +autoload/admin/factory/class.Languages.php +autoload/admin/factory/class.Layouts.php +autoload/admin/factory/class.Newsletter.php +autoload/admin/factory/class.Scontainers.php +autoload/admin/factory/class.ShopProducer.php +autoload/admin/factory/class.ShopTransport.php diff --git a/temp/update_build/update_0.273.zip b/temp/update_build/update_0.273.zip new file mode 100644 index 0000000..b277f34 Binary files /dev/null and b/temp/update_build/update_0.273.zip differ diff --git a/tests/Unit/Domain/Producer/ProducerRepositoryTest.php b/tests/Unit/Domain/Producer/ProducerRepositoryTest.php new file mode 100644 index 0000000..d5f1b8e --- /dev/null +++ b/tests/Unit/Domain/Producer/ProducerRepositoryTest.php @@ -0,0 +1,240 @@ +createMock(\medoo::class); + $repository = new ProducerRepository($mockDb); + + $result = $repository->find(0); + + $this->assertIsArray($result); + $this->assertSame(0, $result['id']); + $this->assertSame('', $result['name']); + $this->assertSame(1, $result['status']); + $this->assertNull($result['img']); + $this->assertSame([], $result['languages']); + } + + public function testFindNormalizesProducerData(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('get') + ->with('pp_shop_producer', '*', ['id' => 5]) + ->willReturn([ + 'id' => '5', + 'name' => 'Apple', + 'status' => '1', + 'img' => '/logo.png', + ]); + + $mockDb->expects($this->once()) + ->method('select') + ->with('pp_shop_producer_lang', '*', ['producer_id' => 5]) + ->willReturn([ + [ + 'lang_id' => 'pl', + 'description' => 'Opis PL', + 'data' => 'Dane PL', + 'meta_title' => 'Meta PL', + ], + [ + 'lang_id' => 'en', + 'description' => 'Desc EN', + 'data' => null, + 'meta_title' => null, + ], + ]); + + $repository = new ProducerRepository($mockDb); + $result = $repository->find(5); + + $this->assertSame(5, $result['id']); + $this->assertSame('Apple', $result['name']); + $this->assertSame(1, $result['status']); + $this->assertSame('/logo.png', $result['img']); + $this->assertArrayHasKey('pl', $result['languages']); + $this->assertArrayHasKey('en', $result['languages']); + $this->assertSame('Opis PL', $result['languages']['pl']['description']); + $this->assertSame('Meta PL', $result['languages']['pl']['meta_title']); + $this->assertNull($result['languages']['en']['meta_title']); + } + + public function testSaveInsertsNewProducer(): void + { + $mockDb = $this->createMock(\medoo::class); + $insertCalls = []; + + $mockDb->method('insert') + ->willReturnCallback(function ($table, $row) use (&$insertCalls) { + $insertCalls[] = ['table' => $table, 'row' => $row]; + }); + + $mockDb->expects($this->once()) + ->method('id') + ->willReturn(42); + + $langs = [['id' => 'pl'], ['id' => 'en']]; + + $repository = new ProducerRepository($mockDb); + $id = $repository->save( + 0, 'Samsung', 1, '/samsung.png', + ['pl' => 'Opis PL', 'en' => 'Desc EN'], + ['pl' => 'Dane', 'en' => null], + ['pl' => 'Meta PL', 'en' => 'Meta EN'], + $langs + ); + + $this->assertSame(42, $id); + + // 1st insert: pp_shop_producer + $this->assertSame('pp_shop_producer', $insertCalls[0]['table']); + $this->assertSame('Samsung', $insertCalls[0]['row']['name']); + $this->assertSame(1, $insertCalls[0]['row']['status']); + + // 2nd and 3rd insert: pp_shop_producer_lang for each language + $langInserts = array_filter($insertCalls, fn($c) => $c['table'] === 'pp_shop_producer_lang'); + $this->assertCount(2, $langInserts); + } + + public function testSaveUpdatesExistingProducer(): void + { + $mockDb = $this->createMock(\medoo::class); + $updateRow = null; + + $mockDb->expects($this->atLeastOnce()) + ->method('update') + ->willReturnCallback(function ($table, $row, $where) use (&$updateRow) { + if ($table === 'pp_shop_producer') { + $updateRow = $row; + $this->assertSame(['id' => 7], $where); + } + return true; + }); + + $mockDb->expects($this->never())->method('id'); + + // get for translation id check + $mockDb->method('get') + ->willReturn(100); + + $langs = [['id' => 'pl']]; + + $repository = new ProducerRepository($mockDb); + $id = $repository->save(7, 'Zaktualizowany', 0, null, ['pl' => 'Nowy opis'], ['pl' => null], ['pl' => 'Meta'], $langs); + + $this->assertSame(7, $id); + $this->assertSame('Zaktualizowany', $updateRow['name']); + $this->assertSame(0, $updateRow['status']); + } + + public function testDeleteReturnsFalseForInvalidId(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->never())->method('delete'); + + $repository = new ProducerRepository($mockDb); + $this->assertFalse($repository->delete(0)); + } + + public function testDeleteReturnsTrueOnSuccess(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('delete') + ->with('pp_shop_producer', ['id' => 3]) + ->willReturn(true); + + $repository = new ProducerRepository($mockDb); + $this->assertTrue($repository->delete(3)); + } + + public function testListForAdminWhitelistsSortAndPagination(): void + { + $mockDb = $this->createMock(\medoo::class); + $queries = []; + + $mockDb->method('query') + ->willReturnCallback(function ($sql, $params = []) use (&$queries) { + $queries[] = ['sql' => $sql, 'params' => $params]; + + if (strpos($sql, 'COUNT(0)') !== false) { + return new class { + public function fetchAll() { return [[1]]; } + }; + } + + return new class { + public function fetchAll() { + return [[ + 'id' => 1, + 'name' => 'Test', + 'status' => 1, + 'img' => null, + ]]; + } + }; + }); + + $repository = new ProducerRepository($mockDb); + $result = $repository->listForAdmin( + [], + 'name DESC; DROP TABLE pp_shop_producer; --', + 'DESC; DELETE FROM pp_users; --', + 1, + 999 + ); + + $this->assertCount(2, $queries); + $dataSql = $queries[1]['sql']; + + $this->assertMatchesRegularExpression('/ORDER BY\s+p\.name\s+ASC,\s+p\.id\s+DESC/i', $dataSql); + $this->assertStringNotContainsString('DROP TABLE', $dataSql); + $this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql); + } + + public function testAllProducersReturnsFormattedList(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('select') + ->with('pp_shop_producer', ['id', 'name'], ['ORDER' => ['name' => 'ASC']]) + ->willReturn([ + ['id' => '1', 'name' => 'Apple'], + ['id' => '2', 'name' => 'Samsung'], + ]); + + $repository = new ProducerRepository($mockDb); + $result = $repository->allProducers(); + + $this->assertCount(2, $result); + $this->assertSame(1, $result[0]['id']); + $this->assertSame('Apple', $result[0]['name']); + $this->assertSame(2, $result[1]['id']); + } + + public function testProducerProductsReturnsPaginatedResults(): void + { + $mockDb = $this->createMock(\medoo::class); + $mockDb->expects($this->once()) + ->method('count') + ->willReturn(30); + + $mockDb->expects($this->once()) + ->method('select') + ->willReturn([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + + $repository = new ProducerRepository($mockDb); + $result = $repository->producerProducts(5, 12, 1); + + $this->assertArrayHasKey('products', $result); + $this->assertArrayHasKey('ls', $result); + $this->assertSame(3, $result['ls']); + } +} diff --git a/tests/Unit/admin/Controllers/ShopProducerControllerTest.php b/tests/Unit/admin/Controllers/ShopProducerControllerTest.php new file mode 100644 index 0000000..582a0ab --- /dev/null +++ b/tests/Unit/admin/Controllers/ShopProducerControllerTest.php @@ -0,0 +1,67 @@ +repository = $this->createMock(ProducerRepository::class); + $this->languagesRepository = $this->createMock(LanguagesRepository::class); + $this->controller = new ShopProducerController($this->repository, $this->languagesRepository); + } + + public function testConstructorAcceptsRepositories(): void + { + $controller = new ShopProducerController($this->repository, $this->languagesRepository); + $this->assertInstanceOf(ShopProducerController::class, $controller); + } + + public function testHasMainActionMethods(): void + { + $this->assertTrue(method_exists($this->controller, 'list')); + $this->assertTrue(method_exists($this->controller, 'edit')); + $this->assertTrue(method_exists($this->controller, 'save')); + $this->assertTrue(method_exists($this->controller, 'delete')); + } + + public function testHasLegacyAliasMethods(): void + { + $this->assertTrue(method_exists($this->controller, 'view_list')); + $this->assertTrue(method_exists($this->controller, 'producer_edit')); + $this->assertTrue(method_exists($this->controller, 'producer_save')); + $this->assertTrue(method_exists($this->controller, 'producer_delete')); + } + + public function testActionMethodReturnTypes(): void + { + $reflection = new \ReflectionClass($this->controller); + + $this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('view_list')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType()); + $this->assertEquals('string', (string)$reflection->getMethod('producer_edit')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType()); + $this->assertEquals('void', (string)$reflection->getMethod('producer_delete')->getReturnType()); + } + + public function testConstructorRequiresBothRepositories(): void + { + $reflection = new \ReflectionClass(ShopProducerController::class); + $constructor = $reflection->getConstructor(); + $params = $constructor->getParameters(); + + $this->assertCount(2, $params); + $this->assertEquals('Domain\Producer\ProducerRepository', $params[0]->getType()->getName()); + $this->assertEquals('Domain\Languages\LanguagesRepository', $params[1]->getType()->getName()); + } +}