1515
1616use Lizmap \App \WktTools ;
1717use Lizmap \Project \Project ;
18+ use Lizmap \Project \Qgis ;
1819use Lizmap \Project \UnknownLizmapProjectException ;
1920
2021/**
@@ -695,6 +696,10 @@ protected function gfiVectorXmlToHtml($layerId, $layerName, $layerTitle, $layer,
695696
696697 $ remoteStorageProfile = RemoteStorageRequest::getProfile ('webdav ' );
697698
699+ // Fields configured in QGIS with a CheckBox edit widget: rendered as
700+ // actual checkboxes in the auto popup, mirroring the editing form.
701+ $ checkBoxFields = $ this ->getCheckBoxFieldsForLayer ($ layerId );
702+
698703 // Get the template for the popup content
699704 $ templateConfigured = false ;
700705 $ popupTemplate = '' ;
@@ -754,6 +759,7 @@ protected function gfiVectorXmlToHtml($layerId, $layerName, $layerTitle, $layer,
754759 'featureId ' => $ id ,
755760 'attributes ' => $ feature ->Attribute ,
756761 'remoteStorageProfile ' => $ remoteStorageProfile ,
762+ 'checkBoxFields ' => $ checkBoxFields ,
757763 ));
758764 $ autoContent = $ popupFeatureContent ;
759765 // Get specific template for the layer has been configured
@@ -852,7 +858,7 @@ protected function gfiVectorXmlToHtml($layerId, $layerName, $layerTitle, $layer,
852858 $ finalContent = $ autoContent ;
853859 if (property_exists ($ configLayer , 'popupSource ' )) {
854860 if (in_array ($ configLayer ->popupSource , array ('qgis ' , 'form ' )) && $ maptipValue ) {
855- $ finalContent = $ maptipValue ;
861+ $ finalContent = $ this -> applyCheckBoxesToFormPopup ( $ maptipValue, $ checkBoxFields ) ;
856862 }
857863 if ($ configLayer ->popupSource == 'lizmap ' && $ templateConfigured ) {
858864 $ finalContent = $ lizmapContent ;
@@ -875,6 +881,7 @@ protected function gfiVectorXmlToHtml($layerId, $layerName, $layerTitle, $layer,
875881 'allFeatureAttributes ' => array_reverse ($ allFeatureAttributes ),
876882 'remoteStorageProfile ' => $ remoteStorageProfile ,
877883 'allFeatureToolbars ' => array_reverse ($ allFeatureToolbars ),
884+ 'checkBoxFields ' => $ checkBoxFields ,
878885 ));
879886 }
880887
@@ -1369,4 +1376,136 @@ protected function getMapData($project, $params, $forced = false)
13691376
13701377 return new OGCResponse ($ code , $ mime , $ data , $ cached );
13711378 }
1379+
1380+ /**
1381+ * Build a map of fields configured with a CheckBox edit widget for the
1382+ * given layer, using the QGIS project's typed XML info (cached per request
1383+ * by ProjectInfo::fromQgisPath).
1384+ *
1385+ * @param string $layerId
1386+ *
1387+ * @return array<string, array{CheckedState: string, UncheckedState: string}>
1388+ */
1389+ private function getCheckBoxFieldsForLayer ($ layerId )
1390+ {
1391+ $ checkBoxFields = array ();
1392+
1393+ $ qgisPath = $ this ->project ->getQgisPath ();
1394+ if (!$ qgisPath || !file_exists ($ qgisPath )) {
1395+ return $ checkBoxFields ;
1396+ }
1397+
1398+ try {
1399+ $ projectInfo = Qgis \ProjectInfo::fromQgisPath ($ qgisPath );
1400+ } catch (\Exception $ e ) {
1401+ return $ checkBoxFields ;
1402+ }
1403+
1404+ $ xmlLayer = $ projectInfo ->getLayerById ($ layerId );
1405+ if (!$ xmlLayer instanceof Qgis \Layer \VectorLayer) {
1406+ return $ checkBoxFields ;
1407+ }
1408+
1409+ foreach ($ xmlLayer ->fieldConfiguration as $ field ) {
1410+ $ editWidget = $ field ->editWidget ;
1411+ if (strtolower ($ editWidget ->type ) !== 'checkbox ' ) {
1412+ continue ;
1413+ }
1414+ $ config = $ editWidget ->config ;
1415+ if (!$ config instanceof Qgis \BaseQgisObject) {
1416+ continue ;
1417+ }
1418+ $ data = $ config ->getData ();
1419+ $ checked = array_key_exists ('CheckedState ' , $ data ) ? (string ) $ data ['CheckedState ' ] : '' ;
1420+ $ unchecked = array_key_exists ('UncheckedState ' , $ data ) ? (string ) $ data ['UncheckedState ' ] : '' ;
1421+ // Fall back to QGIS defaults (see QgisFormControl::fillCheckboxValues)
1422+ $ checkBoxFields [(string ) $ field ->name ] = array (
1423+ 'CheckedState ' => $ checked === '' ? 't ' : $ checked ,
1424+ 'UncheckedState ' => $ unchecked === '' ? 'f ' : $ unchecked ,
1425+ );
1426+ }
1427+
1428+ return $ checkBoxFields ;
1429+ }
1430+
1431+ /**
1432+ * Replace raw CheckBox-widget values in the QGIS drag-and-drop form popup
1433+ * HTML with disabled <input type="checkbox"> elements, mirroring the
1434+ * editing form. Leaves non-matching values untouched.
1435+ *
1436+ * QGIS Server emits each field in the form popup as:
1437+ * <span id="dd_jforms_view_edition_FIELDNAME" class="jforms-control-input">VALUE</span>
1438+ *
1439+ * @param string $maptipHtml form popup HTML returned by QGIS Server
1440+ * @param array $checkBoxFields map fieldName => ['CheckedState' => string, 'UncheckedState' => string]
1441+ *
1442+ * @return string
1443+ */
1444+ private function applyCheckBoxesToFormPopup ($ maptipHtml , $ checkBoxFields )
1445+ {
1446+ if (!is_string ($ maptipHtml ) || $ maptipHtml === '' || !is_array ($ checkBoxFields ) || count ($ checkBoxFields ) === 0 ) {
1447+ return $ maptipHtml ;
1448+ }
1449+
1450+ return preg_replace_callback (
1451+ '/(<span\s+id="dd_jforms_view_edition_([^"]+)"\s+class="jforms-control-input"\s*>)(.*?)(<\/span>)/us ' ,
1452+ function ($ m ) use ($ checkBoxFields ) {
1453+ $ fieldName = html_entity_decode ($ m [2 ], ENT_QUOTES , 'UTF-8 ' );
1454+ if (!isset ($ checkBoxFields [$ fieldName ])) {
1455+ return $ m [0 ];
1456+ }
1457+ $ value = trim (html_entity_decode ($ m [3 ], ENT_QUOTES , 'UTF-8 ' ));
1458+ $ cfg = $ checkBoxFields [$ fieldName ];
1459+ $ state = self ::matchCheckBoxState (
1460+ $ value ,
1461+ isset ($ cfg ['CheckedState ' ]) ? (string ) $ cfg ['CheckedState ' ] : '' ,
1462+ isset ($ cfg ['UncheckedState ' ]) ? (string ) $ cfg ['UncheckedState ' ] : ''
1463+ );
1464+ if ($ state === 'checked ' ) {
1465+ return $ m [1 ].'<input type="checkbox" disabled="disabled" checked="checked" class="lizmap-popup-checkbox-widget"> ' .$ m [4 ];
1466+ }
1467+ if ($ state === 'unchecked ' ) {
1468+ return $ m [1 ].'<input type="checkbox" disabled="disabled" class="lizmap-popup-checkbox-widget"> ' .$ m [4 ];
1469+ }
1470+
1471+ return $ m [0 ];
1472+ },
1473+ $ maptipHtml
1474+ );
1475+ }
1476+
1477+ /**
1478+ * Match a raw attribute value against CheckBox widget states. Tries the
1479+ * configured CheckedState/UncheckedState first, then falls back to common
1480+ * boolean representations so that fields stored as boolean (which come
1481+ * through WMS/WFS as 'true'/'false') still render as checkboxes.
1482+ *
1483+ * @param string $value raw attribute value
1484+ * @param string $checkedExpected CheckedState configured in QGIS
1485+ * @param string $uncheckedExpected UncheckedState configured in QGIS
1486+ *
1487+ * @return null|string 'checked', 'unchecked', or null for no match
1488+ */
1489+ private static function matchCheckBoxState ($ value , $ checkedExpected , $ uncheckedExpected )
1490+ {
1491+ if ($ checkedExpected !== '' && $ value === $ checkedExpected ) {
1492+ return 'checked ' ;
1493+ }
1494+ if ($ uncheckedExpected !== '' && $ value === $ uncheckedExpected ) {
1495+ return 'unchecked ' ;
1496+ }
1497+ $ normalized = strtolower (trim ($ value ));
1498+ if (in_array ($ normalized , array ('true ' , 't ' , '1 ' , 'yes ' , 'on ' ), true )) {
1499+ return 'checked ' ;
1500+ }
1501+ if (in_array ($ normalized , array ('false ' , 'f ' , '0 ' , 'no ' , 'off ' ), true )) {
1502+ return 'unchecked ' ;
1503+ }
1504+ // Treat null-like values (NULL, empty, QGIS's "()" for NULL boolean) as unchecked
1505+ if (in_array ($ normalized , array ('' , 'null ' , '() ' ), true )) {
1506+ return 'unchecked ' ;
1507+ }
1508+
1509+ return null ;
1510+ }
13721511}
0 commit comments