19#include <QDesktopServices> 
   24#include <QTemporaryDir> 
   25#include <QNetworkRequest> 
   26#include <QJsonDocument> 
   27#include <QHttpMultiPart> 
   28#include <QMimeDatabase> 
   29#include <QApplication> 
   60#elif defined(Q_OS_MAC) 
 
   88void QgsAction::handleFormSubmitAction( 
const QString &expandedAction )
 const 
   92  QApplication::setOverrideCursor( Qt::WaitCursor );
 
   94  QUrl url{ expandedAction };
 
   97  const QString payload { url.query( QUrl::ComponentFormattingOption::FullyEncoded ).replace( QChar( 
'+' ), QStringLiteral( 
"%2B" ) ) };
 
  100  const QUrlQuery queryString { url.query( ) };
 
  101  url.setQuery( QString( ) );
 
  103  QNetworkRequest req { url };
 
  107  if ( url.toString().contains( QLatin1String( 
"fake_qgis_http_endpoint" ) ) )
 
  109    req.setUrl( QStringLiteral( 
"file://%1" ).arg( url.path() ) );
 
  112  QNetworkReply *reply = 
nullptr;
 
  116    QString contentType { QStringLiteral( 
"application/x-www-form-urlencoded" ) };
 
  118    QJsonParseError jsonError;
 
  119    QJsonDocument::fromJson( payload.toUtf8(), &jsonError );
 
  120    if ( jsonError.error == QJsonParseError::ParseError::NoError )
 
  122      contentType = QStringLiteral( 
"application/json" );
 
  124    req.setHeader( QNetworkRequest::KnownHeaders::ContentTypeHeader, contentType );
 
  130    QHttpMultiPart *multiPart = 
new QHttpMultiPart( QHttpMultiPart::FormDataType );
 
  131    const QList<QPair<QString, QString>> queryItems { queryString.queryItems( QUrl::ComponentFormattingOption::FullyDecoded ) };
 
  132    for ( 
const QPair<QString, QString> &queryItem : std::as_const( queryItems ) )
 
  135      part.setHeader( QNetworkRequest::ContentDispositionHeader,
 
  136                      QStringLiteral( 
"form-data; name=\"%1\"" )
 
  137                      .arg( QString( queryItem.first ).replace( 
'"', QLatin1String( R
"(\")" ) ) ) ); 
  138      part.setBody( queryItem.second.toUtf8() ); 
  139      multiPart->append( part ); 
  142    multiPart->setParent( reply ); 
  145  QObject::connect( reply, &QNetworkReply::finished, reply, [ reply ] 
  147    if ( reply->error() == QNetworkReply::NoError )
 
  150      if ( QgsVariantUtils::isNull( reply->attribute( QNetworkRequest::RedirectionTargetAttribute ) ) )
 
  153        const QByteArray replyData = reply->readAll();
 
  155        QString filename { 
"download.bin" };
 
  156        if ( const std::string header = reply->header( QNetworkRequest::KnownHeaders::ContentDispositionHeader ).toString().toStdString(); ! header.empty() )
 
  163          const std::string q1 { R
"(filename=)" }; 
  165          if ( size_t pos = header.find( q1 ); pos != std::string::npos ) 
  169            if ( header.find( R
"(filename=")" ) != std::string::npos ) 
  174            const size_t len = pos + q1.size(); 
  176            const std::string q2 { R"(")" }; 
  177            if ( size_t pos = header.find( q2, len ); pos != std::string::npos ) 
  179              bool escaped = false; 
  180              while ( pos != std::string::npos && header[pos - 1] == '\\' )
 
  182                pos = header.find( q2, pos + 1 );
 
  185              ascii = header.substr( len, pos - len );
 
  189                for ( size_t i = 0; i < ascii.size(); ++i )
 
  191                  if ( ascii[i] == 
'\\' )
 
  193                    if ( i > 0 && ascii[i - 1] == 
'\\' )
 
  195                      cleaned.push_back( ascii[i] );
 
  200                    cleaned.push_back( ascii[i] );
 
  210          const std::string u { R
"(UTF-8'')" }; 
  211          if ( const size_t pos = header.find( u ); pos != std::string::npos ) 
  213            utf8 = header.substr( pos + u.size() ); 
  219            if ( ! utf8.empty( ) )
 
  221              filename = QString::fromStdString( utf8 );
 
  226            filename = QString::fromStdString( ascii );
 
  229        else if ( !QgsVariantUtils::isNull( reply->header( QNetworkRequest::KnownHeaders::ContentTypeHeader ) ) )
 
  231          QString contentTypeHeader { reply->header( QNetworkRequest::KnownHeaders::ContentTypeHeader ).toString() };
 
  233          if ( contentTypeHeader.contains( 
';' ) )
 
  235            contentTypeHeader = contentTypeHeader.left( contentTypeHeader.indexOf( 
';' ) );
 
  238          QMimeType mimeType { QMimeDatabase().mimeTypeForName( contentTypeHeader ) };
 
  239          if ( mimeType.isValid() )
 
  241            filename = QStringLiteral( 
"download.%1" ).arg( mimeType.preferredSuffix() );
 
  245        QTemporaryDir tempDir;
 
  246        tempDir.setAutoRemove( false );
 
  248        const QString tempFilePath{ tempDir.path() + QDir::separator() + filename };
 
  249        QFile tempFile{ tempFilePath };
 
  250        tempFile.open( QIODevice::WriteOnly );
 
  251        tempFile.write( replyData );
 
  253        QDesktopServices::openUrl( QUrl::fromLocalFile( tempFilePath ) );
 
  257        QgsMessageLog::logMessage( QObject::tr( 
"Redirect is not supported!" ), QStringLiteral( 
"Form Submit Action" ), Qgis::MessageLevel::Critical );
 
  264    reply->deleteLater();
 
  265    QApplication::restoreOverrideCursor( );
 
  272  mCommand = newCommand;
 
 
  279    QgsDebugError( QStringLiteral( 
"Invalid action cannot be run" ) );
 
  288  QApplication::setOverrideCursor( Qt::WaitCursor );
 
  290  QApplication::restoreOverrideCursor();
 
  294    const QFileInfo finfo( expandedAction );
 
  295    if ( finfo.exists() && finfo.isFile() )
 
  296      QDesktopServices::openUrl( QUrl::fromLocalFile( expandedAction ) );
 
  298      QDesktopServices::openUrl( QUrl( expandedAction, QUrl::TolerantMode ) );
 
  302    handleFormSubmitAction( expandedAction );
 
  313#ifndef __clang_analyzer__ 
 
  321  return mActionScopes;
 
 
  331  QDomElement actionElement = actionNode.toElement();
 
  332  const QDomNodeList actionScopeNodes = actionElement.elementsByTagName( QStringLiteral( 
"actionScope" ) );
 
  334  if ( actionScopeNodes.isEmpty() )
 
  337        << QStringLiteral( 
"Canvas" )
 
  338        << QStringLiteral( 
"Field" )
 
  339        << QStringLiteral( 
"Feature" );
 
  343    for ( 
int j = 0; j < actionScopeNodes.length(); ++j )
 
  345      const QDomElement actionScopeElem = actionScopeNodes.item( j ).toElement();
 
  346      mActionScopes << actionScopeElem.attribute( QStringLiteral( 
"id" ) );
 
  350  mType = 
static_cast< Qgis::AttributeActionType >( actionElement.attributeNode( QStringLiteral( 
"type" ) ).value().toInt() );
 
  351  mDescription = actionElement.attributeNode( QStringLiteral( 
"name" ) ).value();
 
  352  mCommand = actionElement.attributeNode( QStringLiteral( 
"action" ) ).value();
 
  353  mIcon = actionElement.attributeNode( QStringLiteral( 
"icon" ) ).value();
 
  354  mCaptureOutput = actionElement.attributeNode( QStringLiteral( 
"capture" ) ).value().toInt() != 0;
 
  355  mShortTitle = actionElement.attributeNode( QStringLiteral( 
"shortTitle" ) ).value();
 
  356  mNotificationMessage = actionElement.attributeNode( QStringLiteral( 
"notificationMessage" ) ).value();
 
  357  mIsEnabledOnlyWhenEditable = actionElement.attributeNode( QStringLiteral( 
"isEnabledOnlyWhenEditable" ) ).value().toInt() != 0;
 
  358  mId = QUuid( actionElement.attributeNode( QStringLiteral( 
"id" ) ).value() );
 
  360    mId = QUuid::createUuid();
 
 
  365  QDomElement actionSetting = actionsNode.ownerDocument().createElement( QStringLiteral( 
"actionsetting" ) );
 
  366  actionSetting.setAttribute( QStringLiteral( 
"type" ), 
static_cast< int >( mType ) );
 
  367  actionSetting.setAttribute( QStringLiteral( 
"name" ), mDescription );
 
  368  actionSetting.setAttribute( QStringLiteral( 
"shortTitle" ), mShortTitle );
 
  369  actionSetting.setAttribute( QStringLiteral( 
"icon" ), mIcon );
 
  370  actionSetting.setAttribute( QStringLiteral( 
"action" ), mCommand );
 
  371  actionSetting.setAttribute( QStringLiteral( 
"capture" ), mCaptureOutput );
 
  372  actionSetting.setAttribute( QStringLiteral( 
"notificationMessage" ), mNotificationMessage );
 
  373  actionSetting.setAttribute( QStringLiteral( 
"isEnabledOnlyWhenEditable" ), mIsEnabledOnlyWhenEditable );
 
  374  actionSetting.setAttribute( QStringLiteral( 
"id" ), mId.toString() );
 
  376  const auto constMActionScopes = mActionScopes;
 
  377  for ( 
const QString &scope : constMActionScopes )
 
  379    QDomElement actionScopeElem = actionsNode.ownerDocument().createElement( QStringLiteral( 
"actionScope" ) );
 
  380    actionScopeElem.setAttribute( QStringLiteral( 
"id" ), scope );
 
  381    actionSetting.appendChild( actionScopeElem );
 
  384  actionsNode.appendChild( actionSetting );
 
 
  389  mExpressionContextScope = scope;
 
 
  394  return mExpressionContextScope;
 
 
  404      typeText = QObject::tr( 
"Generic" );
 
  409      typeText = QObject::tr( 
"Generic Python" );
 
  414      typeText = QObject::tr( 
"macOS" );
 
  419      typeText = QObject::tr( 
"Windows" );
 
  424      typeText = QObject::tr( 
"Unix" );
 
  429      typeText = QObject::tr( 
"Open URL" );
 
  434      typeText = QObject::tr( 
"Submit URL (urlencoded or JSON)" );
 
  439      typeText = QObject::tr( 
"Submit URL (multipart)" );
 
  443  return { QObject::tr( R
"html( 
  444<h2>Action Details</h2> 
  446   <b>Description:</b> %1<br> 
  447   <b>Short title:</b> %2<br> 
  453  )html" ).arg( mDescription, mShortTitle, typeText, actionScopes().values().join( QLatin1String( ", " ) ), 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 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.
 
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 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)
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.
 
#define QgsDebugError(str)