Browse code

Integrate managed properties BE module and FE modules

Benjamin Roth authored on07/08/2024 13:28:28
Showing10 changed files
... ...
@@ -8,6 +8,7 @@
8 8
  * @license commercial
9 9
  */
10 10
 
11
+use vonRotenberg\RealEstateListingBundle\Model\ManagedPropertyModel;
11 12
 use vonRotenberg\RealEstateListingBundle\Model\RealEstateAssetsModel;
12 13
 use Contao\System;
13 14
 use Contao\ArrayUtil;
... ...
@@ -17,6 +18,9 @@ ArrayUtil::arrayInsert($GLOBALS['BE_MOD'], 1, [
17 18
   'vr_real_estate' => [
18 19
     'vr_re_categories' => [
19 20
       'tables' => array('tl_vr_real_estate_categories','tl_vr_real_estate_assets')
21
+    ],
22
+    'managed_properties' => [
23
+        'tables' => ['tl_vr_re_managedProperties'],
20 24
     ]
21 25
   ]
22 26
 ]);
... ...
@@ -31,5 +35,6 @@ if ($requestStack->getCurrentRequest() !== null && $scopeMatcher->isBackendReque
31 35
 
32 36
 $GLOBALS['TL_MODELS']['tl_vr_real_estate_assets'] = RealEstateAssetsModel::class;
33 37
 $GLOBALS['TL_MODELS']['tl_vr_real_estate_categories'] = RealEstateCategoriesModel::class;
38
+$GLOBALS['TL_MODELS']['tl_vr_re_managedProperties'] = ManagedPropertyModel::class;
34 39
 
35 40
 
... ...
@@ -8,6 +8,8 @@
8 8
  * @license commercial
9 9
  */
10 10
 
11
+use vonRotenberg\RealEstateListingBundle\Controller\FrontendModule\ManagedPropertyController;
12
+use vonRotenberg\RealEstateListingBundle\Controller\FrontendModule\ManagedPropertyReaderController;
11 13
 use vonRotenberg\RealEstateListingBundle\Controller\FrontendModule\RealEstateAssetsAdController;
12 14
 use vonRotenberg\RealEstateListingBundle\Controller\FrontendModule\RealEstateAssetsListController;
13 15
 use vonRotenberg\RealEstateListingBundle\Controller\FrontendModule\RealEstateAssetsReaderController;
... ...
@@ -15,6 +17,23 @@ use vonRotenberg\RealEstateListingBundle\Controller\FrontendModule\RealEstateAss
15 17
 $GLOBALS['TL_DCA']['tl_module']['palettes'][RealEstateAssetsListController::TYPE] = '{title_legend},name,headline,type;{real_estate_legend},vr_re_categories,imgSize,vr_re_grouped;{re_reader_legend},jumpTo;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
16 18
 $GLOBALS['TL_DCA']['tl_module']['palettes'][RealEstateAssetsAdController::TYPE] = '{title_legend},name,headline,type;{real_estate_legend},vr_re_categories;{re_reader_legend},jumpTo;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
17 19
 $GLOBALS['TL_DCA']['tl_module']['palettes'][RealEstateAssetsReaderController::TYPE] = '{title_legend},name,headline,type;{real_estate_legend},imgSize;{re_form_legend},vr_re_formUrl;{re_overview_legend},jumpTo;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
20
+$GLOBALS['TL_DCA']['tl_module']['palettes'][ManagedPropertyController::TYPE] = '
21
+    {title_legend},name,headline,type;
22
+    {image_legend},imgSize;
23
+    {re_reader_legend},jumpTo;
24
+    {template_legend:hide},customTpl;
25
+    {protected_legend:hide},protected;
26
+    {expert_legend:hide},cssID
27
+';
28
+
29
+$GLOBALS['TL_DCA']['tl_module']['palettes'][ManagedPropertyReaderController::TYPE] = '
30
+    {title_legend},name,headline,type;
31
+    {image_legend},imgSize;
32
+    {re_overview_legend},jumpTo;
33
+    {template_legend:hide},customTpl;
34
+    {protected_legend:hide},protected;
35
+    {expert_legend:hide},cssID
36
+';
18 37
 
19 38
 $GLOBALS['TL_DCA']['tl_module']['fields']['vr_re_categories'] = [
20 39
   'exclude'                 => true,
21 40
new file mode 100644
... ...
@@ -0,0 +1,184 @@
1
+<?php
2
+
3
+declare(strict_types=1);
4
+
5
+/**
6
+ * OBG Customizations
7
+ *
8
+ * Copyright (c) 2021 vonRotenberg
9
+ *
10
+ * @license commercial
11
+ */
12
+
13
+use Contao\DC_Table;
14
+use Contao\DataContainer;
15
+
16
+$GLOBALS['TL_DCA']['tl_vr_re_managedProperties'] = array
17
+(
18
+    // Config
19
+    'config' => array
20
+    (
21
+        'dataContainer'               => DC_Table::class,
22
+        'enableVersioning'            => true,
23
+        'markAsCopy'                  => 'address',
24
+        'sql' => array
25
+        (
26
+            'keys' => array
27
+            (
28
+                'id' => 'primary',
29
+            )
30
+        )
31
+    ),
32
+
33
+    // List
34
+    'list' => array
35
+    (
36
+        'sorting' => array
37
+        (
38
+            'mode'                    => DataContainer::MODE_SORTABLE,
39
+            'fields'                  => array('address'),
40
+            'flag'                    => DataContainer::SORT_INITIAL_LETTER_ASC,
41
+            'panelLayout'             => 'filter;sort,search,limit'
42
+        ),
43
+        'label' => array
44
+        (
45
+            'fields'                  => array('address'),
46
+        ),
47
+        'global_operations' => array
48
+        (
49
+            'all' => array
50
+            (
51
+                'href'                => 'act=select',
52
+                'class'               => 'header_edit_all',
53
+                'attributes'          => 'onclick="Backend.getScrollOffset()" accesskey="e"'
54
+            )
55
+        ),
56
+        'operations' => array
57
+        (
58
+            'edit' => array
59
+            (
60
+                'href'                => 'act=edit',
61
+                'icon'                => 'edit.svg',
62
+            ),
63
+            'copy' => array
64
+            (
65
+                'href'                => 'act=copy',
66
+                'icon'                => 'copy.svg'
67
+            ),
68
+            'delete' => array
69
+            (
70
+                'href'                => 'act=delete',
71
+                'icon'                => 'delete.svg',
72
+                'attributes'          => 'onclick="if(!confirm(\'' . ($GLOBALS['TL_LANG']['MSC']['deleteConfirm'] ?? null) . '\'))return false;Backend.getScrollOffset()"'
73
+            ),
74
+            'toggle' => array(
75
+                'href'                => 'act=toggle&amp;field=published',
76
+                'icon'                => 'visible.svg',
77
+            ),
78
+            'show' => array
79
+            (
80
+                'href'                => 'act=show',
81
+                'icon'                => 'show.svg'
82
+            )
83
+        )
84
+    ),
85
+
86
+    // Palettes
87
+    'palettes' => array
88
+    (
89
+        '__selector__'                => array(),
90
+        'default'                     => '{data_legend},address,postal,city,gallerySRC;{description_legend},description;{map_legend},mapIframe;{publish_legend},published,start,stop',
91
+    ),
92
+
93
+    // Subpalettes
94
+    'subpalettes' => array
95
+    (
96
+    ),
97
+
98
+    // Fields
99
+    'fields' => array
100
+    (
101
+        'id' => array
102
+        (
103
+            'sql'                     => "int(10) unsigned NOT NULL auto_increment"
104
+        ),
105
+        'tstamp' => array
106
+        (
107
+            'sql'                     => "int(10) unsigned NOT NULL default 0"
108
+        ),
109
+        'description' => array
110
+        (
111
+            'exclude'                 => true,
112
+            'inputType'               => 'textarea',
113
+            'eval'                    => array('mandatory'=>false, 'rte'=>'tinyMCE', 'helpwizard'=>true, 'tl_class'=>'clr'),
114
+            'explanation'             => 'insertTags',
115
+            'sql'                     => "mediumtext NULL"
116
+        ),
117
+        'postal'                => array
118
+        (
119
+            'exclude'   => true,
120
+            'inputType' => 'text',
121
+            'eval'      => array('mandatory' => true, 'rgxp' => 'natural', 'maxlength' => 5, 'tl_class' => 'w50'),
122
+            'sql'       => "varchar(5) NOT NULL default ''"
123
+        ),
124
+        'city'                  => array
125
+        (
126
+            'exclude'   => true,
127
+            'inputType' => 'text',
128
+            'default'   => 'Kehl',
129
+            'eval'      => array('mandatory' => true, 'maxlength' => 255, 'tl_class' => 'w50'),
130
+            'sql'       => "varchar(255) NOT NULL default ''"
131
+        ),
132
+        'address'               => array
133
+        (
134
+            'exclude'   => true,
135
+            'filter'    => true,
136
+            'inputType' => 'text',
137
+            'eval'      => array('mandatory' => false, 'maxlength' => 255, 'tl_class' => 'w50'),
138
+            'sql'       => "varchar(255) NOT NULL default ''"
139
+        ),
140
+        'gallerySRC'            => array
141
+        (
142
+            'exclude'   => true,
143
+            'inputType' => 'fileTree',
144
+            'eval'      => array('mandatory' => true, 'filesOnly' => true, 'fieldType' => 'checkbox', 'multiple' => true, 'tl_class' => 'clr', 'orderField' => 'orderSRC', 'files' => true, 'extensions' => \Contao\Config::get('validImageTypes'), 'isGallery' => true),
145
+            'sql'       => "blob NULL"
146
+        ),
147
+        'orderSRC'              => array
148
+        (
149
+            'label' => &$GLOBALS['TL_LANG']['MSC']['sortOrder'],
150
+            'sql'   => "blob NULL"
151
+        ),
152
+        'mapIframe'             => array
153
+        (
154
+            'search'                  => true,
155
+            'inputType'               => 'textarea',
156
+            'eval'                    => array('useRawRequestData'=>true, 'class'=>'monospace', 'rte'=>'ace|html', 'helpwizard'=>true),
157
+            'explanation'             => 'insertTags',
158
+            'sql'                     => "mediumtext NULL"
159
+        ),
160
+        'published'             => array
161
+        (
162
+            'exclude'   => true,
163
+            'filter'    => true,
164
+            'toggle'    => true,
165
+            'inputType' => 'checkbox',
166
+            'eval'      => array('doNotCopy' => true),
167
+            'sql'       => "char(1) NOT NULL default ''"
168
+        ),
169
+        'start'                 => array
170
+        (
171
+            'exclude'   => true,
172
+            'inputType' => 'text',
173
+            'eval'      => array('rgxp' => 'datim', 'datepicker' => true, 'tl_class' => 'w50 wizard'),
174
+            'sql'       => "varchar(10) NOT NULL default ''"
175
+        ),
176
+        'stop'                  => array
177
+        (
178
+            'exclude'   => true,
179
+            'inputType' => 'text',
180
+            'eval'      => array('rgxp' => 'datim', 'datepicker' => true, 'tl_class' => 'w50 wizard'),
181
+            'sql'       => "varchar(10) NOT NULL default ''"
182
+        )
183
+    )
184
+);
... ...
@@ -10,6 +10,12 @@
10 10
       <trans-unit id="MOD.vr_re_categories.1">
11 11
         <source>Immobilienobjekte verwalten</source>
12 12
       </trans-unit>
13
+      <trans-unit id="MOD.managed_properties.0">
14
+        <source>Verwaltete Objekte</source>
15
+      </trans-unit>
16
+      <trans-unit id="MOD.managed_properties.1">
17
+        <source>Verwaltete Objekte pflegen.</source>
18
+      </trans-unit>
13 19
       <trans-unit id="MOD.tl_vr_real_estate_categories">
14 20
         <source>Objektkategorie</source>
15 21
       </trans-unit>
16 22
new file mode 100644
... ...
@@ -0,0 +1,96 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<xliff version="1.1">
3
+    <file source-language="en" target-language="de">
4
+        <body>
5
+            <trans-unit id="tl_vr_re_managedProperties.address.0">
6
+                <source>Address</source>
7
+                <target>Adresse</target>
8
+            </trans-unit>
9
+            <trans-unit id="tl_vr_re_managedProperties.address.1">
10
+                <source>The address of the property.</source>
11
+                <target>Die Adresse des Objekts.</target>
12
+            </trans-unit>
13
+            <trans-unit id="tl_vr_re_managedProperties.postal.0">
14
+                <source>Postal</source>
15
+                <target>Postleitzahl</target>
16
+            </trans-unit>
17
+            <trans-unit id="tl_vr_re_managedProperties.postal.1">
18
+                <source>The postal of the property.</source>
19
+                <target>Die Postleitzahl des Objekts.</target>
20
+            </trans-unit>
21
+            <trans-unit id="tl_vr_re_managedProperties.city.0">
22
+                <source>City</source>
23
+                <target>Ort</target>
24
+            </trans-unit>
25
+            <trans-unit id="tl_vr_re_managedProperties.city.1">
26
+                <source>The name of the city where the property is situated.</source>
27
+                <target>Der Name des Ortes, wo das Objekt steht.</target>
28
+            </trans-unit>
29
+            <trans-unit id="tl_vr_re_managedProperties.gallerySRC.0">
30
+                <source>Images</source>
31
+                <target>Bilder</target>
32
+            </trans-unit>
33
+            <trans-unit id="tl_vr_re_managedProperties.gallerySRC.1">
34
+                <source>Select related images of the property in the file browser.</source>
35
+                <target>Wählen Sie relevante Bilder zum Objekt aus dem Dateibrowser aus.</target>
36
+            </trans-unit>
37
+            <trans-unit id="tl_vr_re_managedProperties.description.0">
38
+                <source>Description</source>
39
+                <target>Beschreibung</target>
40
+            </trans-unit>
41
+            <trans-unit id="tl_vr_re_managedProperties.description.1">
42
+                <source>A describing text about the property.</source>
43
+                <target>Ein beschreibender Text zum Objekt.</target>
44
+            </trans-unit>
45
+            <trans-unit id="tl_vr_re_managedProperties.mapIframe.0">
46
+                <source>Map HTML</source>
47
+                <target>Karten-HTML</target>
48
+            </trans-unit>
49
+            <trans-unit id="tl_vr_re_managedProperties.mapIframe.1">
50
+                <source>HTML snippet for the map to embed.</source>
51
+                <target>HTML-Snippet für die einzubettende Karte.</target>
52
+            </trans-unit>
53
+            <trans-unit id="tl_vr_re_managedProperties.published.0">
54
+                <source>Publish</source>
55
+                <target>Veröffentlichen</target>
56
+            </trans-unit>
57
+            <trans-unit id="tl_vr_re_managedProperties.published.1">
58
+                <source>The property is published on the website.</source>
59
+                <target>Das Objekt wird auf der Webseite veröffentlicht.</target>
60
+            </trans-unit>
61
+            <trans-unit id="tl_vr_re_managedProperties.start.0">
62
+                <source>Publish from</source>
63
+                <target>Anzeigen ab</target>
64
+            </trans-unit>
65
+            <trans-unit id="tl_vr_re_managedProperties.start.1">
66
+                <source>From when the object should be visible. Leave empty for immediate publication.</source>
67
+                <target>Ab wann das Objekt sichtbar sein soll. Leer lassen für sofortige Veröffentlichung.</target>
68
+            </trans-unit>
69
+            <trans-unit id="tl_vr_re_managedProperties.stop.0">
70
+                <source>Publish until</source>
71
+                <target>Anzeigen bis</target>
72
+            </trans-unit>
73
+            <trans-unit id="tl_vr_re_managedProperties.stop.1">
74
+                <source>Until when the object should be visible. Leave empty for permanent publication.</source>
75
+                <target>Bis wann das Objekt sichtbar sein soll. Leer lassen für dauerhafte Veröffentlichung.</target>
76
+            </trans-unit>
77
+
78
+            <trans-unit id="tl_vr_re_managedProperties.data_legend">
79
+                <source>Basic data</source>
80
+                <target>Grunddaten</target>
81
+            </trans-unit>
82
+            <trans-unit id="tl_vr_re_managedProperties.description_legend">
83
+                <source>Description</source>
84
+                <target>Beschreibung</target>
85
+            </trans-unit>
86
+            <trans-unit id="tl_vr_re_managedProperties.map_legend">
87
+                <source>Map</source>
88
+                <target>Karte</target>
89
+            </trans-unit>
90
+            <trans-unit id="tl_vr_re_managedProperties.publish_legend">
91
+                <source>Publishing settings</source>
92
+                <target>Veröffentlichungs-Einstellungen</target>
93
+            </trans-unit>
94
+        </body>
95
+    </file>
96
+</xliff>
0 97
\ No newline at end of file
1 98
new file mode 100644
... ...
@@ -0,0 +1,72 @@
1
+{% extends '@Contao/frontend_module/_base.html.twig' %}
2
+{% import "@ContaoCore/Image/Studio/_macros.html.twig" as studio %}
3
+
4
+{% block content %}
5
+
6
+  {% block filter %}
7
+    <div class="-padding content-background-wrapper">
8
+      <div class="content-background-wrapper-bg">
9
+      </div>
10
+
11
+      <div class="content-background-wrapper-fg">
12
+        <div class="content-grid">
13
+          <div class="fragments">
14
+            <h5>{{ 'MSC.re_filter'|trans({}, 'contao_default') }}</h5>
15
+            <form class="assets-list-filter" hx-get="{{ pageUrl is defined ? pageUrl : '' }}" hx-push-url="false" hx-headers='{"VR-Ajax": "RePropertiesList"}' hx-trigger="change, submit" hx-target="closest .module-managed-property" hx-indicator="body" class="filter">
16
+              {% if filterOptions is defined and filterOptions is iterable %}
17
+                {% for key, options in filterOptions %}
18
+                  <div class="filter-wrapper">
19
+                    <select name="filter[{{ key }}]"{% if filter[key] is defined and filter[key] is not empty %} class="active"{% endif %}>
20
+                      <option value="">{{ ('REF.re_filter.' ~ key)|trans({}, 'contao_default') }}</option>
21
+                      {% for value, label in options %}
22
+                        <option value="{{ value }}"{% if filter[key] is defined and filter[key] == value %} selected{% endif %}>{{ label }}</option>
23
+                      {% endfor %}
24
+                    </select>
25
+                  </div>
26
+                {% endfor %}
27
+              {% endif %}
28
+            </form>
29
+          </div>
30
+        </div>
31
+      </div>
32
+    </div>
33
+  {% endblock %}
34
+
35
+  {% block assets %}
36
+    <div class="assetList">
37
+      {% for item in assets %}
38
+        {% block item %}
39
+          <div class="asset re_asset_card">
40
+            {% if item.teaserFigure %}
41
+              {{- studio.figure(item.teaserFigure, { attr: { class: ('image_container media') }}) -}}
42
+            {% endif %}
43
+
44
+            <div class="details">
45
+              <ul>
46
+                <li>
47
+                  <span class="label">Adresse:</span>
48
+                  {% if item.address is not empty %}{{ item.address }}<br>{% endif %}
49
+                  {% if item.postal is not empty and item.city is not empty %}{{ item.postal }} {{ item.city }}<br>{% endif %}
50
+                </li>
51
+                {% if item.livingSpace > 0 %}
52
+                  <li>
53
+                    <span class="label">Wohnfläche:</span>
54
+                    {{ item.livingSpace|number_format(2, ',', '') }} m²
55
+                  </li>
56
+                {% endif %}
57
+              </ul>
58
+              {% if item.detailsUrl %}
59
+                <p class="details-link"><a class="link" href="{{ item.detailsUrl }}">{{ 'MSC.more'|trans({}, 'contao_default') }}</a></p>
60
+              {% endif %}
61
+            </div>
62
+          </div>
63
+        {% endblock %}
64
+      {% endfor %}
65
+    </div>
66
+  {% endblock %}
67
+
68
+
69
+  {% block overlay %}
70
+    <div class="assets-list-overlay"></div>
71
+  {% endblock %}
72
+{% endblock %}
0 73
new file mode 100644
... ...
@@ -0,0 +1,75 @@
1
+{% extends '@Contao/frontend_module/_base.html.twig' %}
2
+{% use "@Contao/component/_list.html.twig" %}
3
+{% use "@Contao/component/_figure.html.twig" %}
4
+
5
+{%- macro listEntry(label, value, options = {}) -%}
6
+  {%- set class = options.class|default({}) -%}
7
+  {%- set prepend = options.prepend|default({}) %}
8
+  {% set append = options.append|default({}) -%}
9
+  {% set strong = options.strong is defined and options.strong ? true : false %}
10
+  {% apply spaceless %}
11
+    {% if label and value %}
12
+      <li class="{% if class %}{{- class -}}{% endif %}{{- strong ? ' strong' : '' -}}">
13
+        <span class="label">{{- label -}}</span>
14
+        <span class="value">{% if prepend %}{{ prepend }}{% endif %}{{- value -}}{% if append %}{{ append }}{% endif %}</span>
15
+      </li>
16
+    {% endif %}
17
+  {% endapply %}
18
+{%- endmacro -%}
19
+{%- macro backgroundWrapper_open(classes = '') -%}
20
+<div class="{{ classes ? classes ~ ' ' : '' }}content-background-wrapper">
21
+  <div class="content-background-wrapper-bg">
22
+  </div>
23
+  <div class="content-background-wrapper-fg">
24
+    <div class="content-grid">
25
+      <div class="fragments">
26
+        {%- endmacro -%}
27
+        {%- macro backgroundWrapper_close() -%}
28
+      </div>
29
+    </div>
30
+  </div>
31
+</div>
32
+{%- endmacro -%}
33
+
34
+
35
+{% block content %}
36
+  {% import "@ContaoCore/Image/Studio/_macros.html.twig" as studio %}
37
+
38
+  <div class="re_asset_expose">
39
+
40
+    {{ _self.backgroundWrapper_open('-padding-medium') }}
41
+    <p class="overviewLink"><a class="btn btn-outline-primary" href="{{ item.listUrl }}">Zurück zur Übersicht</a></p>
42
+    <h1>{% if item.city %}{{ item.city }}{% endif %}{% if item.address %} {{ item.address }}{% endif %}</h1>
43
+    {{ _self.backgroundWrapper_close }}
44
+
45
+    {{ _self.backgroundWrapper_open('-alt-color-1 -padding-medium') }}
46
+
47
+      <div class="teaser-wrapper">
48
+        {% if item.teaserFigure %}
49
+          {{- studio.figure(item.teaserFigure, { attr: { class: ('image_container media') }}) -}}
50
+        {% endif %}
51
+        {% if item.map %}
52
+          {% block map %}<div class="map-embed">{{ item.map|default|insert_tag|raw }}</div>{% endblock %}
53
+        {% endif %}
54
+      </div>
55
+
56
+      {% if item.description %}
57
+        <div class="description">{{ item.description|raw }}</div>
58
+      {% endif %}
59
+
60
+    <div class="expose-gallery">
61
+      {% with {items: item.galleryFigures} %}{{ block('list_component') }}{% endwith %}
62
+    </div>
63
+    {{ _self.backgroundWrapper_close }}
64
+
65
+    {{ _self.backgroundWrapper_open('-padding') }}
66
+    <p class="overviewLink"><a class="btn btn-outline-primary" href="{{ item.listUrl }}">Zurück zur Übersicht</a></p>
67
+    {{ _self.backgroundWrapper_close }}
68
+
69
+
70
+  </div>
71
+{% endblock %}
72
+
73
+{% block list_item %}
74
+  {% with {figure: item} %}{{ block('figure_component') }}{% endwith %}
75
+{% endblock %}
0 76
new file mode 100644
... ...
@@ -0,0 +1,217 @@
1
+<?php
2
+
3
+declare(strict_types=1);
4
+
5
+namespace vonRotenberg\RealEstateListingBundle\Controller\FrontendModule;
6
+
7
+use vonRotenberg\RealEstateListingBundle\Model\ManagedPropertyModel;
8
+use Contao\ArrayUtil;
9
+use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
10
+use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
11
+use Contao\CoreBundle\Exception\ResponseException;
12
+use Contao\CoreBundle\Image\Studio\Figure;
13
+use Contao\CoreBundle\Image\Studio\FigureBuilder;
14
+use Contao\CoreBundle\Twig\FragmentTemplate;
15
+use Contao\File;
16
+use Contao\FilesModel;
17
+use Contao\Input;
18
+use Contao\ModuleModel;
19
+use Contao\PageModel;
20
+use Contao\StringUtil;
21
+use Contao\System;
22
+use Symfony\Component\HttpFoundation\Request;
23
+use Symfony\Component\HttpFoundation\Response;
24
+
25
+#[AsFrontendModule(category: 'vr_real_estate')]
26
+class ManagedPropertyController extends AbstractFrontendModuleController
27
+{
28
+    public const TYPE = 'managed_property';
29
+    protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
30
+    {
31
+
32
+        $arrAssets = [];
33
+        $arrFilterOptions = [
34
+            'city' => []
35
+        ];
36
+        $arrFilterSelected = [];
37
+        $arrAssetsOptions = [];
38
+
39
+        $jumpTo = PageModel::findByPk($model->jumpTo);
40
+        $urlGenerator = System::getContainer()->get('contao.routing.content_url_generator');
41
+
42
+        // Filter
43
+        if (isset($_GET['filter']) && \is_array(Input::get('filter')))
44
+        {
45
+            foreach (Input::get('filter') as $filter=>$value)
46
+            {
47
+                if (in_array($filter, array_keys($arrFilterOptions)) && !empty($value))
48
+                {
49
+                    $rawValue = StringUtil::decodeEntities($value);
50
+                    $arrFilterSelected[$filter] = $rawValue;
51
+
52
+                    if (preg_match('/^.*<>.*$/',$rawValue))
53
+                    {
54
+                        list($start,$stop) = preg_split('/<>/',$rawValue, 2);
55
+                        $arrAssetsOptions['column'][$filter] = "$filter BETWEEN $start AND $stop";
56
+                    } else {
57
+                        $arrAssetsOptions['column'][$filter] = "$filter = ?";
58
+                        $arrAssetsOptions['value'][$filter] = $rawValue;
59
+                    }
60
+                }
61
+            }
62
+        }
63
+
64
+        if (($assets = ManagedPropertyModel::findAllPublished(0,0,$arrAssetsOptions)) === null)
65
+        {
66
+            return $template->getResponse();
67
+        }
68
+
69
+        // Figure Builder
70
+        $figureBuilder = System::getContainer()
71
+            ->get('contao.image.studio')
72
+            ->createFigureBuilder()
73
+            ->setSize($model->imgSize)
74
+            ->setLightboxGroupIdentifier('lb' . $model->id);
75
+
76
+        foreach ($assets as $asset)
77
+        {
78
+            $arrItem = array_merge($asset->row(), [
79
+                'teaserFigure' => $this->getImageFigures($asset->gallerySRC, $figureBuilder, $asset->orderSRC, 1),
80
+                'detailsUrl'   => $jumpTo !== null ? $urlGenerator->generate($jumpTo, ['parameters'=>'/items/'.$asset->id]) : null
81
+            ]);
82
+
83
+            $arrAssets[] = $arrItem;
84
+        }
85
+
86
+        // Populate filters
87
+        $filterAssets = ManagedPropertyModel::findAllPublished();
88
+        foreach ($filterAssets as $asset)
89
+        {
90
+            // Filter options
91
+            if (!empty($asset->city))
92
+            {
93
+                $tmpOptions = $arrAssetsOptions;
94
+                $tmpOptions['column'] = array_merge((isset($arrAssetsOptions['column']) ? $arrAssetsOptions['column'] : []),['city'=>'city = ?']);
95
+                $tmpOptions['value'] = array_merge((isset($arrAssetsOptions['value']) ? $arrAssetsOptions['value'] : []),['city'=>$asset->city]);
96
+                $count = ManagedPropertyModel::countAllPublished($tmpOptions);
97
+                $arrFilterOptions['city'][$asset->city] = $asset->city . ' ('.$count.')';
98
+            }
99
+        }
100
+
101
+        foreach (array_keys($arrFilterOptions) as $filterName)
102
+        {
103
+            $arrFilterOptions[$filterName] = array_unique($arrFilterOptions[$filterName]);
104
+        }
105
+
106
+        // Set template data
107
+        $template->set('filterOptions',$arrFilterOptions);
108
+        $template->set('filter',$arrFilterSelected);
109
+        $template->set('assets',$arrAssets);
110
+
111
+        // Handle ajax
112
+        if ($request->headers->get('VR-Ajax') == 'RePropertiesList')
113
+        {
114
+            throw new ResponseException($template->getResponse());
115
+        }
116
+
117
+        return $template->getResponse();
118
+    }
119
+
120
+    /**
121
+     * @param string|array  $strUuids
122
+     * @param FigureBuilder $figureBuilder
123
+     * @param string|array  $strOrder
124
+     *
125
+     * @return array|Figure
126
+     * @throws \Exception
127
+     */
128
+    public function getImageFigures($strUuids, $figureBuilder, $strOrder = null, int $intLimit = 0, int $intOffset = 0)
129
+    {
130
+        $arrImageFigures = [];
131
+
132
+        if (($imageFiles = FilesModel::findMultipleByUuids(StringUtil::deserialize($strUuids))) !== null)
133
+        {
134
+            $images = array();
135
+            while ($imageFiles->next())
136
+            {
137
+                // Continue if the files has been processed or does not exist
138
+                if (isset($images[$imageFiles->path]) || !file_exists(System::getContainer()->getParameter('kernel.project_dir') . '/' . $imageFiles->path))
139
+                {
140
+                    continue;
141
+                }
142
+
143
+                // Single files
144
+                if ($imageFiles->type == 'file')
145
+                {
146
+                    $objFile = new File($imageFiles->path);
147
+
148
+                    if (!$objFile->isImage)
149
+                    {
150
+                        continue;
151
+                    }
152
+
153
+                    // Add the image
154
+                    $images[$imageFiles->path] = $imageFiles->current();
155
+                } // Folders
156
+                else
157
+                {
158
+                    $objSubfiles = FilesModel::findByPid($imageFiles->uuid, array('order' => 'name'));
159
+
160
+                    if ($objSubfiles === null)
161
+                    {
162
+                        continue;
163
+                    }
164
+
165
+                    while ($objSubfiles->next())
166
+                    {
167
+                        // Skip subfolders
168
+                        if ($objSubfiles->type == 'folder')
169
+                        {
170
+                            continue;
171
+                        }
172
+
173
+                        $objFile = new File($objSubfiles->path);
174
+
175
+                        if (!$objFile->isImage)
176
+                        {
177
+                            continue;
178
+                        }
179
+
180
+                        // Add the image
181
+                        $images[$objSubfiles->path] = $objSubfiles->current();
182
+                    }
183
+                }
184
+            }
185
+
186
+            if ($strOrder !== null)
187
+            {
188
+                $images = ArrayUtil::sortByOrderField($images, $strOrder);
189
+            }
190
+            $images = array_values($images);
191
+
192
+            // Offset images
193
+            if ($intOffset > 0)
194
+            {
195
+                $images = \array_slice($images, ($intOffset < count($images) ? $intOffset : count($images)));
196
+            }
197
+
198
+            // Limit number of images
199
+            if ($intLimit > 0)
200
+            {
201
+                $images = \array_slice($images, 0, $intLimit);
202
+            }
203
+
204
+            // Build figures of images
205
+            foreach ($images as $image)
206
+            {
207
+                $figure = $figureBuilder
208
+                    ->fromFilesModel($image)
209
+                    ->build();
210
+
211
+                $arrImageFigures[] = $figure;
212
+            }
213
+
214
+        }
215
+        return count ($arrImageFigures) === 1 && $intLimit === 1 ? array_shift($arrImageFigures) : $arrImageFigures;
216
+    }
217
+}
0 218
new file mode 100644
... ...
@@ -0,0 +1,187 @@
1
+<?php
2
+
3
+declare(strict_types=1);
4
+
5
+namespace vonRotenberg\RealEstateListingBundle\Controller\FrontendModule;
6
+
7
+use vonRotenberg\RealEstateListingBundle\Model\ManagedPropertyModel;
8
+use Contao\ArrayUtil;
9
+use Contao\Controller;
10
+use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
11
+use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
12
+use Contao\CoreBundle\Exception\ResponseException;
13
+use Contao\CoreBundle\Image\Studio\Figure;
14
+use Contao\CoreBundle\Image\Studio\FigureBuilder;
15
+use Contao\CoreBundle\Twig\FragmentTemplate;
16
+use Contao\Date;
17
+use Contao\File;
18
+use Contao\FilesModel;
19
+use Contao\FrontendTemplate;
20
+use Contao\Input;
21
+use Contao\ModuleModel;
22
+use Contao\PageModel;
23
+use Contao\StringUtil;
24
+use Contao\System;
25
+use Contao\Template;
26
+use Symfony\Component\HttpFoundation\Request;
27
+use Symfony\Component\HttpFoundation\Response;
28
+use Symfony\Contracts\Translation\TranslatorInterface;
29
+use vonRotenberg\RealEstateListingBundle\Model\RealEstateAssetsModel;
30
+
31
+#[AsFrontendModule(category: 'vr_real_estate')]
32
+class ManagedPropertyReaderController extends AbstractFrontendModuleController
33
+{
34
+    public const TYPE = 'managed_property_reader';
35
+    /**
36
+     * @var RealEstateAssetsModel|null
37
+     */
38
+    protected $asset;
39
+
40
+    /**
41
+     * @var TranslatorInterface
42
+     */
43
+    protected $translator;
44
+
45
+    protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
46
+    {
47
+        $this->translator = System::getContainer()->get('translator');
48
+        Controller::loadLanguageFile(ManagedPropertyModel::getTable());
49
+
50
+        $jumpTo = PageModel::findByPk($model->jumpTo);
51
+
52
+        // Set the item from the auto_item parameter
53
+        if (!isset($_GET['items']) && \Config::get('useAutoItem') && isset($_GET['auto_item']))
54
+        {
55
+            Input::setGet('items', Input::get('auto_item'));
56
+        }
57
+
58
+        $this->asset = ManagedPropertyModel::findPublishedByIdOrAlias(Input::get('items'));
59
+
60
+        if ($this->asset === null)
61
+        {
62
+            return new Response();
63
+        }
64
+
65
+        $figureBuilder = System::getContainer()
66
+            ->get('contao.image.studio')
67
+            ->createFigureBuilder()
68
+            ->setSize($model->imgSize)
69
+            ->enableLightbox(true);
70
+//      ->setLightboxGroupIdentifier('lb' . $model->id);
71
+
72
+        $arrItem = array_merge($this->asset->row(), [
73
+            'features'          => StringUtil::deserialize($this->asset->features,true),
74
+            'availableFrom'     => ($this->asset->availability == 'immediately' ? $this->translator->trans('REF.re_availability.immediately', [], 'contao_default') : Date::parse(Date::getNumericDateFormat(), $this->asset->availableFrom)),
75
+            'deadline'          => ($this->asset->stop > 0 ? Date::parse(Date::getNumericDateFormat(), $this->asset->stop) : ''),
76
+            'teaserFigure'      => $this->getImageFigures($this->asset->gallerySRC, $figureBuilder, $this->asset->orderSRC, 1),
77
+            'galleryFigures'    => $this->getImageFigures($this->asset->gallerySRC, $figureBuilder, $this->asset->orderSRC, 0, 0),
78
+            'floorPlansFigures' => $this->getImageFigures($this->asset->floorPlansSRC, $figureBuilder, $this->asset->floorPlansOrderSRC),
79
+            'listUrl'           => $jumpTo !== null ? $jumpTo->getFrontendUrl() : null,
80
+            'pdfUrl'            => $request->getBaseUrl() . $request->getPathInfo() . '?pdf',
81
+            'qrUrl'             => $request->getUriForPath($request->getPathInfo()),
82
+            'map'               => $this->asset->mapIframe ?? ''
83
+        ]);
84
+
85
+        $template->item = $arrItem;
86
+
87
+        return $template->getResponse();
88
+    }
89
+
90
+    /**
91
+     * @param string|array  $strUuids
92
+     * @param FigureBuilder $figureBuilder
93
+     * @param string|array  $strOrder
94
+     *
95
+     * @return array|Figure
96
+     * @throws \Exception
97
+     */
98
+    public function getImageFigures($strUuids, $figureBuilder, $strOrder = null, int $intLimit = 0, int $intOffset = 0)
99
+    {
100
+        $arrImageFigures = [];
101
+
102
+        if (($imageFiles = FilesModel::findMultipleByUuids(StringUtil::deserialize($strUuids))) !== null)
103
+        {
104
+            $images = array();
105
+            while ($imageFiles->next())
106
+            {
107
+                // Continue if the files has been processed or does not exist
108
+                if (isset($images[$imageFiles->path]) || !file_exists(System::getContainer()->getParameter('kernel.project_dir') . '/' . $imageFiles->path))
109
+                {
110
+                    continue;
111
+                }
112
+
113
+                // Single files
114
+                if ($imageFiles->type == 'file')
115
+                {
116
+                    $objFile = new File($imageFiles->path);
117
+
118
+                    if (!$objFile->isImage)
119
+                    {
120
+                        continue;
121
+                    }
122
+
123
+                    // Add the image
124
+                    $images[$imageFiles->path] = $imageFiles->current();
125
+                } // Folders
126
+                else
127
+                {
128
+                    $objSubfiles = FilesModel::findByPid($imageFiles->uuid, array('order' => 'name'));
129
+
130
+                    if ($objSubfiles === null)
131
+                    {
132
+                        continue;
133
+                    }
134
+
135
+                    while ($objSubfiles->next())
136
+                    {
137
+                        // Skip subfolders
138
+                        if ($objSubfiles->type == 'folder')
139
+                        {
140
+                            continue;
141
+                        }
142
+
143
+                        $objFile = new File($objSubfiles->path);
144
+
145
+                        if (!$objFile->isImage)
146
+                        {
147
+                            continue;
148
+                        }
149
+
150
+                        // Add the image
151
+                        $images[$objSubfiles->path] = $objSubfiles->current();
152
+                    }
153
+                }
154
+            }
155
+
156
+            if ($strOrder !== null)
157
+            {
158
+                $images = ArrayUtil::sortByOrderField($images, $strOrder);
159
+            }
160
+            $images = array_values($images);
161
+
162
+            // Offset images
163
+            if ($intOffset > 0)
164
+            {
165
+                $images = \array_slice($images, ($intOffset < count($images) ? $intOffset : count($images)));
166
+            }
167
+
168
+            // Limit number of images
169
+            if ($intLimit > 0)
170
+            {
171
+                $images = \array_slice($images, 0, $intLimit);
172
+            }
173
+
174
+            // Build figures of images
175
+            foreach ($images as $image)
176
+            {
177
+                $figure = $figureBuilder
178
+                    ->fromFilesModel($image)
179
+                    ->build();
180
+
181
+                $arrImageFigures[] = $figure;
182
+            }
183
+
184
+        }
185
+        return count ($arrImageFigures) === 1 && $intLimit === 1 ? array_shift($arrImageFigures) : $arrImageFigures;
186
+    }
187
+}
0 188
new file mode 100644
... ...
@@ -0,0 +1,178 @@
1
+<?php
2
+
3
+/**
4
+ * OBG Customizations
5
+ *
6
+ * Copyright (c) 2021 vonRotenberg
7
+ *
8
+ * @license commercial
9
+ */
10
+
11
+namespace vonRotenberg\RealEstateListingBundle\Model;
12
+
13
+use Contao\Date;
14
+use Contao\Model;
15
+use Contao\Model\Collection;
16
+
17
+/**
18
+ * Reads and writes assets
19
+ *
20
+ * @property string|integer         $id
21
+ * @property string|integer         $tstamp
22
+ * @property string                 $address
23
+ * @property string                 $postal
24
+ * @property string                 $city
25
+ * @property string|null            $gallerySRC
26
+ * @property string|null            $orderSRC
27
+ * @property string|null            $description
28
+ * @property string|boolean         $published
29
+ * @property string|integer         $start
30
+ * @property string|integer         $stop
31
+ *
32
+ * @method static ManagedPropertyModel|null findById($id, array $opt = array())
33
+ * @method static ManagedPropertyModel|null findByPk($id, array $opt = array())
34
+ * @method static ManagedPropertyModel|null findOneBy($col, $val, array $opt = array())
35
+ * @method static ManagedPropertyModel|null findOneByTstamp($val, array $opt = array())
36
+ * @method static ManagedPropertyModel|null findOneByAddress($val, array $opt = array())
37
+ * @method static ManagedPropertyModel|null findOneByPostal($val, array $opt = array())
38
+ * @method static ManagedPropertyModel|null findOneByCity($val, array $opt = array())
39
+ * @method static ManagedPropertyModel|null findOneByGallerySRC($val, array $opt = array())
40
+ * @method static ManagedPropertyModel|null findOneByDescription($val, array $opt = array())
41
+ * @method static ManagedPropertyModel|null findOneByPublished($val, array $opt = array())
42
+ * @method static ManagedPropertyModel|null findOneByStart($val, array $opt = array())
43
+ * @method static ManagedPropertyModel|null findOneByStop($val, array $opt = array())
44
+ *
45
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByTstamp($val, array $opt = array())
46
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByAddress($val, array $opt = array())
47
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByPostal($val, array $opt = array())
48
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByCity($val, array $opt = array())
49
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByGallerySRC($val, array $opt = array())
50
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByDescription($val, array $opt = array())
51
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByPublished($val, array $opt = array())
52
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByStart($val, array $opt = array())
53
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findByStop($val, array $opt = array())
54
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findMultipleByIds($val, array $opt = array())
55
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findBy($col, $val, array $opt = array())
56
+ * @method static Collection|ManagedPropertyModel[]|ManagedPropertyModel|null findAll(array $opt = array())
57
+ *
58
+ * @method static integer countById($id, array $opt = array())
59
+ * @method static integer countByTstamp($val, array $opt = array())
60
+ * @method static integer countByAddress($val, array $opt = array())
61
+ * @method static integer countByPostal($val, array $opt = array())
62
+ * @method static integer countByCity($val, array $opt = array())
63
+ * @method static integer countByGallerySRC($val, array $opt = array())
64
+ * @method static integer countByDescription($val, array $opt = array())
65
+ * @method static integer countByPublished($val, array $opt = array())
66
+ * @method static integer countByStart($val, array $opt = array())
67
+ * @method static integer countByStop($val, array $opt = array())
68
+ *
69
+ * @author Benjamin Roth <https://www.vonrotenberg.de>
70
+ */
71
+class ManagedPropertyModel extends Model
72
+{
73
+
74
+    /**
75
+     * Table name
76
+     * @var string
77
+     */
78
+    protected static $strTable = 'tl_vr_re_managedProperties';
79
+
80
+    /**
81
+     * Find a published assets from one or more categories
82
+     *
83
+     * @param mixed $varId      The numeric id or alias name
84
+     * @param array $arrOptions An optional options array
85
+     *
86
+     * @return ManagedPropertyModel|null A model or null if there is no asset
87
+     */
88
+    public static function findPublishedByIdOrAlias($varId, array $arrOptions = array())
89
+    {
90
+        $t = static::$strTable;
91
+        $arrColumns = !preg_match('/^[1-9]\d*$/', $varId) ? array("BINARY $t.alias=?") : array("$t.id=?");
92
+
93
+        if (!static::isPreviewMode($arrOptions))
94
+        {
95
+            $time = Date::floorToMinute();
96
+            $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'$time')";
97
+        }
98
+
99
+        return static::findOneBy($arrColumns, $varId, $arrOptions);
100
+    }
101
+
102
+    /**
103
+     * Find a published assets from one or more categories
104
+     *
105
+     * @param array   $arrPids    An array of parent IDs
106
+     * @param integer $intLimit   An optional limit
107
+     * @param integer $intOffset  An optional offset
108
+     * @param array   $arrOptions An optional options array
109
+     *
110
+     * @return Collection|ManagedPropertyModel[]|ManagedPropertyModel|null A collection of models or null if there are no assets
111
+     */
112
+    public static function findAllPublished($intLimit = 0, $intOffset = 0, array $arrOptions = array())
113
+    {
114
+        $t = static::$strTable;
115
+        $arrColumns = [];
116
+        $arrValues = [];
117
+
118
+        if (!static::isPreviewMode($arrOptions))
119
+        {
120
+            $time = Date::floorToMinute();
121
+            $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'$time')";
122
+        }
123
+
124
+        if (!isset($arrOptions['order']))
125
+        {
126
+            $arrOptions['order'] = "$t.city ASC, $t.livingSpace ASC";
127
+        }
128
+
129
+        $arrOptions['limit'] = $intLimit;
130
+        $arrOptions['offset'] = $intOffset;
131
+
132
+        if (isset($arrOptions['column']))
133
+        {
134
+            $arrColumns = array_merge($arrColumns,array_values($arrOptions['column']));
135
+            unset($arrOptions['column']);
136
+        }
137
+        if (isset($arrOptions['value']))
138
+        {
139
+            $arrValues = array_merge($arrValues,array_values($arrOptions['value']));
140
+            unset($arrOptions['value']);
141
+        }
142
+
143
+        return static::findBy($arrColumns, $arrValues, $arrOptions);
144
+    }
145
+
146
+    /**
147
+     * Find a published assets from one or more categories
148
+     *
149
+     * @param array $arrPids    An array of parent IDs
150
+     * @param array $arrOptions An optional options array
151
+     *
152
+     * @return integer The number of matching rows
153
+     */
154
+    public static function countAllPublished(array $arrOptions = array())
155
+    {
156
+        $t = static::$strTable;
157
+        $arrColumns = [];
158
+        $arrValues = [];
159
+
160
+        if (!static::isPreviewMode($arrOptions))
161
+        {
162
+            $time = Date::floorToMinute();
163
+            $arrColumns[] = "$t.published='1' AND ($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'$time')";
164
+        }
165
+
166
+        if (isset($arrOptions['column']))
167
+        {
168
+            $arrColumns = array_merge($arrColumns,array_values($arrOptions['column']));
169
+            unset($arrOptions['column']);
170
+        }
171
+        if (isset($arrOptions['value']))
172
+        {
173
+            $arrValues = array_merge($arrValues,array_values($arrOptions['value']));
174
+            unset($arrOptions['value']);
175
+        }
176
+        return static::countBy($arrColumns, $arrValues, $arrOptions);
177
+    }
178
+}