29#include <QApplication>
30#include <QDesktopServices>
33#include <QHttpMultiPart>
34#include <QJsonDocument>
35#include <QMimeDatabase>
36#include <QNetworkRequest>
38#include <QTemporaryDir>
42using namespace Qt::StringLiterals;
63#elif defined(Q_OS_MAC)
91void QgsAction::handleFormSubmitAction(
const QString &expandedAction )
const
95 QApplication::setOverrideCursor( Qt::WaitCursor );
97 QUrl url{ expandedAction };
100 const QString payload { url.query( QUrl::ComponentFormattingOption::FullyEncoded ).replace( QChar(
'+' ), u
"%2B"_s ) };
103 const QUrlQuery queryString { url.query( ) };
104 url.setQuery( QString( ) );
106 QNetworkRequest req { url };
110 if ( url.toString().contains(
"fake_qgis_http_endpoint"_L1 ) )
112 req.setUrl( u
"file://%1"_s.arg( url.path() ) );
115 QNetworkReply *reply =
nullptr;
119 QString contentType { u
"application/x-www-form-urlencoded"_s };
121 QJsonParseError jsonError;
122 QJsonDocument::fromJson( payload.toUtf8(), &jsonError );
123 if ( jsonError.error == QJsonParseError::ParseError::NoError )
125 contentType = u
"application/json"_s;
127 req.setHeader( QNetworkRequest::KnownHeaders::ContentTypeHeader, contentType );
133 QHttpMultiPart *multiPart =
new QHttpMultiPart( QHttpMultiPart::FormDataType );
134 const QList<QPair<QString, QString>> queryItems { queryString.queryItems( QUrl::ComponentFormattingOption::FullyDecoded ) };
135 for (
const QPair<QString, QString> &queryItem : std::as_const( queryItems ) )
138 part.setHeader( QNetworkRequest::ContentDispositionHeader,
139 u
"form-data; name=\"%1\""_s
140 .arg( QString( queryItem.first ).replace(
'"', R
"(\")"_L1 ) ) );
141 part.setBody( queryItem.second.toUtf8() );
142 multiPart->append( part );
145 multiPart->setParent( reply );
148 QObject::connect( reply, &QNetworkReply::finished, reply, [ reply ]
150 if ( reply->error() == QNetworkReply::NoError )
156 const QByteArray replyData = reply->readAll();
158 QString filename {
"download.bin" };
159 if (
const std::string header = reply->header( QNetworkRequest::KnownHeaders::ContentDispositionHeader ).toString().toStdString(); ! header.empty() )
166 const std::string q1 { R
"(filename=)" };
168 if (
size_t pos = header.find( q1 ); pos != std::string::npos )
172 if ( header.find( R
"(filename=")" ) != std::string::npos )
177 const size_t len = pos + q1.size();
179 const std::string q2 { R
"(")" };
180 if (
size_t pos = header.find( q2, len ); pos != std::string::npos )
182 bool escaped =
false;
183 while ( pos != std::string::npos && header[pos - 1] ==
'\\' )
185 pos = header.find( q2, pos + 1 );
188 ascii = header.substr( len, pos - len );
192 for (
size_t i = 0; i < ascii.size(); ++i )
194 if ( ascii[i] ==
'\\' )
196 if ( i > 0 && ascii[i - 1] ==
'\\' )
198 cleaned.push_back( ascii[i] );
203 cleaned.push_back( ascii[i] );
206 ascii = std::move( cleaned );
213 const std::string u { R
"(UTF-8'')" };
214 if (
const size_t pos = header.find( u ); pos != std::string::npos )
216 utf8 = header.substr( pos + u.size() );
222 if ( ! utf8.empty( ) )
224 filename = QString::fromStdString( utf8 );
229 filename = QString::fromStdString( ascii );
234 QString contentTypeHeader { reply->header( QNetworkRequest::KnownHeaders::ContentTypeHeader ).toString() };
236 if ( contentTypeHeader.contains(
';' ) )
238 contentTypeHeader = contentTypeHeader.left( contentTypeHeader.indexOf(
';' ) );
241 QMimeType mimeType { QMimeDatabase().mimeTypeForName( contentTypeHeader ) };
242 if ( mimeType.isValid() )
244 filename = u
"download.%1"_s.arg( mimeType.preferredSuffix() );
248 QTemporaryDir tempDir;
249 tempDir.setAutoRemove(
false );
251 const QString tempFilePath{ tempDir.path() + QDir::separator() + filename };
252 QFile tempFile{ tempFilePath };
253 if ( tempFile.open( QIODevice::WriteOnly ) )
255 tempFile.write( replyData );
257 QDesktopServices::openUrl( QUrl::fromLocalFile( tempFilePath ) );
273 reply->deleteLater();
274 QApplication::restoreOverrideCursor( );
281 mCommand = newCommand;
297 QApplication::setOverrideCursor( Qt::WaitCursor );
299 QApplication::restoreOverrideCursor();
303 const QFileInfo finfo( expandedAction );
304 if ( finfo.exists() && finfo.isFile() )
305 QDesktopServices::openUrl( QUrl::fromLocalFile( expandedAction ) );
307 QDesktopServices::openUrl( QUrl( expandedAction, QUrl::TolerantMode ) );
311 handleFormSubmitAction( expandedAction );
322#ifndef __clang_analyzer__
330 return mActionScopes;
340 QDomElement actionElement = actionNode.toElement();
341 const QDomNodeList actionScopeNodes = actionElement.elementsByTagName( u
"actionScope"_s );
343 if ( actionScopeNodes.isEmpty() )
352 for (
int j = 0; j < actionScopeNodes.length(); ++j )
354 const QDomElement actionScopeElem = actionScopeNodes.item( j ).toElement();
355 mActionScopes << actionScopeElem.attribute( u
"id"_s );
361 QgsDebugMsgLevel(
"context" + u
"project:layers:%1:actiondescriptions"_s.arg( context.
currentLayerId() ) +
" source " + actionElement.attributeNode( u
"name"_s ).value(), 3 );
362 mCommand = actionElement.attributeNode( u
"action"_s ).value();
363 mIcon = actionElement.attributeNode( u
"icon"_s ).value();
364 mCaptureOutput = actionElement.attributeNode( u
"capture"_s ).value().toInt() != 0;
366 QgsDebugMsgLevel(
"context" + u
"project:layers:%1:actionshorttitles"_s.arg( context.
currentLayerId() ) +
" source " + actionElement.attributeNode( u
"shortTitle"_s ).value(), 3 );
367 mNotificationMessage = actionElement.attributeNode( u
"notificationMessage"_s ).value();
368 mIsEnabledOnlyWhenEditable = actionElement.attributeNode( u
"isEnabledOnlyWhenEditable"_s ).value().toInt() != 0;
369 mId = QUuid( actionElement.attributeNode( u
"id"_s ).value() );
371 mId = QUuid::createUuid();
376 QDomElement actionSetting = actionsNode.ownerDocument().createElement( u
"actionsetting"_s );
377 actionSetting.setAttribute( u
"type"_s,
static_cast< int >( mType ) );
378 actionSetting.setAttribute( u
"name"_s, mDescription );
379 actionSetting.setAttribute( u
"shortTitle"_s, mShortTitle );
380 actionSetting.setAttribute( u
"icon"_s, mIcon );
381 actionSetting.setAttribute( u
"action"_s, mCommand );
382 actionSetting.setAttribute( u
"capture"_s, mCaptureOutput );
383 actionSetting.setAttribute( u
"notificationMessage"_s, mNotificationMessage );
384 actionSetting.setAttribute( u
"isEnabledOnlyWhenEditable"_s, mIsEnabledOnlyWhenEditable );
385 actionSetting.setAttribute( u
"id"_s, mId.toString() );
387 const auto constMActionScopes = mActionScopes;
388 for (
const QString &scope : constMActionScopes )
390 QDomElement actionScopeElem = actionsNode.ownerDocument().createElement( u
"actionScope"_s );
391 actionScopeElem.setAttribute( u
"id"_s, scope );
392 actionSetting.appendChild( actionScopeElem );
395 actionsNode.appendChild( actionSetting );
400 mExpressionContextScope = scope;
405 return mExpressionContextScope;
415 typeText = QObject::tr(
"Generic" );
420 typeText = QObject::tr(
"Generic Python" );
425 typeText = QObject::tr(
"macOS" );
430 typeText = QObject::tr(
"Windows" );
435 typeText = QObject::tr(
"Unix" );
440 typeText = QObject::tr(
"Open URL" );
445 typeText = QObject::tr(
"Submit URL (urlencoded or JSON)" );
450 typeText = QObject::tr(
"Submit URL (multipart)" );
454 return { QObject::tr( R
"html(
455<h2>Action Details</h2>
457 <b>Description:</b> %1<br>
458 <b>Short title:</b> %2<br>
464 )html" ).arg( mDescription, mShortTitle, typeText, actionScopes().values().join( ", "_L1 ), mCommand )};
AttributeActionType
Attribute action types.
@ OpenUrl
Open URL action.
@ SubmitUrlMultipart
POST data to an URL using "multipart/form-data".
@ Windows
Windows specific.
@ SubmitUrlEncoded
POST data to an URL, using "application/x-www-form-urlencoded" or "application/json" if the body is v...
@ Critical
Critical/error message.
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
void run(QgsVectorLayer *layer, const QgsFeature &feature, const QgsExpressionContext &expressionContext) const
Run this action.
void setCommand(const QString &newCommand)
Sets the action command.
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 readXml(const QDomNode &actionNode, const QgsReadWriteContext &context=QgsReadWriteContext())
Reads an XML definition from actionNode into this object.
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.
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
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.
virtual QString translate(const QString &context, const QString &sourceText, const char *disambiguation=nullptr, int n=-1) const =0
Translates a string using the Qt QTranslator mechanism.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
A container for the context for various read/write operations on objects.
const QgsProjectTranslator * projectTranslator() const
Returns the project translator.
const QString currentLayerId() const
Returns the currently used layer id as string.
static QgsRunProcess * create(const QString &action, bool capture)
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)