Compare commits

..

1 Commits

Author SHA1 Message Date
Yoann Quenach de Quivillic
d02d18e600
Merge 2e312118e88840e586d33656efc45a6de3961a5c into 08dd318614d21eb557a1311775b1a1dff509d3b4 2025-06-30 13:41:45 +02:00
7 changed files with 133 additions and 101 deletions

View File

@ -979,31 +979,7 @@ if (WITH_CORE)
else() else()
# UNIX # UNIX
set (DEFAULT_BIN_SUBDIR bin) set (DEFAULT_BIN_SUBDIR bin)
set (DEFAULT_CGIBIN_SUBDIR bin)
# From https://www.cyberciti.biz/faq/how-do-i-find-the-url-for-my-cgi-bin/
execute_process(COMMAND lsb_release -a OUTPUT_VARIABLE LSB_RELEASE_A)
if(EXISTS "/etc/fedora-release")
# in /var/www/cgi-bin
set (DEFAULT_CGIBIN_SUBDIR www/cgi-bin)
elseif (${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD")
# in /usr/local/www/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR www/cgi-bin)
elseif (${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD")
# in /usr/local/libexec/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR libexec/cgi-bin)
elseif ("${LSB_RELEASE_A}" MATCHES "Ubuntu" OR "${LSB_RELEASE_A}" MATCHES "Debian" OR "${LSB_RELEASE_A}" MATCHES "Mint")
# in /usr/lib/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR lib/cgi-bin)
else()
# others: Red Hat/CentOS/Rocky/Alma Linux
# in /var/www/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR www/cgi-bin)
endif()
set (DEFAULT_LIB_SUBDIR lib${LIB_SUFFIX}) set (DEFAULT_LIB_SUBDIR lib${LIB_SUFFIX})
set (DEFAULT_DATA_SUBDIR share/qgis) set (DEFAULT_DATA_SUBDIR share/qgis)
set (DEFAULT_LIBEXEC_SUBDIR lib${LIB_SUFFIX}/qgis) set (DEFAULT_LIBEXEC_SUBDIR lib${LIB_SUFFIX}/qgis)

View File

@ -659,10 +659,7 @@ class Repositories(QObject):
.strip() .strip()
) )
if not qgisMaximumVersion: if not qgisMaximumVersion:
if qgisMinimumVersion[0] == "3" and supports_qt6: qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
qgisMaximumVersion = "4.99"
else:
qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
# if compatible, add the plugin to the list # if compatible, add the plugin to the list
if not pluginNodes.item(i).firstChildElement( if not pluginNodes.item(i).firstChildElement(
"disabled" "disabled"
@ -848,10 +845,7 @@ class Plugins(QObject):
qgisMinimumVersion = "0" qgisMinimumVersion = "0"
qgisMaximumVersion = pluginMetadata("qgisMaximumVersion").strip() qgisMaximumVersion = pluginMetadata("qgisMaximumVersion").strip()
if not qgisMaximumVersion: if not qgisMaximumVersion:
if qgisMinimumVersion[0] == "3" and supports_qt6: qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
qgisMaximumVersion = "4.99"
else:
qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
# if compatible, add the plugin to the list # if compatible, add the plugin to the list
if not isCompatible( if not isCompatible(
pyQgisVersion(), qgisMinimumVersion, qgisMaximumVersion pyQgisVersion(), qgisMinimumVersion, qgisMaximumVersion

View File

@ -727,25 +727,19 @@ bool QgsPluginRegistry::checkPythonPlugin( const QString &packageName )
bool QgsPluginRegistry::isPythonPluginCompatible( const QString &packageName ) const bool QgsPluginRegistry::isPythonPluginCompatible( const QString &packageName ) const
{ {
#ifdef WITH_BINDINGS #ifdef WITH_BINDINGS
bool supportsQgis4 = true; #if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
const QString supportsQt6 = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "supportsQt6" ) ).trimmed(); const QString supportsQt6 = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "supportsQt6" ) ).trimmed();
if ( supportsQt6.compare( QLatin1String( "YES" ), Qt::CaseInsensitive ) != 0 && supportsQt6.compare( QLatin1String( "TRUE" ), Qt::CaseInsensitive ) != 0 ) if ( supportsQt6.compare( QLatin1String( "YES" ), Qt::CaseInsensitive ) != 0 && supportsQt6.compare( QLatin1String( "TRUE" ), Qt::CaseInsensitive ) != 0 )
{ {
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
if ( !getenv( "QGIS_DISABLE_SUPPORTS_QT6_CHECK" ) ) if ( !getenv( "QGIS_DISABLE_SUPPORTS_QT6_CHECK" ) )
{ {
return false; return false;
} }
#endif
supportsQgis4 = false;
} }
#endif
const QString minVersion = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "qgisMinimumVersion" ) ); const QString minVersion = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "qgisMinimumVersion" ) );
// try to read qgisMaximumVersion. Note checkQgisVersion can cope with "__error__" value. // try to read qgisMaximumVersion. Note checkQgisVersion can cope with "__error__" value.
QString maxVersion = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "qgisMaximumVersion" ) ); const QString maxVersion = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "qgisMaximumVersion" ) );
if ( maxVersion == QLatin1String( "__error__" ) && minVersion.startsWith( QLatin1String( "3." ) ) && supportsQgis4 )
{
maxVersion = QLatin1String( "4.99.0" );
}
return minVersion != QLatin1String( "__error__" ) && checkQgisVersion( minVersion, maxVersion ); return minVersion != QLatin1String( "__error__" ) && checkQgisVersion( minVersion, maxVersion );
#else #else
Q_UNUSED( packageName ) Q_UNUSED( packageName )

View File

@ -1532,7 +1532,6 @@ void QgsLineString::visitPointsByRegularDistance( const double distance, const s
double pZ = std::numeric_limits<double>::quiet_NaN(); double pZ = std::numeric_limits<double>::quiet_NaN();
double pM = std::numeric_limits<double>::quiet_NaN(); double pM = std::numeric_limits<double>::quiet_NaN();
double nextPointDistance = distance; double nextPointDistance = distance;
const double eps = 4 * nextPointDistance * std::numeric_limits<double>::epsilon ();
for ( int i = 1; i < totalPoints; ++i ) for ( int i = 1; i < totalPoints; ++i )
{ {
double thisX = *x++; double thisX = *x++;
@ -1541,7 +1540,7 @@ void QgsLineString::visitPointsByRegularDistance( const double distance, const s
double thisM = m ? *m++ : 0.0; double thisM = m ? *m++ : 0.0;
const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY ); const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength, eps ) ) while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength ) )
{ {
// point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength ); const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );

View File

@ -116,6 +116,21 @@ QStringList makeKeyTokens_( const QString &scope, const QString &key )
// be sure to include the canonical root node // be sure to include the canonical root node
keyTokens.push_front( QStringLiteral( "properties" ) ); keyTokens.push_front( QStringLiteral( "properties" ) );
//check validy of keys since an invalid xml name will will be dropped upon saving the xml file. If not valid, we print a message to the console.
for ( int i = 0; i < keyTokens.size(); ++i )
{
const QString keyToken = keyTokens.at( i );
//invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
//note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
const thread_local QRegularExpression sInvalidRegexp = QRegularExpression( QStringLiteral( "([^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\-\\.0-9\\x{B7}\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}]|^[^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}])" ) );
if ( keyToken.contains( sInvalidRegexp ) )
{
const QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
QgsMessageLog::logMessage( errorString, QString(), Qgis::MessageLevel::Critical );
}
}
return keyTokens; return keyTokens;
} }
@ -1307,20 +1322,20 @@ void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
* scope. "layers" is a list containing three string values. * scope. "layers" is a list containing three string values.
* *
* \code{.xml} * \code{.xml}
* <properties name="properties"> * <properties>
* <properties name="fsplugin"> * <fsplugin>
* <properties name="foo" type="int" >42</properties> * <foo type="int" >42</foo>
* <properties name="baz" type="int" >1</properties> * <baz type="int" >1</baz>
* <properties name="layers" type="QStringList"> * <layers type="QStringList" >
* <value>railroad</value> * <value>railroad</value>
* <value>airport</value> * <value>airport</value>
* </properties> * </layers>
* <properties name="xyqzzy" type="int" >1</properties> * <xyqzzy type="int" >1</xyqzzy>
* <properties name="bar" type="double" >123.456</properties> * <bar type="double" >123.456</bar>
* <properties name="feature_types" type="QStringList"> * <feature_types type="QStringList" >
* <value>type</value> * <value>type</value>
* </properties> * </feature_types>
* </properties> * </fsplugin>
* </properties> * </properties>
* \endcode * \endcode
* *
@ -3977,25 +3992,10 @@ bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &pro
const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) ); const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
if ( !propertiesElem.isNull() ) if ( !propertiesElem.isNull() )
{ {
QDomElement e = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ); const QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
if ( e.isNull() ) if ( !absElem.isNull() )
{ {
e = propertiesElem.firstChildElement( QStringLiteral( "properties" ) ); useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
while ( !e.isNull() && e.attribute( QStringLiteral( "name" ) ) != QStringLiteral( "Paths" ) )
e = e.nextSiblingElement( QStringLiteral( "properties" ) );
e = e.firstChildElement( QStringLiteral( "properties" ) );
while ( !e.isNull() && e.attribute( QStringLiteral( "name" ) ) != QStringLiteral( "Absolute" ) )
e = e.nextSiblingElement( QStringLiteral( "properties" ) );
}
else
{
e = e.firstChildElement( QStringLiteral( "Absolute" ) );
}
if ( !e.isNull() )
{
useAbsolutePaths = e.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
} }
} }

View File

@ -233,15 +233,15 @@ bool QgsProjectPropertyValue::readXml( const QDomNode &keyNode )
// keyElement is created by parent QgsProjectPropertyKey // keyElement is created by parent QgsProjectPropertyKey
bool QgsProjectPropertyValue::writeXml( QString const &nodeName, bool QgsProjectPropertyValue::writeXml( QString const &nodeName,
QDomElement &keyElement, QDomElement &keyElement,
QDomDocument &document ) QDomDocument &document )
{ {
QDomElement valueElement = document.createElement( QStringLiteral( "properties" ) ); QDomElement valueElement = document.createElement( nodeName );
// remember the type so that we can rebuild it when the project is read in // remember the type so that we can rebuild it when the project is read in
valueElement.setAttribute( QStringLiteral( "name" ), nodeName );
valueElement.setAttribute( QStringLiteral( "type" ), mValue.typeName() ); valueElement.setAttribute( QStringLiteral( "type" ), mValue.typeName() );
// we handle string lists differently from other types in that we // we handle string lists differently from other types in that we
// create a sequence of repeated elements to cover all the string list // create a sequence of repeated elements to cover all the string list
// members; each value will be in a <value></value> tag. // members; each value will be in a <value></value> tag.
@ -362,41 +362,33 @@ bool QgsProjectPropertyKey::readXml( const QDomNode &keyNode )
while ( i < subkeys.count() ) while ( i < subkeys.count() )
{ {
const QDomNode subkey = subkeys.item( i );
QString name;
if ( subkey.nodeName() == QStringLiteral( "properties" ) &&
subkey.hasAttributes() && // if we have attributes
subkey.isElement() && // and we're an element
subkey.toElement().hasAttribute( QStringLiteral( "name" ) ) ) // and we have a "name" attribute
name = subkey.toElement().attribute( QStringLiteral( "name" ) );
else
name = subkey.nodeName();
// if the current node is an element that has a "type" attribute, // if the current node is an element that has a "type" attribute,
// then we know it's a leaf node; i.e., a subkey _value_, and not // then we know it's a leaf node; i.e., a subkey _value_, and not
// a subkey // a subkey
if ( subkey.hasAttributes() && // if we have attributes if ( subkeys.item( i ).hasAttributes() && // if we have attributes
subkey.isElement() && // and we're an element subkeys.item( i ).isElement() && // and we're an element
subkey.toElement().hasAttribute( QStringLiteral( "type" ) ) ) // and we have a "type" attribute subkeys.item( i ).toElement().hasAttribute( QStringLiteral( "type" ) ) ) // and we have a "type" attribute
{ {
// then we're a key value // then we're a key value
// delete mProperties.take( subkeys.item( i ).nodeName() );
delete mProperties.take( name ); mProperties.insert( subkeys.item( i ).nodeName(), new QgsProjectPropertyValue );
mProperties.insert( name, new QgsProjectPropertyValue );
if ( !mProperties[name]->readXml( subkey ) ) QDomNode subkey = subkeys.item( i );
if ( !mProperties[subkeys.item( i ).nodeName()]->readXml( subkey ) )
{ {
QgsDebugError( QStringLiteral( "unable to parse key value %1" ).arg( name ) ); QgsDebugError( QStringLiteral( "unable to parse key value %1" ).arg( subkeys.item( i ).nodeName() ) );
} }
} }
else // otherwise it's a subkey, so just recurse on down the remaining keys else // otherwise it's a subkey, so just recurse on down the remaining keys
{ {
addKey( name ); addKey( subkeys.item( i ).nodeName() );
if ( !mProperties[name]->readXml( subkey ) ) QDomNode subkey = subkeys.item( i );
if ( !mProperties[subkeys.item( i ).nodeName()]->readXml( subkey ) )
{ {
QgsDebugError( QStringLiteral( "unable to parse subkey %1" ).arg( name ) ); QgsDebugError( QStringLiteral( "unable to parse subkey %1" ).arg( subkeys.item( i ).nodeName() ) );
} }
} }
@ -416,8 +408,7 @@ bool QgsProjectPropertyKey::writeXml( QString const &nodeName, QDomElement &elem
// If it's an _empty_ node (i.e., one with no properties) we need to emit // If it's an _empty_ node (i.e., one with no properties) we need to emit
// an empty place holder; else create new Dom elements as necessary. // an empty place holder; else create new Dom elements as necessary.
QDomElement keyElement = document.createElement( "properties" ); // Dom element for this property key QDomElement keyElement = document.createElement( nodeName ); // Dom element for this property key
keyElement.toElement().setAttribute( QStringLiteral( "name" ), nodeName );
if ( ! mProperties.isEmpty() ) if ( ! mProperties.isEmpty() )
{ {

View File

@ -65,6 +65,84 @@ class TestQgsProject(QgisTestCase):
QgisTestCase.__init__(self, methodName) QgisTestCase.__init__(self, methodName)
self.messageCaught = False self.messageCaught = False
def test_makeKeyTokens_(self):
# see http://www.w3.org/TR/REC-xml/#d0e804 for a list of valid characters
invalidTokens = []
validTokens = []
# all test tokens will be generated by prepending or inserting characters to this token
validBase = "valid"
# some invalid characters, not allowed anywhere in a token
# note that '/' must not be added here because it is taken as a separator by makeKeyTokens_()
invalidChars = "+*,;<>|!$%()=?#\x01"
# generate the characters that are allowed at the start of a token (and at every other position)
validStartChars = ":_"
charRanges = [
(ord("a"), ord("z")),
(ord("A"), ord("Z")),
(0x00F8, 0x02FF),
(0x0370, 0x037D),
(0x037F, 0x1FFF),
(0x200C, 0x200D),
(0x2070, 0x218F),
(0x2C00, 0x2FEF),
(0x3001, 0xD7FF),
(0xF900, 0xFDCF),
(0xFDF0, 0xFFFD),
# (0x10000, 0xEFFFF), while actually valid, these are not yet accepted by makeKeyTokens_()
]
for r in charRanges:
for c in range(r[0], r[1]):
validStartChars += chr(c)
# generate the characters that are only allowed inside a token, not at the start
validInlineChars = "-.\xB7"
charRanges = [
(ord("0"), ord("9")),
(0x0300, 0x036F),
(0x203F, 0x2040),
]
for r in charRanges:
for c in range(r[0], r[1]):
validInlineChars += chr(c)
# test forbidden start characters
for c in invalidChars + validInlineChars:
invalidTokens.append(c + validBase)
# test forbidden inline characters
for c in invalidChars:
invalidTokens.append(validBase[:4] + c + validBase[4:])
# test each allowed start character
for c in validStartChars:
validTokens.append(c + validBase)
# test each allowed inline character
for c in validInlineChars:
validTokens.append(validBase[:4] + c + validBase[4:])
logger = QgsApplication.messageLog()
logger.messageReceived.connect(self.catchMessage)
prj = QgsProject.instance()
for token in validTokens:
self.messageCaught = False
prj.readEntry("test", token)
myMessage = f"valid token '{token}' not accepted"
assert not self.messageCaught, myMessage
for token in invalidTokens:
self.messageCaught = False
prj.readEntry("test", token)
myMessage = f"invalid token '{token}' accepted"
assert self.messageCaught, myMessage
logger.messageReceived.disconnect(self.catchMessage)
def catchMessage(self): def catchMessage(self):
self.messageCaught = True self.messageCaught = True