Index: /changelog
===================================================================
--- /changelog	(revision 8193)
+++ /changelog	(revision 8195)
@@ -552,4 +552,5 @@
 
 #4.4.3
+- Feature: Bilder kÃ¶nnen an Produktgruppen angehÃ€ngt werden
 - Bugfix: ProduktgruppenÃŒbersichtsseite funktioniert ohne Lagerbestand
 - Bugfix: user_views Templates werden aus dem Child-Theme geladen
Index: /model/productgroup.php
===================================================================
--- /model/productgroup.php	(revision 8195)
+++ /model/productgroup.php	(revision 8195)
@@ -0,0 +1,119 @@
+<?php
+
+    namespace wpsg;
+
+    /**
+     * Model fÃŒr eine Bestellung
+     */
+    class productgroup extends \wpsg_model {
+
+        static $_tableName = 'WPSG_TBL_PRODUCTS_GROUP';
+
+        public $data = null;
+
+        /**
+         * LÃ€dt die Daten der Bestellung
+         */
+        public function load($productgroup_id) {
+
+            $this->data = $this->db->fetchRow("SELECT * FROM `".constant(self::$_tableName)."` WHERE `id` = '".intval($productgroup_id)."' ");
+            $this->id = $productgroup_id;
+
+        }
+
+        public function setImage(int $image_id) {
+
+            $this->setMeta('image', $image_id);
+
+        }
+
+        public function getImage(): int {
+
+            return intval($this->getMeta('image'));
+
+        }
+
+        public function getName(): string {
+
+            return __($this->data['name'], 'wpsg');
+
+        }
+
+        public function getTemplate() {
+
+			if ($this->data['template_file'] !== '0' && $this->data['template_file'] !== '') $template = $this->data['template_file']; else $template = false;
+
+            return $template;
+
+        }
+
+        /**
+         * @param $arFilter
+         * @return \wpsg_product[]
+         * @throws Exception
+         */
+        public function findProducts($arFilter = []) {
+
+            $arFilter['productgroup_ids'] = [$this->getId()];
+
+            return \wpsg_product::find($arFilter);
+
+        }
+
+        /**
+         * @param $arFilter
+         * @return productgroup[]
+         * @throws Exception
+         */
+        public static function find($arFilter = []) {
+
+            list($strQuerySELECT, $strQueryWHERE, $strQueryJOIN, $strQueryHAVING, $strQueryORDER) = self::getQueryParts($arFilter);
+
+            $strLimit = "";
+
+            if (wpsg_isSizedArray($arFilter['limit'])) $strLimit = "LIMIT ".wpsg_q($arFilter['limit'][0]).", ".wpsg_q($arFilter['limit'][1]);
+
+            $strQuery = "
+				SELECT
+					PG.`id`
+					".$strQuerySELECT."
+				FROM
+					`".constant(self::$_tableName)."` AS PG
+					    ".$strQueryJOIN."
+				WHERE
+					1
+					".$strQueryWHERE."
+				HAVING
+					1
+					".$strQueryHAVING."
+				ORDER BY
+					".$strQueryORDER."		
+				".$strLimit."
+			";
+
+            $arProductgroupIDs = $GLOBALS['wpsg_db']->fetchAssocField($strQuery);
+            $arReturn = array();
+
+            foreach ($arProductgroupIDs as $productgroup_id) {
+
+                $arReturn[$productgroup_id] = self::getInstance($productgroup_id);
+
+            }
+
+            return $arReturn;
+
+        }
+
+        public static function getQueryParts($arFilter = []) {
+
+            $strQuerySELECT = "";
+            $strQueryWHERE = "";
+            $strQueryJOIN = "";
+            $strQueryHAVING = "";
+            $strQueryORDER = " `id` ASC ";
+
+            return [$strQuerySELECT, $strQueryWHERE, $strQueryJOIN, $strQueryHAVING, $strQueryORDER];
+
+        }
+
+    }
Index: /mods/wpsg_mod_productgroups.class.php
===================================================================
--- /mods/wpsg_mod_productgroups.class.php	(revision 8193)
+++ /mods/wpsg_mod_productgroups.class.php	(revision 8195)
@@ -95,9 +95,9 @@
 		} // public function settings_edit()
 
-		public function settings_save()
-		{
+		public function settings_save() {
 			
 			$this->shop->update_option('wpsg_productgroups_page', $_REQUEST['wpsg_productgroups_page'], false, false, WPSG_SANITIZE_PAGEID);
-			$this->shop->update_option('wpsg_productgroups_order', $_REQUEST['wpsg_productgroups_order'], false, false, WPSG_SANITIZE_VALUES, ['id', 'alphabetisch', 'buyed', 'erstellungsdatum', 'preis']);
+			$this->shop->update_option('wpsg_productgroups_page_layout', $_REQUEST['wpsg_productgroups_page_layout'], false, false, WPSG_SANITIZE_INT);
+            $this->shop->update_option('wpsg_productgroups_order', $_REQUEST['wpsg_productgroups_order'], false, false, WPSG_SANITIZE_VALUES, ['id', 'alphabetisch', 'buyed', 'erstellungsdatum', 'preis']);
 			$this->shop->update_option('wpsg_mod_productgroups_order_filter', $_REQUEST['wpsg_mod_productgroups_order_filter'], false, false, WPSG_SANITIZE_CHECKBOX);
 			$this->shop->update_option('wpsg_mod_productgroups_productindex', $_REQUEST['wpsg_mod_productgroups_productindex'], false, false, WPSG_SANITIZE_CHECKBOX);
@@ -308,6 +308,5 @@
 		} // public function produkt_edit_sidebar(&$product_content, &$produkt_data)
 
-		public function content_filter(&$content)
-		{
+		public function content_filter(&$content) {
 
 		    $id = wpsg_get_the_id();
@@ -315,6 +314,13 @@
 			if ($id <= 0 || $id != $this->shop->get_option('wpsg_productgroups_page')) return;
 
-			if (isset($_REQUEST['show']) && $_REQUEST['show'] > 0)
-			{
+            if ($this->shop->get_option('wpsg_productgroups_page_layout') === '1') {
+
+                $content = $this->shop->render(WPSG_PATH_VIEW.'/mods/mod_productgroups/show_2.phtml', false);
+
+                return;
+
+            }
+
+			if (isset($_REQUEST['show']) && $_REQUEST['show'] > 0) {
 
 				$arrGrp = $this->db->fetchRow("
@@ -330,6 +336,5 @@
 				");
 
-				if ($this->shop->isOtherLang())
-				{
+				if ($this->shop->isOtherLang()) {
 
 					$lang = @unserialize($arrGrp['lang']);
@@ -340,9 +345,7 @@
 
 				$strOrder = "sticky DESC";
-				if ($this->shop->get_option('wpsg_productgroups_order') != '')
-				{
-
-					switch ($this->shop->get_option('wpsg_productgroups_order'))
-					{
+				if ($this->shop->get_option('wpsg_productgroups_order') != '') {
+
+					switch ($this->shop->get_option('wpsg_productgroups_order')) {
 
 						case 'id':
@@ -384,6 +387,5 @@
 
 				// Bilder der Produkte
-				foreach ($arrProdukte as $k => $p)
-				{
+				foreach ($arrProdukte as $k => $p) {
 
 					$arrProdukte[$k] = $this->shop->loadProduktArray($p['id']);
@@ -399,7 +401,5 @@
 				$content = $this->shop->render(WPSG_PATH_VIEW.'/mods/mod_productgroups/show.phtml', false);
 
-			}
-			else
-			{
+			} else {
 
 				$arrGrp = $this->db->fetchAssoc("
@@ -414,9 +414,7 @@
 				");
 
-				foreach ($arrGrp as $k => $pg)
-				{
-
-					if ($this->shop->isOtherLang())
-					{
+				foreach ($arrGrp as $k => $pg) {
+
+					if ($this->shop->isOtherLang()) {
 
 						$lang = @unserialize($pg['lang']);
@@ -428,9 +426,7 @@
 					$strOrder = "sticky DESC";
 
-					if ($this->shop->get_option('wpsg_productgroups_order') != '')
-					{
-
-						switch ($this->shop->get_option('wpsg_productgroups_order'))
-						{
+					if ($this->shop->get_option('wpsg_productgroups_order') != '')	{
+
+						switch ($this->shop->get_option('wpsg_productgroups_order')) {
 
 							case 'id':
@@ -752,4 +748,7 @@
 			}
 
+            $oProductgroup = \wpsg\productgroup::getInstance(intval($_REQUEST['edit_id']));
+            $oProductgroup->setImage(intval($_REQUEST['image']??0));
+
 			$this->shop->callMods('wpsg_mod_productgroups_save', array($_REQUEST['edit_id']));
 
Index: /views/mods/mod_productgroups/add.phtml
===================================================================
--- /views/mods/mod_productgroups/add.phtml	(revision 8193)
+++ /views/mods/mod_productgroups/add.phtml	(revision 8195)
@@ -4,4 +4,13 @@
 	 * Template fÃŒr das Anlegen/Bearbeiten einer Produktgruppe
 	 */
+
+    /** @var \wpsg\productgroup|null $oProductgroup */
+    $oProductgroup = null;
+
+    if (intval($_REQUEST['edit_id']??0) > 0) {
+
+        $oProductgroup = wpsg\productgroup::getInstance(intval($_REQUEST['edit_id']));
+
+    }
 
 ?>
@@ -62,4 +71,76 @@
                         <?php echo wpsg_drawForm_Select('infopage', __('Info Seite', 'wpsg'), $this->view['pages'], $this->view['data']['infopage']); ?>
 
+                        <div class="form-group form-group-sm ">
+                            <label class="col-sm-6 control-label"><?php echo __('Produktgruppenbild', 'wpsg'); ?></label>
+                            <div class="col-sm-6">
+                                <div class="wpsg_field_wrap">
+                                    <div id="imageBox"><?php
+
+                                        if ($oProductgroup !== null) {
+
+                                            if ($oProductgroup->getImage() > 0) {
+
+                                                echo wp_get_attachment_image($oProductgroup->getImage(), 'full', false, [
+                                                    'style' => 'max-width:100%; height:auto; margin-bottom:8px;'
+                                                ]);
+
+                                            }
+
+                                        }
+
+                                    ?></div>
+
+                                    <input value="Mediathek" class="button" type="button" style="text-align:center;" size="10" id="btnAddImgLink" />
+                                    <input type="hidden" name="image" value="<?php echo (($oProductgroup !== null)?$oProductgroup->getImage():0); ?>" id="image_input" />
+
+                                    <script>
+
+                                        let wpframe = undefined;
+                                        let imageBox = document.getElementById('imageBox');
+                                        let elImageInput = document.getElementById('image_input');
+
+                                        document.getElementById('btnAddImgLink').addEventListener('click', (event) => {
+
+                                            event.preventDefault();
+
+                                            if (wpframe !== undefined) { wpframe.open(); return; }
+
+                                            wpframe = wp.media.frames.file_frame = wp.media({
+                                                title: "<?php echo __('AuswÃ€hlen oder Hochladen von Medien', 'wpsg'); ?>",
+                                                button: { text: "<?php echo __('Medien benutzen', 'wpsg'); ?>" },
+                                                multiple: false
+                                            });
+
+                                            wpframe.on('select', () => {
+
+                                                let attachments = wpframe.state().get('selection').map((attachment) => {
+
+                                                    attachment.toJSON();
+
+                                                    return attachment;
+
+                                                });
+
+                                                for (let i = 0; i < attachments.length; ++i) {
+
+                                                    let attachment = attachments[i];
+
+                                                    imageBox.innerHTML = '<img src="' + attachment.attributes.url + '" alt="" style="max-width:100%; margin-bottom:8px;"/>';
+                                                    elImageInput.value = attachment.attributes.id;
+
+                                                }
+
+                                            });
+
+                                            wpframe.open();
+
+                                        });
+
+                                    </script>
+                                </div>
+                            </div>
+                            <div class="clearfix wpsg_clear"></div>
+                        </div>
+
                     <?php echo wpsg_drawForm_AdminboxEnd(); ?>
 
Index: /views/mods/mod_productgroups/settings_edit.phtml
===================================================================
--- /views/mods/mod_productgroups/settings_edit.phtml	(revision 8193)
+++ /views/mods/mod_productgroups/settings_edit.phtml	(revision 8195)
@@ -7,4 +7,8 @@
 ?>
 <?php echo wpsg_drawForm_Select('wpsg_productgroups_page', __('ProduktgruppenÃŒbersichtsseite', 'wpsg'), $this->view['pages'], $this->get_option('wpsg_productgroups_page'), array('help' => 'wpsg_mod_productgroups_page')); ?>
+<?php echo wpsg_drawForm_Select('wpsg_productgroups_page_layout', __('Layout der Ãbersichtsseite', 'wpsg'), [
+    '0' => 'Layout 1 (show.phtml)',
+    '1' => 'Layout 2 (show_2.phml)'
+], $this->get_option('wpsg_productgroups_page_layout'), ['help' => 'wpsg_productgroups_page_layout']); ?>
 <?php echo wpsg_drawForm_Select('wpsg_productgroups_order', __('Sortierung innerhalb der Gruppe', 'wpsg'), array(
 	'id' => __('Nach ID', 'wpsg'),
Index: /views/mods/mod_productgroups/show_2.phtml
===================================================================
--- /views/mods/mod_productgroups/show_2.phtml	(revision 8195)
+++ /views/mods/mod_productgroups/show_2.phtml	(revision 8195)
@@ -0,0 +1,81 @@
+<?php
+
+    /** Template fÃŒr die Anzeige von Produktgruppen Layout 2 */
+
+    namespace wpsg;
+
+    $page_url = \get_permalink(\get_the_ID());
+    $page_url .= ((strpos($page_url, '?') === false)?'?':'&');
+
+    if (isset($_REQUEST['show'])) {
+
+        $oProductgroup = productgroup::getInstance(intval($_REQUEST['show']));
+        $arProducts = $oProductgroup->findProducts();
+        $view = 'products';
+
+    } else {
+
+        $arProductgroups = productgroup::find();
+        $view = 'productgroups';
+
+    }
+
+?>
+
+<div class="wpsg_mod_productgroup_layout2_wrap">
+    <?php if ($view === 'products') { ?>
+
+        <div class="wpsg_mod_productgroup_layout2 products">
+
+            <?php foreach ($arProducts as $oProduct) { ?>
+
+                <?php echo $this->renderProdukt($oProduct->getId(), $oProductgroup->getTemplate()); ?>
+
+            <?php } ?>
+
+            <br />
+
+            <a href="<?php echo $page_url; ?>"><?php echo __('ZurÃŒck zur Ãbersicht', 'wpsg'); ?></a>
+            
+        </div>
+
+    <?php } else if ($view === 'productgroups') { ?>
+
+        <div class="wpsg_mod_productgroup_layout2 productgroups">
+
+            <?php foreach ($arProductgroups as $k => $oProductgroup) { ?>
+
+                <a href="<?php echo $page_url; ?>show=<?php echo $oProductgroup->getId(); ?>" class="productgroup">
+
+                    <div class="title"><?php echo $oProductgroup->getName(); ?></div>
+
+                    <?php echo \wp_get_attachment_image($oProductgroup->getImage(), 'full', false, [
+                        'class' => 'bg'
+                    ]); ?>
+
+                </a>
+
+            <?php } ?>
+
+        </div>
+
+        <style>
+
+            .wpsg_mod_productgroup_layout2.productgroups { all:revert; display:grid; grid-template-columns:repeat(2, 1fr); grid-template-rows:repeat(10, 1fr); grid-column-gap:1rem; grid-row-gap:1rem; }
+            .wpsg_mod_productgroup_layout2.productgroups > .productgroup { grid-column:span 2; padding-top:100%; text-decoration:none; position:relative; overflow:hidden; transition:all 0.3s ease; overflow:hidden width:100%; }
+            .wpsg_mod_productgroup_layout2.productgroups > .productgroup  .bg { position:absolute; left:0; top:0; object-position:50% 50%; background-color:#DEDEDE; width:100%; height:100%; object-fit:cover; z-index:-1; }
+            .wpsg_mod_productgroup_layout2.productgroups > .productgroup .title {  border-radius:2px; transition:all 0.3s ease; position:absolute; left:1rem; top:1rem; background-color:#000000; color:#FFFFFF; padding:0 0.5rem; }
+            .wpsg_mod_productgroup_layout2.productgroups > .productgroup:hover .title { background-color:#FFFFFF; color:#000000; }
+
+            @media screen and (min-width:768px) {
+
+                .wpsg_mod_productgroup_layout2.productgroups > .productgroup { padding-top:initial; grid-column:initial; }
+                .wpsg_mod_productgroup_layout2.productgroups > .productgroup:nth-child(6n + 1),
+                .wpsg_mod_productgroup_layout2.productgroups > .productgroup:nth-child(6n + 5) { grid-row:span 2; padding-top:200px; }
+
+            }
+
+        </style>
+
+    <?php } ?>
+</div>
Index: /views/produkttemplates/standard3.phtml
===================================================================
--- /views/produkttemplates/standard3.phtml	(revision 8195)
+++ /views/produkttemplates/standard3.phtml	(revision 8195)
@@ -0,0 +1,118 @@
+<?php
+
+	/*
+	 * Template fÃŒr das Produkt im Frontend Layout 3
+	 */
+
+	/** @var wpsg_product $oProduct */
+	$oProduct = $this->view['oProduct'];
+
+    $arAttachmentIDsAll = $this->imagehandler->getAttachmentIDs($this->view['data']['product_id']);
+
+?>
+
+<div class="wpsg_produkt_wrapper layout3">
+
+    <input type="hidden" name="wpsg_post_id" value="<?php echo get_the_ID(); ?>" />
+	<input type="hidden" name="titleDisplayed" value="<?php echo $this->titleDisplayed; ?>" />
+
+    <div class="col_wrap">
+        <div class="col image">
+            <div class="thumbnails">
+
+                <?php foreach ($arAttachmentIDsAll as $k => $image_id) { ?>
+
+                    <?php echo \wp_get_attachment_image($image_id, 'small', false, [
+                        'data-index' => $k
+                    ]); ?>
+
+                <?php } ?>
+
+            </div>
+            <div class="view">
+                <div class="wpsg_product_slider">
+
+                    <?php foreach ($arAttachmentIDsAll as $k => $image_id) { ?>
+
+                        <div>
+                            <div class="ds_image_zoom">
+                                <?php echo \wp_get_attachment_image($image_id, 'full', false, [
+                                    'data-index' => $k
+                                ]); ?>
+                            </div>
+                        </div>
+
+                    <?php } ?>
+
+                </div>
+            </div>
+        </div>
+        <div>
+            TODO
+        </div>
+    </div>
+
+    <style>
+
+        .wpsg_produkt_wrapper.layout3 .col_wrap { display:flex; width:100%; justify-content:stretch; gap:1rem; }
+        .wpsg_produkt_wrapper.layout3 .col_wrap > * { flex-grow:1; flex-shrink:0; width:0; }
+        .wpsg_produkt_wrapper.layout3 .col_wrap > .col.image { display:flex; justify-content:stretch; gap:0.5rem; }
+        .wpsg_produkt_wrapper.layout3 .col_wrap > .col.image .thumbnails { width:20%; flex-shrink:0; }
+        .wpsg_produkt_wrapper.layout3 .col_wrap > .col.image .thumbnails img { aspect-ratio: 1 / 1; object-fit:cover; object-position:50% 50%; }
+        .wpsg_produkt_wrapper img { max-width:100%; height:auto; }
+
+        .ds_image_zoom.attached { position:relative; overflow:hidden; cursor:-webkit-zoom-in; cursor:zoom-in; }
+        .ds_image_zoom.attached img { all:initial; position:absolute; left:0; top:0; /* transition:width 0.3s, height 0.3s, left 0.3s, top 0.3s; */ }
+
+    </style>
+
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.4/tiny-slider.css">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.2/min/tiny-slider.js"></script>
+    <script src="http://127.0.0.1:8080/dsimagezoom.js"></script>
+
+    <script>
+
+        const slider = tns({
+            container: '.wpsg_product_slider',
+            items: 1,
+            slideBy: 1,
+            controls: false,
+            autoplayButton: false,
+            nav: false,
+            arrowKeys: false,
+            autoplayButtonOutput: false,
+            autoplay: false,
+            autoHeight: true
+        });
+
+        for (const el_tn of document.querySelectorAll('.wpsg_produkt_wrapper.layout3 .thumbnails img')) {
+
+            el_tn.addEventListener('click', (event) => {
+
+                slider.goTo(el_tn.getAttribute('data-index'));
+
+            });
+
+        }
+
+        window.addEventListener('load', () => {
+
+            slider.goTo(0);
+
+        });
+
+        slider.events.on('transitionEnd', (event) => {
+
+            DsImageZoom.init(document.querySelectorAll('.tns-slide-active .ds_image_zoom'));
+
+        });
+
+        slider.events.on('transitionStart', (event) => {
+
+            DsImageZoom.destroy(document.querySelectorAll('.tns-item .ds_image_zoom'));
+
+        });
+
+    </script>
+
+</div>
Index: /wpshopgermany.php
===================================================================
--- /wpshopgermany.php	(revision 8193)
+++ /wpshopgermany.php	(revision 8195)
@@ -167,4 +167,5 @@
 	require_once(dirname(__FILE__).'/model/wpsg_customer.class.php');
 	require_once(dirname(__FILE__).'/model/wpsg_news.class.php');
+	require_once(dirname(__FILE__).'/model/productgroup.php');
 	require_once(dirname(__FILE__).'/mods/wpsg_mod_basic.class.php');
     require_once(dirname(__FILE__).'/lib/wpsg_calculation.class.php');
