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.