19 #include <QDesktopServices>
24 #include <QTemporaryDir>
25 #include <QNetworkRequest>
26 #include <QJsonDocument>
27 #include <QHttpMultiPart>
28 #include <QMimeDatabase>
50 #elif defined(Q_OS_MAC)
68 void QgsAction::handleFormSubmitAction(
const QString &expandedAction )
const
71 QUrl url{ expandedAction };
74 const QString payload { url.query( QUrl::ComponentFormattingOption::FullyEncoded ).replace( QChar(
'+' ), QStringLiteral(
"%2B" ) ) };
77 const QUrlQuery queryString { url.query( ) };
78 url.setQuery( QString( ) );
80 QNetworkRequest req { url };
84 if ( url.toString().contains( QLatin1String(
"fake_qgis_http_endpoint" ) ) )
86 req.setUrl( QStringLiteral(
"file://%1" ).arg( url.path() ) );
89 QNetworkReply *reply =
nullptr;
93 QString contentType { QStringLiteral(
"application/x-www-form-urlencoded" ) };
95 QJsonParseError jsonError;
96 QJsonDocument::fromJson( payload.toUtf8(), &jsonError );
97 if ( jsonError.error == QJsonParseError::ParseError::NoError )
99 contentType = QStringLiteral(
"application/json" );
101 req.setHeader( QNetworkRequest::KnownHeaders::ContentTypeHeader, contentType );
107 QHttpMultiPart *multiPart =
new QHttpMultiPart( QHttpMultiPart::FormDataType );
108 const QList<QPair<QString, QString>> queryItems { queryString.queryItems( QUrl::ComponentFormattingOption::FullyDecoded ) };
109 for (
const QPair<QString, QString> &queryItem : std::as_const( queryItems ) )
112 part.setHeader( QNetworkRequest::ContentDispositionHeader,
113 QStringLiteral(
"form-data; name=\"%1\"" )
114 .arg( QString( queryItem.first ).replace(
'"', QLatin1String( R
"(\")" ) ) ) );
115 part.setBody( queryItem.second.toUtf8() );
116 multiPart->append( part );
119 multiPart->setParent( reply );
122 QObject::connect( reply, &QNetworkReply::finished, reply, [ reply ]
124 if ( reply->error() == QNetworkReply::NoError )
127 if ( reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).isNull() )
130 const QByteArray replyData = reply->readAll();
132 QString filename {
"download.bin" };
133 if ( const std::string header = reply->header( QNetworkRequest::KnownHeaders::ContentDispositionHeader ).toString().toStdString(); ! header.empty() )
137 const std::string q1 { R
"(filename=")" };
138 if ( const unsigned long pos = header.find( q1 ); pos != std::string::npos )
140 const unsigned long len = pos + q1.size();
142 const std::string q2 { R"(")" };
143 if ( unsigned long pos = header.find( q2, len ); pos != std::string::npos )
145 bool escaped = false;
146 while ( pos != std::string::npos && header[pos - 1] == '\\' )
148 pos = header.find( q2, pos + 1 );
151 ascii = header.substr( len, pos - len );
155 for ( size_t i = 0; i < ascii.size(); ++i )
157 if ( ascii[i] ==
'\\' )
159 if ( i > 0 && ascii[i - 1] ==
'\\' )
161 cleaned.push_back( ascii[i] );
166 cleaned.push_back( ascii[i] );
176 const std::string u { R
"(UTF-8'')" };
177 if ( const unsigned long pos = header.find( u ); pos != std::string::npos )
179 utf8 = header.substr( pos + u.size() );
185 if ( ! utf8.empty( ) )
187 filename = QString::fromStdString( utf8 );
192 filename = QString::fromStdString( ascii );
195 else if ( !reply->header( QNetworkRequest::KnownHeaders::ContentTypeHeader ).isNull() )
197 QString contentTypeHeader { reply->header( QNetworkRequest::KnownHeaders::ContentTypeHeader ).toString() };
199 if ( contentTypeHeader.contains(
';' ) )
201 contentTypeHeader = contentTypeHeader.left( contentTypeHeader.indexOf(
';' ) );
204 QMimeType mimeType { QMimeDatabase().mimeTypeForName( contentTypeHeader ) };
205 if ( mimeType.isValid() )
207 filename = QStringLiteral(
"download.%1" ).arg( mimeType.preferredSuffix() );
211 QTemporaryDir tempDir;
212 tempDir.setAutoRemove( false );
214 const QString tempFilePath{ tempDir.path() + QDir::separator() + filename };
215 QFile tempFile{ tempFilePath };
216 tempFile.open( QIODevice::WriteOnly );
217 tempFile.write( replyData );
219 QDesktopServices::openUrl( QUrl::fromLocalFile( tempFilePath ) );
223 QgsMessageLog::logMessage( QObject::tr(
"Redirect is not supported!" ), QStringLiteral(
"Form Submit Action" ), Qgis::MessageLevel::Critical );
230 reply->deleteLater();
239 QgsDebugMsg( QStringLiteral(
"Invalid action cannot be run" ) );
251 const QFileInfo finfo( expandedAction );
252 if ( finfo.exists() && finfo.isFile() )
253 QDesktopServices::openUrl( QUrl::fromLocalFile( expandedAction ) );
255 QDesktopServices::openUrl( QUrl( expandedAction, QUrl::TolerantMode ) );
259 handleFormSubmitAction( expandedAction );
277 return mActionScopes;
287 QDomElement actionElement = actionNode.toElement();
288 const QDomNodeList actionScopeNodes = actionElement.elementsByTagName( QStringLiteral(
"actionScope" ) );
290 if ( actionScopeNodes.isEmpty() )
293 << QStringLiteral(
"Canvas" )
294 << QStringLiteral(
"Field" )
295 << QStringLiteral(
"Feature" );
299 for (
int j = 0; j < actionScopeNodes.length(); ++j )
301 const QDomElement actionScopeElem = actionScopeNodes.item( j ).toElement();
302 mActionScopes << actionScopeElem.attribute( QStringLiteral(
"id" ) );
306 mType =
static_cast< QgsAction::ActionType >( actionElement.attributeNode( QStringLiteral(
"type" ) ).value().toInt() );
307 mDescription = actionElement.attributeNode( QStringLiteral(
"name" ) ).value();
308 mCommand = actionElement.attributeNode( QStringLiteral(
"action" ) ).value();
309 mIcon = actionElement.attributeNode( QStringLiteral(
"icon" ) ).value();
310 mCaptureOutput = actionElement.attributeNode( QStringLiteral(
"capture" ) ).value().toInt() != 0;
311 mShortTitle = actionElement.attributeNode( QStringLiteral(
"shortTitle" ) ).value();
312 mNotificationMessage = actionElement.attributeNode( QStringLiteral(
"notificationMessage" ) ).value();
313 mIsEnabledOnlyWhenEditable = actionElement.attributeNode( QStringLiteral(
"isEnabledOnlyWhenEditable" ) ).value().toInt() != 0;
314 mId = QUuid( actionElement.attributeNode( QStringLiteral(
"id" ) ).value() );
316 mId = QUuid::createUuid();
321 QDomElement actionSetting = actionsNode.ownerDocument().createElement( QStringLiteral(
"actionsetting" ) );
322 actionSetting.setAttribute( QStringLiteral(
"type" ), mType );
323 actionSetting.setAttribute( QStringLiteral(
"name" ), mDescription );
324 actionSetting.setAttribute( QStringLiteral(
"shortTitle" ), mShortTitle );
325 actionSetting.setAttribute( QStringLiteral(
"icon" ), mIcon );
326 actionSetting.setAttribute( QStringLiteral(
"action" ), mCommand );
327 actionSetting.setAttribute( QStringLiteral(
"capture" ), mCaptureOutput );
328 actionSetting.setAttribute( QStringLiteral(
"notificationMessage" ), mNotificationMessage );
329 actionSetting.setAttribute( QStringLiteral(
"isEnabledOnlyWhenEditable" ), mIsEnabledOnlyWhenEditable );
330 actionSetting.setAttribute( QStringLiteral(
"id" ), mId.toString() );
332 const auto constMActionScopes = mActionScopes;
333 for (
const QString &scope : constMActionScopes )
335 QDomElement actionScopeElem = actionsNode.ownerDocument().createElement( QStringLiteral(
"actionScope" ) );
336 actionScopeElem.setAttribute( QStringLiteral(
"id" ), scope );
337 actionSetting.appendChild( actionScopeElem );
340 actionsNode.appendChild( actionSetting );
345 mExpressionContextScope = scope;
350 return mExpressionContextScope;
360 typeText = QObject::tr(
"Generic" );
365 typeText = QObject::tr(
"Generic Python" );
370 typeText = QObject::tr(
"Mac" );
375 typeText = QObject::tr(
"Windows" );
380 typeText = QObject::tr(
"Unix" );
385 typeText = QObject::tr(
"Open URL" );
390 typeText = QObject::tr(
"Submit URL (urlencoded or JSON)" );
395 typeText = QObject::tr(
"Submit URL (multipart)" );
399 return { QObject::tr( R
"html(
400 <h2>Action Details</h2>
402 <b>Description:</b> %1<br>
403 <b>Short title:</b> %2<br>
409 )html" ).arg( mDescription, mShortTitle, typeText, actionScopes().values().join( QLatin1String( ", " ) ), mCommand )};
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
void readXml(const QDomNode &actionNode)
Reads an XML definition from actionNode into this object.
void run(QgsVectorLayer *layer, const QgsFeature &feature, const QgsExpressionContext &expressionContext) const
Run this action.
bool runable() const
Checks if the action is runable on the current platform.
bool isValid() const
Returns true if this action was a default constructed one.
void setExpressionContextScope(const QgsExpressionContextScope &scope)
Sets an expression context scope to use for running the action.
QString html() const
Returns an HTML table with the basic information about this action.
void writeXml(QDomNode &actionsNode) const
Appends an XML definition for this action as a new child node to actionsNode.
QgsExpressionContextScope expressionContextScope() const
Returns an expression context scope used for running the action.
void setActionScopes(const QSet< QString > &actionScopes)
The action scopes define where an action will be available.
@ SubmitUrlEncoded
POST data to an URL, using "application/x-www-form-urlencoded" or "application/json" if the body is v...
@ SubmitUrlMultipart
POST data to an URL using "multipart/form-data".
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static QgsRunProcess * create(const QString &action, bool capture)
Represents a vector layer which manages a vector based data sets.