[qgis] 01/06: Imported Upstream version 2.14.9+dfsg

Bas Couwenberg sebastic at debian.org
Fri Nov 25 15:36:55 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository qgis.

commit 03a6947a426c227f8d533ac12db6eb18dee08672
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Fri Nov 25 14:30:36 2016 +0100

    Imported Upstream version 2.14.9+dfsg
---
 CMakeLists.txt                                     |   2 +-
 ChangeLog                                          | 285 +++++++++++++++++++++
 ci/travis/linux/qt4/script.sh                      |   2 +-
 debian/changelog                                   |  10 +-
 python/core/geometry/qgsabstractgeometryv2.sip     |   4 +-
 python/core/geometry/qgscircularstringv2.sip       |   4 +-
 python/core/geometry/qgscompoundcurvev2.sip        |   4 +-
 python/core/geometry/qgscurvepolygonv2.sip         |   5 +-
 python/core/geometry/qgscurvev2.sip                |   4 +-
 python/core/geometry/qgsgeometrycollectionv2.sip   |   5 +-
 python/core/geometry/qgslinestringv2.sip           |   1 +
 python/core/geometry/qgsmulticurvev2.sip           |   4 +-
 python/core/geometry/qgsmultilinestringv2.sip      |   4 +-
 python/core/geometry/qgsmultipointv2.sip           |   5 +-
 python/core/geometry/qgsmultipolygonv2.sip         |   4 +-
 python/core/geometry/qgsmultisurfacev2.sip         |   4 +-
 python/core/geometry/qgspointv2.sip                |   1 +
 python/core/geometry/qgspolygonv2.sip              |   4 +-
 python/core/geometry/qgssurfacev2.sip              |   2 +-
 python/core/symbology-ng/qgssymbollayerv2.sip      |  68 +++--
 python/gui/gui.sip                                 |   1 +
 python/gui/layertree/qgslayertreeview.sip          |   8 +
 python/gui/qgsextentgroupbox.sip                   |   2 +-
 python/gui/qgsfiledownloader.sip                   |  66 +++++
 python/gui/qgsgeometryrubberband.sip               |   2 +-
 python/plugins/processing/algs/help/qgis.yaml      |   4 +-
 python/plugins/processing/algs/qgis/Eliminate.py   |   5 +-
 python/plugins/processing/algs/qgis/Union.py       |   2 +-
 python/plugins/processing/algs/r/RUtils.py         |   2 +-
 src/app/composer/qgsattributeselectiondialog.cpp   |   2 +-
 src/app/nodetool/qgsmaptoolnodetool.cpp            |   7 +-
 src/app/qgisapp.cpp                                |   4 +-
 src/app/qgsattributetabledialog.cpp                |   4 +-
 src/app/qgsfeatureaction.cpp                       |  32 ++-
 src/app/qgsfieldcalculator.cpp                     |   4 +-
 src/app/qgsidentifyresultsdialog.cpp               |  52 ++++
 src/app/qgsidentifyresultsdialog.h                 |   7 +
 src/core/composer/qgscomposerattributetablev2.cpp  |   2 +-
 src/core/composer/qgscomposermapgrid.cpp           |   8 +-
 src/core/geometry/qgsabstractgeometryv2.h          |   2 +-
 src/core/geometry/qgscircularstringv2.cpp          |   3 +
 src/core/geometry/qgscurvepolygonv2.cpp            |  23 +-
 src/core/geometry/qgscurvepolygonv2.h              |   1 +
 src/core/geometry/qgsgeometry.cpp                  |   9 +-
 src/core/geometry/qgsgeometrycollectionv2.cpp      |  16 ++
 src/core/geometry/qgsgeometrycollectionv2.h        |   2 +
 src/core/geometry/qgsgeometryutils.cpp             |   1 +
 src/core/geometry/qgsgeometryutils.h               |   8 +-
 src/core/geometry/qgslinestringv2.cpp              |  10 +-
 src/core/geometry/qgslinestringv2.h                |   1 +
 src/core/geometry/qgsmultipointv2.h                |   1 +
 src/core/geometry/qgspointv2.h                     |   1 +
 src/core/qgsconditionalstyle.cpp                   |   2 +-
 src/core/qgsexpression.cpp                         |   2 +-
 src/core/qgsexpressioncontext.cpp                  |   4 +-
 src/core/qgsogcutils.cpp                           |   2 +-
 src/core/qgspointlocator.cpp                       |   2 +
 src/core/qgsvectorlayerrenderer.cpp                |   2 +-
 src/core/qgswebpage.h                              |   4 +
 src/core/symbology-ng/qgssymbolv2.cpp              |  10 +-
 src/gui/CMakeLists.txt                             |   3 +
 .../qgsfieldconditionalformatwidget.cpp            |   2 +-
 src/gui/layertree/qgslayertreeview.cpp             |  46 ++++
 src/gui/layertree/qgslayertreeview.h               |   8 +
 src/gui/qgsattributedialog.cpp                     |   5 +-
 src/gui/qgsattributedialog.h                       |   5 +-
 src/gui/qgsextentgroupbox.h                        |   2 +-
 src/gui/qgsfiledownloader.cpp                      | 203 +++++++++++++++
 src/gui/qgsfiledownloader.h                        | 112 ++++++++
 src/gui/qgsmapcanvas.cpp                           |  11 +-
 src/gui/symbology-ng/qgssymbollayerv2widget.cpp    |   2 +-
 src/providers/ogr/qgsogrprovider.cpp               |  34 ++-
 src/providers/oracle/qgsoracleprovider.cpp         |   9 +-
 src/providers/spatialite/qgsspatialiteprovider.cpp |  11 +-
 .../virtual/qgsvirtuallayersqlitehelper.cpp        |  14 +-
 .../virtual/qgsvirtuallayersqlitemodule.cpp        |   2 +-
 src/providers/wms/qgswmsdataitems.cpp              |  33 ++-
 src/providers/wms/qgswmsprovider.cpp               |   3 +-
 src/server/qgshostedrdsbuilder.cpp                 |   2 +-
 src/server/qgsserverprojectparser.cpp              |  45 ++++
 src/server/qgsserverprojectparser.h                |   3 +
 src/server/qgswfsserver.cpp                        |  23 ++
 src/server/qgswmsconfigparser.cpp                  |  12 +-
 src/server/qgswmsprojectparser.cpp                 |   9 +-
 src/server/qgswmsserver.cpp                        |  10 +
 tests/src/core/testqgsgeometry.cpp                 |   5 +-
 tests/src/core/testqgspointlocator.cpp             |  43 ++++
 tests/src/gui/CMakeLists.txt                       |   2 +-
 tests/src/gui/testqgsfiledownloader.cpp            | 251 ++++++++++++++++++
 tests/src/python/CMakeLists.txt                    |   4 +
 tests/src/python/qgis_wrapped_server.py            | 156 +++++++++++
 tests/src/python/test_authmanager_password_ows.py  | 186 ++++++++++++++
 tests/src/python/test_authmanager_pki_ows.py       | 207 +++++++++++++++
 tests/src/python/test_authmanager_pki_postgres.py  | 233 +++++++++++++++++
 tests/src/python/test_provider_ogr_gpkg.py         |  56 +++-
 tests/src/python/test_qgsfiledownloader.py         | 147 +++++++++++
 tests/src/python/test_qgsserver.py                 |  16 +-
 tests/src/python/utilities.py                      |  23 ++
 tests/testdata/qgis_server/getprojectsettings.txt  |  21 +-
 .../{test+project.qgs => test_project.qgs}         | 239 ++++++++++-------
 ...roject_inspire.qgs => test_project_inspire.qgs} |   0
 .../{test+project_wfs.qgs => test_project_wfs.qgs} |   0
 102 files changed, 2645 insertions(+), 295 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2f3e2bc..d6db4b1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 SET(CPACK_PACKAGE_VERSION_MAJOR "2")
 SET(CPACK_PACKAGE_VERSION_MINOR "14")
-SET(CPACK_PACKAGE_VERSION_PATCH "8")
+SET(CPACK_PACKAGE_VERSION_PATCH "9")
 SET(COMPLETE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH})
 SET(RELEASE_NAME "Essen")
 IF (POLICY CMP0048) # in CMake 3.0.0+
diff --git a/ChangeLog b/ChangeLog
index d7a3f86..e5b78ef 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,288 @@
+Matthias Kuhn <matthias at opengis.ch>	2016-10-17
+
+    Don't rely on RTTI to convert symbol layer to sip objects
+
+Marco Hugentobler <marco.hugentobler at sourcepole.ch>	2016-11-18
+
+    Add global scope in server
+
+Merge: 520a137 4cc85fd
+mhugent <marco.hugentobler at sourcepole.ch>	2016-11-18
+
+    Merge pull request #3781 from mhugent/wms_project_variables
+
+    Set project expression variables in server
+
+Marco Hugentobler <marco.hugentobler at sourcepole.ch>	2016-11-18
+
+    Set scope directly to new context
+
+Marco Hugentobler <marco.hugentobler at sourcepole.ch>	2016-11-17
+
+    Set project expression variables in server
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-16
+
+    fix 27de86 indentation
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-16
+
+    oracle provider: fix retrieval of column comments for geometryless tables (fixes #15853)
+
+    (cherry picked from commit a62fdb085d741da898f2bbb048c33b58aee9d630)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-15
+
+    Fix compilation without QtWebKit
+
+     (cherry-picked from 04e02aff69)
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-11
+
+    fix builds on trusty and precise
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-01
+
+    fix typo
+
+Merge: 526949b 78f0da2
+Alessandro Pasotti <elpaso at itopen.it>	2016-11-11
+
+    Merge pull request #3752 from elpaso/downloader_2_14_test_fix
+
+    Make file downloader test more stable
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-11
+
+    Make file downloader test more stable
+
+Matthias Kuhn <matthias at opengis.ch>	2016-11-11
+
+    [spatialite] Don't skip default values
+
+    When inserting multiple features in a single prepared statement, the spatialite
+    provider would skip any default for individual features, even though they have
+    been specified in the field list, resulting in missing fields.
+
+Merge: afb4739 a676bde
+Alessandro Pasotti <elpaso at itopen.it>	2016-11-10
+
+    Merge pull request #3744 from elpaso/downloader_2_14
+
+    [bugfix][backport] File downloader for identify dialog hyperlinks
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-09
+
+    [bugfix][backport] File downloader for identify dialog hyperlinks
+
+    fixes #14703
+
+    Include C++ and Python tests
+
+     ( cherry-picked from commit bdc2e24 )
+
+    Try to convince Travis to behave like a normal mechanical being
+    Travis won: ported all test cases to Python
+    and disabled C++ companion test (still useful locally and
+    for debugging)
+
+    For the curious: QTemporaryFile is not working as expected
+
+     ( cherry-picked from 57aa7fd )
+
+Hugo Mercier <hugo.mercier at oslandia.com>	2016-11-09
+
+    [virtual] Fix encoding issue
+
+rldhont <rldhont at gmail.com>	2016-11-08
+
+    [BUGFIX][QGIS Server] Revert layer order in WMS GetContext request
+
+Etienne Trimaille <gustrimaille at yahoo.fr>	2016-11-08
+
+    fixed wrong provider reference in union algorithm
+
+rldhont <rldhont at gmail.com>	2016-11-07
+
+    [BUGFIX] QgsOgcUtils: fix typo contians -> contains
+
+    This has been fix in feature 'QgsOgcUtils: add conversion from QgsSQLStatement to OGC filters' 1da1c278e758e7c84376ce7548c38392aa62c66d
+
+Merge: 5fed35a 6fd3f74
+Alessandro Pasotti <elpaso at itopen.it>	2016-11-05
+
+    Merge pull request #3724 from elpaso/auth_tests_2_14
+
+    [tests] Authmanager tests for username/pwd and PKI
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-05
+
+    [tests] Authmanager tests for username/pwd and PKI
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-05
+
+    Add missing /Transfer/ annotations to geometry classes
+
+    (cherry picked from commit 91e2f009c9652aac2a54380083f2c1c591069471)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-05
+
+    Add missing /Factory/ annotations to geometry classes
+
+    (cherry picked from commit 93e559d399a57faf300d2ca6093c30220879f228)
+
+Marco Hugentobler <marco.hugentobler at sourcepole.ch>	2016-11-04
+
+    Fix empty legends in WMS GetPrint
+
+rldhont <rldhont at gmail.com>	2016-11-03
+
+    [BUGFIX][QGIS Server] No flags in QgsFeatureRequest if expression needs geometry
+
+rldhont <rldhont at gmail.com>	2016-11-02
+
+    [BUGFIX][QGIS Server] Apply filter element
+
+Even Rouault <even.rouault at spatialys.com>	2016-11-02
+
+    test_provider_ogr_gpkg.py: Test disabling walForSqlite3 setting
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-01
+
+    fix typos
+
+    (cherry picked from commit 7a326b1b8d7026bc2782afc0fdc5abbaa1065370)
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-31
+
+    Fix test_provider_ogr_gpkg.py
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-31
+
+    [OGR provider] Make addAttributes() return the requested field type, precision and width so as to make  QgsVectorLayerEditBuffer::commitChanges() API
+
+    Fixes #15614
+
+Martin Dobias <wonder.sk at gmail.com>	2016-10-14
+
+    Fix layer tree expanded state when used expand/collapse all (fixes #15691)
+
+    (cherry picked from commit de85fdd6e8fa3d4f38196376aabccce317cbf341)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-10-07
+
+    Fix crash in node tool after deleting the whole geometry (fixes #15659)
+
+    Made sure that both closestVertex() and closestSegment() return negative
+    distance on error (e.g. with null or emtpy geometry).
+
+    Also fixes snapping when dealing with layers with null/invalid geometries
+
+    (cherry picked from commit c093d5188fad685c4a596ff23c27aad7d151dac2)
+
+Hugo Mercier <hugo.mercier at oslandia.com>	2016-10-28
+
+    Don't delete QgsAttributeDialog too early. Fixes #15737
+
+    (cherry picked from commit 9ecdf6101433)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-10-05
+
+    Fix crash when loading WCS layers (fixes #15595)
+
+    The problem is that some providers would still issue network
+    requests in prepareJobs() - this should be ideally avoided,
+    because it is run in main thread - all the work should be deferred
+    to be done in worker thread.
+
+    (cherry picked from commit 08f4a0f40cce21d5730653a75bdd44f175f3b0b8)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-10-03
+
+    Fix WMS identify when using "Feature" format and the layer has named CRS
+
+    (cherry picked from commit 9ef91ea6294cdab762ffb2543d02473bcccbed80)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Fix build
+
+Martin Dobias <wonder.sk at gmail.com>	2016-10-03
+
+    Fix listing of WMTS layers in browser (fixes #15350)
+
+    (cherry picked from commit 29d2bef7954d4e2b2cf21611c1398d0a7666e4c6)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Use QgsExpressionContextScope::addVariable instead of setVariable
+
+    ...where appropriate (ie, read-only, non user set variables).
+    It's much faster as it doesn't need to check whether the
+    variable already exists.
+
+    Results in ~10% improvement in rendering speed. Refs #15752.
+
+    (cherry-picked from 85897885445c7383938ac89318d12a7d37024bb4)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Optimise QgsAbstractGeometry
+
+    Make nCoordinates virtual, and provide shortcuts for some
+    geometry types. The base method which calls coordinateSequence()
+    is quite slow in certain circumstances.
+
+    Speeds up rendering point layers by ~25%, also likely to
+    speed up lots of geometry heavy operations throughout QGIS
+
+    Refs #15752
+
+    (cherry-picked from 49432a84683e99bdc2592e7ae808e81fa3bc40cd)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Speed up point layer rendering - don't calculate unused label obstacles
+
+    Cuts render time by ~60%. Fix #15752.
+
+    (cherry-picked from 5798a82c8011ea7f44a1ed1d55ef0719786e8056)
+
+Marco Hugentobler <marco.hugentobler at sourcepole.ch>	2016-10-27
+
+    Fix python bindings of QgsGeometryRubberBand
+
+Merge: 36db42e 2a13dbf
+Alexander Bruy <alexander.bruy at gmail.com>	2016-10-26
+
+    Merge pull request #3674 from gacarrillor/patch-3
+
+    [processing] import vector in Eliminate to avoid error
+
+Germán <geotux_tuxman at linuxmail.org>	2016-10-25
+
+    Import vector because it is used in line 95.
+
+    It seems that line 95 was added by a cherry pick commit.
+
+rldhont <rldhont at gmail.com>	2016-10-25
+
+    [BUGFIX][QGIS Server] Do not cache invalid layer
+
+    After readLayerXml, the server stored layers in cache and in registry without verifying validity.
+
+Juergen E. Fischer <jef at norbit.de>	2016-10-22
+
+    fix #15744 (followup dbf6169)
+
+Matteo Ghetta <matteo.ghetta at gmail.com>	2016-10-10
+
+    Missing import fixed for R algorithm
+
+Juergen E. Fischer <jef at norbit.de>	2016-10-21
+
+    Release of 2.14.8
+
 rldhont <rldhont at gmail.com>	2016-10-21
 
     [BUGFIX][QGIS-Server] Don't failed when transform boundingbox
diff --git a/ci/travis/linux/qt4/script.sh b/ci/travis/linux/qt4/script.sh
index 8e499f4..e2d70b1 100755
--- a/ci/travis/linux/qt4/script.sh
+++ b/ci/travis/linux/qt4/script.sh
@@ -24,4 +24,4 @@ if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
   chmod -R ugo-w ~/.ccache
 fi
 
-xvfb-run ctest -V -E 'qgis_openstreetmaptest|qgis_wcsprovidertest|qgis_ziplayertest' -S ./qgis-test-travis.ctest --output-on-failure
+xvfb-run ctest -V -E 'qgis_filedownloader|qgis_openstreetmaptest|qgis_wcsprovidertest|qgis_ziplayertest' -S ./qgis-test-travis.ctest --output-on-failure
diff --git a/debian/changelog b/debian/changelog
index 34e2ccd..e11ca65 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,14 @@
-qgis (2.14.8) UNRELEASED; urgency=medium
+qgis (2.14.9) UNRELEASED; urgency=medium
+
+  * Release of 2.14.9
+
+ -- Jürgen E. Fischer <jef at norbit.de>  Fri, 25 Nov 2016 14:11:24 +0100
+
+qgis (2.14.8) unstable; urgency=medium
 
   * Release of 2.14.8
 
- -- Jürgen E. Fischer <jef at norbit.de>  Fri, 21 Oct 2016 14:15:16 +0200
+ -- Jürgen E. Fischer <jef at norbit.de>  Fri, 25 Nov 2016 14:11:24 +0100
 
 qgis (2.14.7) unstable; urgency=medium
 
diff --git a/python/core/geometry/qgsabstractgeometryv2.sip b/python/core/geometry/qgsabstractgeometryv2.sip
index 2264423..0d950ed 100644
--- a/python/core/geometry/qgsabstractgeometryv2.sip
+++ b/python/core/geometry/qgsabstractgeometryv2.sip
@@ -77,7 +77,7 @@ class QgsAbstractGeometryV2
 
     /** Clones the geometry by performing a deep copy
      */
-    virtual QgsAbstractGeometryV2* clone() const = 0;
+    virtual QgsAbstractGeometryV2* clone() const = 0 /Factory/;
 
     /** Clears the geometry, ie reset it to a null geometry
      */
@@ -227,7 +227,7 @@ class QgsAbstractGeometryV2
 
     /** Returns the number of nodes contained in the geometry
      */
-    int nCoordinates() const;
+    virtual int nCoordinates() const;
 
     /** Returns the point corresponding to a specified vertex id
      */
diff --git a/python/core/geometry/qgscircularstringv2.sip b/python/core/geometry/qgscircularstringv2.sip
index d471d99..b0372cc 100644
--- a/python/core/geometry/qgscircularstringv2.sip
+++ b/python/core/geometry/qgscircularstringv2.sip
@@ -13,7 +13,7 @@ class QgsCircularStringV2: public QgsCurveV2
 
     virtual QString geometryType() const;
     virtual int dimension() const;
-    virtual QgsCircularStringV2* clone() const;
+    virtual QgsCircularStringV2* clone() const /Factory/;
     virtual void clear();
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
@@ -57,7 +57,7 @@ class QgsCircularStringV2: public QgsCurveV2
     /**
      * @copydoc QgsCurveV2::curveToLine()
      */
-    virtual QgsLineStringV2* curveToLine() const;
+    virtual QgsLineStringV2* curveToLine() const /Factory/;
 
     void draw( QPainter& p ) const;
 
diff --git a/python/core/geometry/qgscompoundcurvev2.sip b/python/core/geometry/qgscompoundcurvev2.sip
index aaa6423..0c709b2 100644
--- a/python/core/geometry/qgscompoundcurvev2.sip
+++ b/python/core/geometry/qgscompoundcurvev2.sip
@@ -15,7 +15,7 @@ class QgsCompoundCurveV2: public QgsCurveV2
 
     virtual QString geometryType() const;
     virtual int dimension() const;
-    virtual QgsCompoundCurveV2* clone() const;
+    virtual QgsCompoundCurveV2* clone() const /Factory/;
     virtual void clear();
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
@@ -34,7 +34,7 @@ class QgsCompoundCurveV2: public QgsCurveV2
     virtual QgsPointV2 endPoint() const;
     virtual void points( QList<QgsPointV2>& pts ) const;
     virtual int numPoints() const;
-    virtual QgsLineStringV2* curveToLine() const;
+    virtual QgsLineStringV2* curveToLine() const /Factory/;
 
     /** Returns the number of curves in the geometry.
      */
diff --git a/python/core/geometry/qgscurvepolygonv2.sip b/python/core/geometry/qgscurvepolygonv2.sip
index 4843e16..d21ceee 100644
--- a/python/core/geometry/qgscurvepolygonv2.sip
+++ b/python/core/geometry/qgscurvepolygonv2.sip
@@ -12,7 +12,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2
 
     virtual QString geometryType() const;
     virtual int dimension() const;
-    virtual QgsCurvePolygonV2* clone() const;
+    virtual QgsCurvePolygonV2* clone() const /Factory/;
     void clear();
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
@@ -28,7 +28,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2
     //surface interface
     virtual double area() const;
     virtual double perimeter() const;
-    QgsPolygonV2* surfaceToPolygon() const;
+    QgsPolygonV2* surfaceToPolygon() const /Factory/;
 
     //curve polygon interface
     int numInteriorRings() const;
@@ -65,6 +65,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2
     virtual bool deleteVertex( QgsVertexId position );
 
     virtual QList< QList< QList< QgsPointV2 > > > coordinateSequence() const;
+    virtual int nCoordinates() const;
     double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const;
 
diff --git a/python/core/geometry/qgscurvev2.sip b/python/core/geometry/qgscurvev2.sip
index 82822b5..79eb415 100644
--- a/python/core/geometry/qgscurvev2.sip
+++ b/python/core/geometry/qgscurvev2.sip
@@ -10,7 +10,7 @@ class QgsCurveV2: public QgsAbstractGeometryV2
     virtual bool operator==( const QgsCurveV2& other ) const = 0;
     virtual bool operator!=( const QgsCurveV2& other ) const = 0;
 
-    virtual QgsCurveV2* clone() const = 0;
+    virtual QgsCurveV2* clone() const = 0 /Factory/;
 
     /** Returns the starting point of the curve.
      * @see endPoint
@@ -33,7 +33,7 @@ class QgsCurveV2: public QgsAbstractGeometryV2
     /** Returns a new line string geometry corresponding to a segmentized approximation
      * of the curve.
      */
-    virtual QgsLineStringV2* curveToLine() const = 0;
+    virtual QgsLineStringV2* curveToLine() const = 0 /Factory/;
 
     /** Adds a curve to a painter path.
      */
diff --git a/python/core/geometry/qgsgeometrycollectionv2.sip b/python/core/geometry/qgsgeometrycollectionv2.sip
index 87c2f0e..c526a5c 100644
--- a/python/core/geometry/qgsgeometrycollectionv2.sip
+++ b/python/core/geometry/qgsgeometrycollectionv2.sip
@@ -10,7 +10,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     //QgsGeometryCollectionV2& operator=( const QgsGeometryCollectionV2& c );
     virtual ~QgsGeometryCollectionV2();
 
-    virtual QgsGeometryCollectionV2* clone() const;
+    virtual QgsGeometryCollectionV2* clone() const /Factory/;
 
     /** Returns the number of geometries within the collection.
      */
@@ -69,6 +69,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     virtual QgsRectangle boundingBox() const;
 
     virtual QList< QList< QList< QgsPointV2 > > > coordinateSequence() const;
+    virtual int nCoordinates() const;
     virtual double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const;
 
@@ -84,7 +85,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     bool hasCurvedSegments() const;
 
     /** Returns a geometry without curves. Caller takes ownership*/
-    QgsAbstractGeometryV2* segmentize() const;
+    QgsAbstractGeometryV2* segmentize() const /Factory/;
 
     /** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
      * @param vertex the vertex id
diff --git a/python/core/geometry/qgslinestringv2.sip b/python/core/geometry/qgslinestringv2.sip
index 319a811..5257225 100644
--- a/python/core/geometry/qgslinestringv2.sip
+++ b/python/core/geometry/qgslinestringv2.sip
@@ -134,6 +134,7 @@ class QgsLineStringV2: public QgsCurveV2
     virtual QgsLineStringV2* curveToLine() const /Factory/;
 
     int numPoints() const;
+    virtual int nCoordinates() const;
     void points( QList<QgsPointV2>& pt ) const;
 
     void draw( QPainter& p ) const;
diff --git a/python/core/geometry/qgsmulticurvev2.sip b/python/core/geometry/qgsmulticurvev2.sip
index f40774b..ca4acad 100644
--- a/python/core/geometry/qgsmulticurvev2.sip
+++ b/python/core/geometry/qgsmulticurvev2.sip
@@ -6,7 +6,7 @@ class QgsMultiCurveV2: public QgsGeometryCollectionV2
 
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiCurveV2* clone() const;
+    virtual QgsMultiCurveV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiCurveV2: public QgsGeometryCollectionV2
     QString asJSON( int precision = 17 ) const;
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
     /** Returns a geometry without curves. Caller takes ownership*/
     QgsAbstractGeometryV2* segmentize() const /Factory/;
 
diff --git a/python/core/geometry/qgsmultilinestringv2.sip b/python/core/geometry/qgsmultilinestringv2.sip
index a3853cc..dcc971d 100644
--- a/python/core/geometry/qgsmultilinestringv2.sip
+++ b/python/core/geometry/qgsmultilinestringv2.sip
@@ -6,7 +6,7 @@ class QgsMultiLineStringV2: public QgsMultiCurveV2
 
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiLineStringV2* clone() const;
+    virtual QgsMultiLineStringV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiLineStringV2: public QgsMultiCurveV2
     QString asJSON( int precision = 17 ) const;
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
     /** Returns the geometry converted to the more generic curve type QgsMultiCurve
         @return the converted geometry. Caller takes ownership*/
diff --git a/python/core/geometry/qgsmultipointv2.sip b/python/core/geometry/qgsmultipointv2.sip
index 2838451..0fdf0f3 100644
--- a/python/core/geometry/qgsmultipointv2.sip
+++ b/python/core/geometry/qgsmultipointv2.sip
@@ -5,7 +5,7 @@ class QgsMultiPointV2: public QgsGeometryCollectionV2
 %End
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiPointV2* clone() const;
+    virtual QgsMultiPointV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -16,9 +16,10 @@ class QgsMultiPointV2: public QgsGeometryCollectionV2
     QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const;
     QString asJSON( int precision = 17 ) const;
 
+    virtual int nCoordinates() const;
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
   protected:
 
diff --git a/python/core/geometry/qgsmultipolygonv2.sip b/python/core/geometry/qgsmultipolygonv2.sip
index 66d080f..1265db0 100644
--- a/python/core/geometry/qgsmultipolygonv2.sip
+++ b/python/core/geometry/qgsmultipolygonv2.sip
@@ -5,7 +5,7 @@ class QgsMultiPolygonV2: public QgsMultiSurfaceV2
 %End
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiPolygonV2* clone() const;
+    virtual QgsMultiPolygonV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiPolygonV2: public QgsMultiSurfaceV2
 
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
     /** Returns the geometry converted to the more generic curve type QgsMultiSurface
         @return the converted geometry. Caller takes ownership*/
diff --git a/python/core/geometry/qgsmultisurfacev2.sip b/python/core/geometry/qgsmultisurfacev2.sip
index 2e406d1..fcc2999 100644
--- a/python/core/geometry/qgsmultisurfacev2.sip
+++ b/python/core/geometry/qgsmultisurfacev2.sip
@@ -5,7 +5,7 @@ class QgsMultiSurfaceV2: public QgsGeometryCollectionV2
 %End
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiSurfaceV2* clone() const;
+    virtual QgsMultiSurfaceV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiSurfaceV2: public QgsGeometryCollectionV2
 
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
     /** Returns a geometry without curves. Caller takes ownership*/
     QgsAbstractGeometryV2* segmentize() const /Factory/;
diff --git a/python/core/geometry/qgspointv2.sip b/python/core/geometry/qgspointv2.sip
index 13d3f48..a92278f 100644
--- a/python/core/geometry/qgspointv2.sip
+++ b/python/core/geometry/qgspointv2.sip
@@ -156,6 +156,7 @@ class QgsPointV2: public QgsAbstractGeometryV2
     void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform );
     void transform( const QTransform& t );
     virtual QList< QList< QList< QgsPointV2 > > > coordinateSequence() const;
+    virtual int nCoordinates() const;
 
     //low-level editing
     virtual bool insertVertex( QgsVertexId position, const QgsPointV2& vertex );
diff --git a/python/core/geometry/qgspolygonv2.sip b/python/core/geometry/qgspolygonv2.sip
index 7416804..b7cb575 100644
--- a/python/core/geometry/qgspolygonv2.sip
+++ b/python/core/geometry/qgspolygonv2.sip
@@ -11,7 +11,7 @@ class QgsPolygonV2: public QgsCurvePolygonV2
     bool operator!=( const QgsPolygonV2& other ) const;
 
     virtual QString geometryType() const;
-    virtual QgsPolygonV2* clone() const;
+    virtual QgsPolygonV2* clone() const /Factory/;
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
 
@@ -24,7 +24,7 @@ class QgsPolygonV2: public QgsCurvePolygonV2
     // inherited: QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const;
     // inherited: QString asJSON( int precision = 17 ) const;
 
-    QgsPolygonV2* surfaceToPolygon() const;
+    QgsPolygonV2* surfaceToPolygon() const /Factory/;
 
     /** Returns the geometry converted to the more generic curve type QgsCurvePolygon
          @return the converted geometry. Caller takes ownership*/
diff --git a/python/core/geometry/qgssurfacev2.sip b/python/core/geometry/qgssurfacev2.sip
index 571d337..a172608 100644
--- a/python/core/geometry/qgssurfacev2.sip
+++ b/python/core/geometry/qgssurfacev2.sip
@@ -6,7 +6,7 @@ class QgsSurfaceV2: public QgsAbstractGeometryV2
 
   public:
 
-    virtual QgsPolygonV2* surfaceToPolygon() const = 0;
+    virtual QgsPolygonV2* surfaceToPolygon() const = 0 /Factory/;
 
     virtual QgsRectangle boundingBox() const;
 
diff --git a/python/core/symbology-ng/qgssymbollayerv2.sip b/python/core/symbology-ng/qgssymbollayerv2.sip
index 307e977..359778f 100644
--- a/python/core/symbology-ng/qgssymbollayerv2.sip
+++ b/python/core/symbology-ng/qgssymbollayerv2.sip
@@ -1,6 +1,7 @@
 class QgsSymbolLayerV2
 {
 %TypeHeaderCode
+#include <qgssymbolv2.h>
 #include <qgssymbollayerv2.h>
 #include <qgslinesymbollayerv2.h>
 %End
@@ -9,53 +10,48 @@ class QgsSymbolLayerV2
   switch (sipCpp->type())
   {
     case QgsSymbolV2::Marker:
-      if (dynamic_cast<QgsEllipseSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsEllipseSymbolLayerV2;
-      else if (dynamic_cast<QgsFontMarkerSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsFontMarkerSymbolLayerV2;
-      else if (dynamic_cast<QgsSimpleMarkerSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsSimpleMarkerSymbolLayerV2;
-      else if (dynamic_cast<QgsSvgMarkerSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsSvgMarkerSymbolLayerV2;
-      else if (dynamic_cast<QgsVectorFieldSymbolLayer*>(sipCpp) != NULL)
-	sipType = sipType_QgsVectorFieldSymbolLayer;
+      if ( sipCpp->layerType() == "EllipseMarker" )
+        sipType = sipType_QgsEllipseSymbolLayerV2;
+      else if ( sipCpp->layerType() == "FontMarker" )
+        sipType = sipType_QgsFontMarkerSymbolLayerV2;
+      else if ( sipCpp->layerType() == "SimpleMarker" )
+        sipType = sipType_QgsSimpleMarkerSymbolLayerV2;
+      else if ( sipCpp->layerType() == "SvgMarker" )
+        sipType = sipType_QgsSvgMarkerSymbolLayerV2;
+      else if ( sipCpp->layerType() == "VectorField" )
+        sipType = sipType_QgsVectorFieldSymbolLayer;
       else
 	sipType = sipType_QgsMarkerSymbolLayerV2;
       break;
 
     case QgsSymbolV2::Line:
-      if (dynamic_cast<QgsMarkerLineSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsMarkerLineSymbolLayerV2;
-      else if (dynamic_cast<QgsSimpleLineSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsSimpleLineSymbolLayerV2;
+      if ( sipCpp->layerType() == "MarkerLine" )
+        sipType = sipType_QgsMarkerLineSymbolLayerV2;
+      else if ( sipCpp->layerType() == "SimpleLine" )
+        sipType = sipType_QgsSimpleLineSymbolLayerV2;
       else
 	sipType = sipType_QgsLineSymbolLayerV2;
       break;
 
     case QgsSymbolV2::Fill:
-      if (dynamic_cast<QgsSimpleFillSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsSimpleFillSymbolLayerV2;
-      else if (dynamic_cast<QgsImageFillSymbolLayer*>(sipCpp) != NULL)
-      {
-	if (dynamic_cast<QgsLinePatternFillSymbolLayer*>(sipCpp) != NULL)
-	  sipType = sipType_QgsLinePatternFillSymbolLayer;
-	else if (dynamic_cast<QgsPointPatternFillSymbolLayer*>(sipCpp) != NULL)
-	  sipType = sipType_QgsPointPatternFillSymbolLayer;
-	else if (dynamic_cast<QgsSVGFillSymbolLayer*>(sipCpp) != NULL)
-	  sipType = sipType_QgsSVGFillSymbolLayer;
-	else if (dynamic_cast<QgsRasterFillSymbolLayer*>(sipCpp) != NULL)
-	  sipType = sipType_QgsRasterFillSymbolLayer;
-	else
-	  sipType = sipType_QgsImageFillSymbolLayer;
-      }
-      else if (dynamic_cast<QgsCentroidFillSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsCentroidFillSymbolLayerV2;
-      else if (dynamic_cast<QgsGradientFillSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsGradientFillSymbolLayerV2;
-      else if (dynamic_cast<QgsShapeburstFillSymbolLayerV2*>(sipCpp) != NULL)
-	sipType = sipType_QgsShapeburstFillSymbolLayerV2;
+      if ( sipCpp->layerType() == "SimpleFill" )
+        sipType = sipType_QgsSimpleFillSymbolLayerV2;
+      else if ( sipCpp->layerType() == "LinePatternFill" )
+        sipType = sipType_QgsLinePatternFillSymbolLayer;
+      else if ( sipCpp->layerType() == "PointPatternFill" )
+        sipType = sipType_QgsPointPatternFillSymbolLayer;
+      else if ( sipCpp->layerType() == "SVGFill" )
+        sipType = sipType_QgsSVGFillSymbolLayer;
+      else if ( sipCpp->layerType() == "RasterFill" )
+        sipType = sipType_QgsRasterFillSymbolLayer;
+      else if ( sipCpp->layerType() == "CentroidFill" )
+        sipType = sipType_QgsCentroidFillSymbolLayerV2;
+      else if ( sipCpp->layerType() == "GradientFill" )
+        sipType = sipType_QgsGradientFillSymbolLayerV2;
+      else if ( sipCpp->layerType() == "ShapeburstFill" )
+        sipType = sipType_QgsShapeburstFillSymbolLayerV2;
       else
-	sipType = sipType_QgsFillSymbolLayerV2;
+        sipType = sipType_QgsFillSymbolLayerV2;
       break;
 
     case QgsSymbolV2::Hybrid:
diff --git a/python/gui/gui.sip b/python/gui/gui.sip
index cd9ad21..09065c0 100644
--- a/python/gui/gui.sip
+++ b/python/gui/gui.sip
@@ -74,6 +74,7 @@
 %Include qgsfieldvalidator.sip
 %Include qgsfiledropedit.sip
 %Include qgsfilewidget.sip
+%Include qgsfiledownloader.sip
 %Include qgsfilterlineedit.sip
 %Include qgsformannotationitem.sip
 %Include qgsgenericprojectionselector.sip
diff --git a/python/gui/layertree/qgslayertreeview.sip b/python/gui/layertree/qgslayertreeview.sip
index af1f539..636a276 100644
--- a/python/gui/layertree/qgslayertreeview.sip
+++ b/python/gui/layertree/qgslayertreeview.sip
@@ -72,6 +72,14 @@ class QgsLayerTreeView : QTreeView
     //! Force refresh of layer symbology. Normally not needed as the changes of layer's renderer are monitored by the model
     void refreshLayerSymbology( const QString& layerId );
 
+    //! Enhancement of QTreeView::expandAll() that also records expanded state in layer tree nodes
+    //! @note added in QGIS 2.18
+    void expandAllNodes();
+
+    //! Enhancement of QTreeView::collapseAll() that also records expanded state in layer tree nodes
+    //! @note added in QGIS 2.18
+    void collapseAllNodes();
+
   signals:
     //! Emitted when a current layer is changed
     void currentLayerChanged( QgsMapLayer* layer );
diff --git a/python/gui/qgsextentgroupbox.sip b/python/gui/qgsextentgroupbox.sip
index 1a5c658..c2b72d0 100644
--- a/python/gui/qgsextentgroupbox.sip
+++ b/python/gui/qgsextentgroupbox.sip
@@ -58,7 +58,7 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox
     //! set output extent to be the same as current extent (may be transformed to output CRS)
     void setOutputExtentFromCurrent();
 
-    //! set output extent to custom extent (may be transformed to outut CRS)
+    //! set output extent to custom extent (may be transformed to output CRS)
     void setOutputExtentFromUser( const QgsRectangle& extent, const QgsCoordinateReferenceSystem& crs );
 
   signals:
diff --git a/python/gui/qgsfiledownloader.sip b/python/gui/qgsfiledownloader.sip
new file mode 100644
index 0000000..63a110d
--- /dev/null
+++ b/python/gui/qgsfiledownloader.sip
@@ -0,0 +1,66 @@
+/***************************************************************************
+  qgsfiledownloader.sip
+  --------------------------------------
+  Date                 : November 2016
+  Copyright            : (C) 2016 by Alessandro Pasotti
+  Email                : elpaso at itopen dot it
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+/** \ingroup gui
+ * QgsFileDownloader is a utility class for downloading files.
+ *
+ * To use this class, it is necessary to pass the URL and an output file name as
+ * arguments to the constructor, the download will start immediately.
+ * The download is asynchronous and depending on the guiNotificationsEnabled
+ * parameter accepted by the constructor (default = true) the class will
+ * show a progress dialog and report all errors in a QMessageBox::warning dialog.
+ * If the guiNotificationsEnabled parameter is set to false, the class can still
+ * be used through the signals and slots mechanism.
+ * The object will destroy itself when the request completes, errors or is canceled.
+ *
+ * @note added in QGIS 2.18.1
+ */
+class QgsFileDownloader : public QObject
+{
+  %TypeHeaderCode
+  #include <qgsfiledownloader.h>
+  %End
+  public:
+    /**
+     * QgsFileDownloader
+     * @param url the download url
+     * @param outputFileName file name where the downloaded content will be stored
+     * @param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
+     */
+    QgsFileDownloader(QUrl url, QString outputFileName, bool guiNotificationsEnabled = true);
+
+    signals:
+      /** Emitted when the download has completed successfully  */
+      void downloadCompleted();
+      /** Emitted always when the downloader exits */
+      void downloadExited();
+      /** Emitted when the download was canceled by the user */
+      void downloadCanceled();
+      /** Emitted when an error makes the download fail */
+       void downloadError( QStringList errorMessages );
+      /** Emitted when data ready to be processed */
+      void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+
+    public slots:
+      /**
+       * Called when a download is canceled by the user
+       * this slot aborts the download and deletes the object
+       */
+      void onDownloadCanceled();
+
+    private:
+      ~QgsFileDownloader();
+
+};
diff --git a/python/gui/qgsgeometryrubberband.sip b/python/gui/qgsgeometryrubberband.sip
index 4d8cc7a..3cb22a7 100644
--- a/python/gui/qgsgeometryrubberband.sip
+++ b/python/gui/qgsgeometryrubberband.sip
@@ -54,7 +54,7 @@ class QgsGeometryRubberBand: QgsMapCanvasItem
     ~QgsGeometryRubberBand();
 
     /** Sets geometry (takes ownership). Geometry is expected to be in map coordinates */
-    void setGeometry( QgsAbstractGeometryV2* geom );
+    void setGeometry( QgsAbstractGeometryV2* geom /Transfer/ );
     /** Returns a pointer to the geometry*/
     const QgsAbstractGeometryV2* geometry();
     /** Moves vertex to new position (in map coordinates)*/
diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml
index bcf266f..2779b26 100644
--- a/python/plugins/processing/algs/help/qgis.yaml
+++ b/python/plugins/processing/algs/help/qgis.yaml
@@ -394,9 +394,9 @@ qgis:splitlineswithlines: >
   This algorithm split the lines in a line layer using the lines in another line layer to define the breaking points. Intersection between geometries in both layers are considered as split points.
 
 qgis:splitvectorlayer: >
-  This algorithm takes a vector layer and an attribute and generates a set of vector layers in an outut folder. Each of the layers created in that folder contains all features from the input layer with the same value for the specified attribute.
+  This algorithm takes a vector layer and an attribute and generates a set of vector layers in an output folder. Each of the layers created in that folder contains all features from the input layer with the same value for the specified attribute.
 
-  The number of files generated is equal to the nuber of different values found for the specified attribute.
+  The number of files generated is equal to the number of different values found for the specified attribute.
 
 qgis:statisticsbycategories:
 
diff --git a/python/plugins/processing/algs/qgis/Eliminate.py b/python/plugins/processing/algs/qgis/Eliminate.py
index 6bc6352..63dd595 100644
--- a/python/plugins/processing/algs/qgis/Eliminate.py
+++ b/python/plugins/processing/algs/qgis/Eliminate.py
@@ -37,7 +37,7 @@ from processing.core.parameters import ParameterTableField
 from processing.core.parameters import ParameterString
 from processing.core.parameters import ParameterSelection
 from processing.core.outputs import OutputVector
-from processing.tools import dataobjects
+from processing.tools import dataobjects, vector
 
 
 class Eliminate(GeoAlgorithm):
@@ -54,9 +54,6 @@ class Eliminate(GeoAlgorithm):
     MODE_SMALLEST_AREA = 1
     MODE_BOUNDARY = 2
 
-    def getIcon(self):
-        return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'eliminate.png'))
-
     def defineCharacteristics(self):
         self.name, self.i18n_name = self.trAlgorithm('Eliminate sliver polygons')
         self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
diff --git a/python/plugins/processing/algs/qgis/Union.py b/python/plugins/processing/algs/qgis/Union.py
index a292622..8c9fe0e 100644
--- a/python/plugins/processing/algs/qgis/Union.py
+++ b/python/plugins/processing/algs/qgis/Union.py
@@ -65,7 +65,7 @@ class Union(GeoAlgorithm):
         geomType = vlayerA.wkbType()
         fields = vector.combineVectorFields(vlayerA, vlayerB)
         writer = self.getOutputFromName(Union.OUTPUT).getVectorWriter(fields,
-                                                                      geomType, vproviderA.crs())
+                                                                      geomType, vlayerA.crs())
         inFeatA = QgsFeature()
         inFeatB = QgsFeature()
         outFeat = QgsFeature()
diff --git a/python/plugins/processing/algs/r/RUtils.py b/python/plugins/processing/algs/r/RUtils.py
index ddfe701..8c5612c 100644
--- a/python/plugins/processing/algs/r/RUtils.py
+++ b/python/plugins/processing/algs/r/RUtils.py
@@ -33,7 +33,7 @@ import subprocess
 from PyQt4.QtCore import QSettings, QCoreApplication
 from processing.core.ProcessingConfig import ProcessingConfig
 from processing.core.ProcessingLog import ProcessingLog
-from processing.tools.system import userFolder, isWindows, mkdir
+from processing.tools.system import userFolder, isWindows, mkdir, getTempFilenameInTempFolder
 
 
 class RUtils:
diff --git a/src/app/composer/qgsattributeselectiondialog.cpp b/src/app/composer/qgsattributeselectiondialog.cpp
index 0e4fdc5..7c360de 100644
--- a/src/app/composer/qgsattributeselectiondialog.cpp
+++ b/src/app/composer/qgsattributeselectiondialog.cpp
@@ -107,7 +107,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
   }
 
   QScopedPointer< QgsExpressionContext > expContext( object->createExpressionContext() );
-  expContext->lastScope()->setVariable( "row_number", 1 );
+  expContext->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), 1, true ) );
   expContext->setHighlightedVariables( QStringList() << "row_number" );
   return QgsExpressionContext( *expContext );
 }
diff --git a/src/app/nodetool/qgsmaptoolnodetool.cpp b/src/app/nodetool/qgsmaptoolnodetool.cpp
index ccb0786..0ca5092 100644
--- a/src/app/nodetool/qgsmaptoolnodetool.cpp
+++ b/src/app/nodetool/qgsmaptoolnodetool.cpp
@@ -272,12 +272,11 @@ void QgsMapToolNodeTool::canvasPressEvent( QgsMapMouseEvent* e )
 
     // get geometry and find if snapping is near it
     int atVertex, beforeVertex, afterVertex;
-    double dist;
-    QgsPoint closestLayerVertex = mSelectedFeature->geometry()->closestVertex( layerCoordPoint, atVertex, beforeVertex, afterVertex, dist );
-    dist = sqrt( dist );
+    double sqrDist; // will be negative on error
+    QgsPoint closestLayerVertex = mSelectedFeature->geometry()->closestVertex( layerCoordPoint, atVertex, beforeVertex, afterVertex, sqrDist );
 
     mSnapper.snapToCurrentLayer( e->pos(), snapResults, QgsSnapper::SnapToVertex, tol );
-    if ( dist <= tol )
+    if ( sqrDist >= 0 && sqrt( sqrDist ) <= tol )
     {
       // some vertex selected
       mMoving = true;
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 581b4d1..e4e6496 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -2757,11 +2757,11 @@ void QgisApp::initLayerTreeView()
   QAction* actionExpandAll = new QAction( tr( "Expand All" ), this );
   actionExpandAll->setIcon( QgsApplication::getThemeIcon( "/mActionExpandTree.svg" ) );
   actionExpandAll->setToolTip( tr( "Expand All" ) );
-  connect( actionExpandAll, SIGNAL( triggered( bool ) ), mLayerTreeView, SLOT( expandAll() ) );
+  connect( actionExpandAll, SIGNAL( triggered( bool ) ), mLayerTreeView, SLOT( expandAllNodes() ) );
   QAction* actionCollapseAll = new QAction( tr( "Collapse All" ), this );
   actionCollapseAll->setIcon( QgsApplication::getThemeIcon( "/mActionCollapseTree.svg" ) );
   actionCollapseAll->setToolTip( tr( "Collapse All" ) );
-  connect( actionCollapseAll, SIGNAL( triggered( bool ) ), mLayerTreeView, SLOT( collapseAll() ) );
+  connect( actionCollapseAll, SIGNAL( triggered( bool ) ), mLayerTreeView, SLOT( collapseAllNodes() ) );
 
   QToolBar* toolbar = new QToolBar();
   toolbar->setIconSize( QSize( 16, 16 ) );
diff --git a/src/app/qgsattributetabledialog.cpp b/src/app/qgsattributetabledialog.cpp
index 8a1684b..1670cd3 100644
--- a/src/app/qgsattributetabledialog.cpp
+++ b/src/app/qgsattributetabledialog.cpp
@@ -55,7 +55,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
   if ( layer )
     expContext << QgsExpressionContextUtils::layerScope( layer );
 
-  expContext.lastScope()->setVariable( "row_number", 1 );
+  expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), 1, true ) );
 
   expContext.setHighlightedVariables( QStringList() << "row_number" );
 
@@ -393,7 +393,7 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, const
     }
 
     context.setFeature( feature );
-    context.lastScope()->setVariable( QString( "row_number" ), rownum );
+    context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), rownum, true ) );
 
     QVariant value = exp.evaluate( &context );
     fld.convertCompatible( value );
diff --git a/src/app/qgsfeatureaction.cpp b/src/app/qgsfeatureaction.cpp
index 106cfcf..a78df31 100644
--- a/src/app/qgsfeatureaction.cpp
+++ b/src/app/qgsfeatureaction.cpp
@@ -98,7 +98,9 @@ bool QgsFeatureAction::viewFeatureForm( QgsHighlight *h )
 
   QgsAttributeDialog *dialog = newDialog( true );
   dialog->setHighlight( h );
-  dialog->show(); // will also delete the dialog on close (show() is overridden)
+  // delete the dialog when it is closed
+  dialog->setAttribute( Qt::WA_DeleteOnClose );
+  dialog->show();
 
   return true;
 }
@@ -108,24 +110,29 @@ bool QgsFeatureAction::editFeature( bool showModal )
   if ( !mLayer )
     return false;
 
-  QgsAttributeDialog *dialog = newDialog( false );
-
-  if ( !mFeature->isValid() )
-    dialog->setIsAddDialog( true );
-
   if ( showModal )
   {
-    dialog->setAttribute( Qt::WA_DeleteOnClose );
-    int rv = dialog->exec();
+    QScopedPointer<QgsAttributeDialog> dialog( newDialog( false ) );
+
+    if ( !mFeature->isValid() )
+      dialog->setIsAddDialog( true );
 
+    int rv = dialog->exec();
     mFeature->setAttributes( dialog->feature()->attributes() );
     return rv;
   }
   else
   {
-    dialog->show(); // will also delete the dialog on close (show() is overridden)
-  }
+    QgsAttributeDialog* dialog = newDialog( false );
+
+    if ( !mFeature->isValid() )
+      dialog->setIsAddDialog( true );
 
+    // delete the dialog when it is closed
+    dialog->setAttribute( Qt::WA_DeleteOnClose );
+    dialog->show();
+  }
+  
   return true;
 }
 
@@ -193,6 +200,8 @@ bool QgsFeatureAction::addFeature( const QgsAttributeMap& defaultAttributes, boo
   else
   {
     QgsAttributeDialog *dialog = newDialog( false );
+    // delete the dialog when it is closed
+    dialog->setAttribute( Qt::WA_DeleteOnClose );
     dialog->setIsAddDialog( true );
     dialog->setEditCommandMessage( text() );
 
@@ -201,12 +210,11 @@ bool QgsFeatureAction::addFeature( const QgsAttributeMap& defaultAttributes, boo
     if ( !showModal )
     {
       setParent( dialog ); // keep dialog until the dialog is closed and destructed
-      dialog->show(); // will also delete the dialog on close (show() is overridden)
+      dialog->show();
       mFeature = nullptr;
       return true;
     }
 
-    dialog->setAttribute( Qt::WA_DeleteOnClose );
     dialog->exec();
   }
 
diff --git a/src/app/qgsfieldcalculator.cpp b/src/app/qgsfieldcalculator.cpp
index f8dc400..b26ab2a 100644
--- a/src/app/qgsfieldcalculator.cpp
+++ b/src/app/qgsfieldcalculator.cpp
@@ -43,7 +43,7 @@ QgsFieldCalculator::QgsFieldCalculator( QgsVectorLayer* vl, QWidget* parent )
   << QgsExpressionContextUtils::projectScope()
   << QgsExpressionContextUtils::layerScope( mVectorLayer );
 
-  expContext.lastScope()->setVariable( "row_number", 1 );
+  expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), 1, true ) );
   expContext.setHighlightedVariables( QStringList() << "row_number" );
 
   builder->setLayer( vl );
@@ -281,7 +281,7 @@ void QgsFieldCalculator::accept()
     while ( fit.nextFeature( feature ) )
     {
       expContext.setFeature( feature );
-      expContext.lastScope()->setVariable( QString( "row_number" ), rownum );
+      expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), rownum, true ) );
 
       QVariant value = exp.evaluate( &expContext );
       if ( exp.hasEvalError() )
diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp
index fe0557a..f884c9b 100644
--- a/src/app/qgsidentifyresultsdialog.cpp
+++ b/src/app/qgsidentifyresultsdialog.cpp
@@ -35,6 +35,7 @@
 #include "qgsvectorlayer.h"
 #include "qgswebview.h"
 #include "qgswebframe.h"
+#include "qgsfiledownloader.h"
 
 #include <QCloseEvent>
 #include <QLabel>
@@ -52,6 +53,11 @@
 #include <QDesktopServices>
 #include <QMessageBox>
 #include <QComboBox>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QRegExp>
 
 //graph
 #include <qwt_plot.h>
@@ -66,6 +72,7 @@ QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWeb
   setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
   page()->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
   // page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
+  page()->setForwardUnsupportedContent( true );
   page()->setLinkDelegationPolicy( QWebPage::DontDelegateLinks );
   settings()->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, true );
   settings()->setAttribute( QWebSettings::JavascriptCanOpenWindows, true );
@@ -73,6 +80,51 @@ QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWeb
 #ifdef QGISDEBUG
   settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
 #endif
+  connect( page(), SIGNAL( downloadRequested( QNetworkRequest ) ), this, SLOT( downloadRequested( QNetworkRequest ) ) );
+  connect( page(), SIGNAL( unsupportedContent( QNetworkReply* ) ), this, SLOT( unsupportedContent( QNetworkReply* ) ) );
+}
+
+
+void QgsIdentifyResultsWebView::downloadRequested( const QNetworkRequest &request )
+{
+  handleDownload( request.url() );
+}
+
+void QgsIdentifyResultsWebView::unsupportedContent( QNetworkReply * reply )
+{
+  handleDownload( reply->url() );
+}
+
+void QgsIdentifyResultsWebView::handleDownload( QUrl url )
+{
+  if ( ! url.isValid() )
+  {
+    QMessageBox::warning( this, tr( "Invalid URL" ), tr( "The download URL is not valid: %1" ).arg( url.toString( ) ) );
+  }
+  else
+  {
+    const QString DOWNLOADER_LAST_DIR_KEY( "Qgis/fileDownloaderLastDir" );
+    QSettings settings;
+    // Try to get some information from the URL
+    QFileInfo info( url.toString( ) );
+    QString savePath = settings.value( DOWNLOADER_LAST_DIR_KEY ).toString( );
+    QString fileName = info.fileName().replace( QRegExp( "[^A-z0-9\\-_\\.]" ), "_" );
+    if ( ! savePath.isEmpty() && ! fileName.isEmpty( ) )
+    {
+      savePath = QDir::cleanPath( savePath + QDir::separator() + fileName );
+    }
+    QString targetFile = QFileDialog::getSaveFileName( this,
+                         tr( "Save as" ),
+                         savePath,
+                         info.suffix( ).isEmpty() ? QString( ) : "*." +  info.suffix( )
+                                                     );
+    if ( ! targetFile.isEmpty() )
+    {
+      settings.setValue( DOWNLOADER_LAST_DIR_KEY, QFileInfo( targetFile ).dir().absolutePath( ) );
+      // Start the download
+      new QgsFileDownloader( url, targetFile );
+    }
+  }
 }
 
 void QgsIdentifyResultsWebView::print( void )
diff --git a/src/app/qgsidentifyresultsdialog.h b/src/app/qgsidentifyresultsdialog.h
index da68cc0..e1e95be 100644
--- a/src/app/qgsidentifyresultsdialog.h
+++ b/src/app/qgsidentifyresultsdialog.h
@@ -31,6 +31,9 @@
 
 #include <QWidget>
 #include <QList>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QUrl>
 
 class QCloseEvent;
 class QTreeWidgetItem;
@@ -57,9 +60,13 @@ class APP_EXPORT QgsIdentifyResultsWebView : public QgsWebView
     QSize sizeHint() const override;
   public slots:
     void print( void );
+    void downloadRequested( const QNetworkRequest &request );
+    void unsupportedContent( QNetworkReply *reply );
   protected:
     void contextMenuEvent( QContextMenuEvent* ) override;
     QgsWebView *createWindow( QWebPage::WebWindowType type ) override;
+  private:
+    void handleDownload( QUrl url );
 };
 
 class APP_EXPORT QgsIdentifyResultsFeatureItem: public QTreeWidgetItem
diff --git a/src/core/composer/qgscomposerattributetablev2.cpp b/src/core/composer/qgscomposerattributetablev2.cpp
index 2b58610..e88677b 100644
--- a/src/core/composer/qgscomposerattributetablev2.cpp
+++ b/src/core/composer/qgscomposerattributetablev2.cpp
@@ -503,7 +503,7 @@ bool QgsComposerAttributeTableV2::getTableContents( QgsComposerTableContents &co
       {
         // Lets assume it's an expression
         QgsExpression* expression = new QgsExpression(( *columnIt )->attribute() );
-        context->lastScope()->setVariable( QString( "row_number" ), counter + 1 );
+        context->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), counter + 1, true ) );
         expression->prepare( context.data() );
         QVariant value = expression->evaluate( context.data() );
         currentRow << value;
diff --git a/src/core/composer/qgscomposermapgrid.cpp b/src/core/composer/qgscomposermapgrid.cpp
index 9f89f73..c1911a8 100644
--- a/src/core/composer/qgscomposermapgrid.cpp
+++ b/src/core/composer/qgscomposermapgrid.cpp
@@ -1502,8 +1502,8 @@ QString QgsComposerMapGrid::gridAnnotationString( double value, QgsComposerMapGr
   }
   else if ( mGridAnnotationFormat == CustomFormat )
   {
-    expressionContext.lastScope()->setVariable( "grid_number", value );
-    expressionContext.lastScope()->setVariable( "grid_axis", coord == QgsComposerMapGrid::Longitude ? "x" : "y" );
+    expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_number" ), value, true ) );
+    expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_axis" ), coord == QgsComposerMapGrid::Longitude ? "x" : "y", true ) );
     if ( !mGridAnnotationExpression.data() )
     {
       mGridAnnotationExpression.reset( new QgsExpression( mGridAnnotationExpressionString ) );
@@ -2238,8 +2238,8 @@ QgsExpressionContext* QgsComposerMapGrid::createExpressionContext() const
 {
   QgsExpressionContext* context = QgsComposerObject::createExpressionContext();
   context->appendScope( new QgsExpressionContextScope( tr( "Grid" ) ) );
-  context->lastScope()->setVariable( "grid_number", 0 );
-  context->lastScope()->setVariable( "grid_axis", "x" );
+  context->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_number" ), 0, true ) );
+  context->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_axis" ), "x", true ) );
   context->setHighlightedVariables( QStringList() << "grid_number" << "grid_axis" );
   return context;
 }
diff --git a/src/core/geometry/qgsabstractgeometryv2.h b/src/core/geometry/qgsabstractgeometryv2.h
index f5493f9..e6d4984 100644
--- a/src/core/geometry/qgsabstractgeometryv2.h
+++ b/src/core/geometry/qgsabstractgeometryv2.h
@@ -213,7 +213,7 @@ class CORE_EXPORT QgsAbstractGeometryV2
 
     /** Returns the number of nodes contained in the geometry
      */
-    int nCoordinates() const;
+    virtual int nCoordinates() const;
 
     /** Returns the point corresponding to a specified vertex id
      */
diff --git a/src/core/geometry/qgscircularstringv2.cpp b/src/core/geometry/qgscircularstringv2.cpp
index 7245894..c88c4f7 100644
--- a/src/core/geometry/qgscircularstringv2.cpp
+++ b/src/core/geometry/qgscircularstringv2.cpp
@@ -825,6 +825,9 @@ double QgsCircularStringV2::closestSegment( const QgsPointV2& pt, QgsPointV2& se
     }
   }
 
+  if ( minDist == std::numeric_limits<double>::max() )
+    return -1; // error: no segments
+
   segmentPt = minDistSegmentPoint;
   vertexAfter = minDistVertexAfter;
   vertexAfter.part = 0;
diff --git a/src/core/geometry/qgscurvepolygonv2.cpp b/src/core/geometry/qgscurvepolygonv2.cpp
index 0393dbc..33c037f 100644
--- a/src/core/geometry/qgscurvepolygonv2.cpp
+++ b/src/core/geometry/qgscurvepolygonv2.cpp
@@ -607,11 +607,32 @@ QgsCoordinateSequenceV2 QgsCurvePolygonV2::coordinateSequence() const
   return mCoordinateSequence;
 }
 
+int QgsCurvePolygonV2::nCoordinates() const
+{
+  if ( !mCoordinateSequence.isEmpty() )
+    return QgsAbstractGeometryV2::nCoordinates();
+
+  int count = 0;
+
+  if ( mExteriorRing )
+  {
+    count += mExteriorRing->nCoordinates();
+  }
+
+  QList<QgsCurveV2*>::const_iterator it = mInteriorRings.constBegin();
+  for ( ; it != mInteriorRings.constEnd(); ++it )
+  {
+    count += ( *it )->nCoordinates();
+  }
+
+  return count;
+}
+
 double QgsCurvePolygonV2::closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt, QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const
 {
   if ( !mExteriorRing )
   {
-    return 0.0;
+    return -1;
   }
   QList<QgsCurveV2*> segmentList;
   segmentList.append( mExteriorRing );
diff --git a/src/core/geometry/qgscurvepolygonv2.h b/src/core/geometry/qgscurvepolygonv2.h
index c644d37..e8c4e94 100644
--- a/src/core/geometry/qgscurvepolygonv2.h
+++ b/src/core/geometry/qgscurvepolygonv2.h
@@ -91,6 +91,7 @@ class CORE_EXPORT QgsCurvePolygonV2: public QgsSurfaceV2
     virtual bool deleteVertex( QgsVertexId position ) override;
 
     virtual QgsCoordinateSequenceV2 coordinateSequence() const override;
+    virtual int nCoordinates() const override;
     double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const override;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const override;
 
diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp
index 8cf207b..682b066 100644
--- a/src/core/geometry/qgsgeometry.cpp
+++ b/src/core/geometry/qgsgeometry.cpp
@@ -347,6 +347,7 @@ QgsPoint QgsGeometry::closestVertex( const QgsPoint& point, int& atVertex, int&
 {
   if ( !d->geometry )
   {
+    sqrDist = -1;
     return QgsPoint( 0, 0 );
   }
 
@@ -531,12 +532,14 @@ double QgsGeometry::closestVertexWithContext( const QgsPoint& point, int& atVert
 {
   if ( !d->geometry )
   {
-    return 0.0;
+    return -1;
   }
 
   QgsVertexId vId;
   QgsPointV2 pt( point.x(), point.y() );
   QgsPointV2 closestPoint = QgsGeometryUtils::closestVertex( *( d->geometry ), pt, vId );
+  if ( !vId.isValid() )
+    return -1;
   atVertex = vertexNrFromVertexId( vId );
   return QgsGeometryUtils::sqrDistance2D( closestPoint, pt );
 }
@@ -550,7 +553,7 @@ double QgsGeometry::closestSegmentWithContext(
 {
   if ( !d->geometry )
   {
-    return 0;
+    return -1;
   }
 
   QgsPointV2 segmentPt;
@@ -558,6 +561,8 @@ double QgsGeometry::closestSegmentWithContext(
   bool leftOfBool;
 
   double sqrDist = d->geometry->closestSegment( QgsPointV2( point.x(), point.y() ), segmentPt,  vertexAfter, &leftOfBool, epsilon );
+  if ( sqrDist < 0 )
+    return -1;
 
   minDistPoint.setX( segmentPt.x() );
   minDistPoint.setY( segmentPt.y() );
diff --git a/src/core/geometry/qgsgeometrycollectionv2.cpp b/src/core/geometry/qgsgeometrycollectionv2.cpp
index 2cec10c..f545d18 100644
--- a/src/core/geometry/qgsgeometrycollectionv2.cpp
+++ b/src/core/geometry/qgsgeometrycollectionv2.cpp
@@ -361,6 +361,22 @@ QgsCoordinateSequenceV2 QgsGeometryCollectionV2::coordinateSequence() const
   return mCoordinateSequence;
 }
 
+int QgsGeometryCollectionV2::nCoordinates() const
+{
+  if ( !mCoordinateSequence.isEmpty() )
+    return QgsAbstractGeometryV2::nCoordinates();
+
+  int count = 0;
+
+  QVector< QgsAbstractGeometryV2* >::const_iterator geomIt = mGeometries.constBegin();
+  for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
+  {
+    count += ( *geomIt )->nCoordinates();
+  }
+
+  return count;
+}
+
 double QgsGeometryCollectionV2::closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const
 {
   return QgsGeometryUtils::closestSegmentFromComponents( mGeometries, QgsGeometryUtils::PART, pt, segmentPt, vertexAfter, leftOf, epsilon );
diff --git a/src/core/geometry/qgsgeometrycollectionv2.h b/src/core/geometry/qgsgeometrycollectionv2.h
index 12c0c48..77e7e86 100644
--- a/src/core/geometry/qgsgeometrycollectionv2.h
+++ b/src/core/geometry/qgsgeometrycollectionv2.h
@@ -93,6 +93,8 @@ class CORE_EXPORT QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     virtual QgsRectangle boundingBox() const override;
 
     virtual QgsCoordinateSequenceV2 coordinateSequence() const override;
+    virtual int nCoordinates() const override;
+
     virtual double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const override;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const override;
 
diff --git a/src/core/geometry/qgsgeometryutils.cpp b/src/core/geometry/qgsgeometryutils.cpp
index 19e60d1..f913c0a 100644
--- a/src/core/geometry/qgsgeometryutils.cpp
+++ b/src/core/geometry/qgsgeometryutils.cpp
@@ -65,6 +65,7 @@ QgsPointV2 QgsGeometryUtils::closestVertex( const QgsAbstractGeometryV2& geom, c
   double minDist = std::numeric_limits<double>::max();
   double currentDist = 0;
   QgsPointV2 minDistPoint;
+  id = QgsVertexId(); // set as invalid
 
   QgsVertexId vertexId;
   QgsPointV2 vertex;
diff --git a/src/core/geometry/qgsgeometryutils.h b/src/core/geometry/qgsgeometryutils.h
index 667fae1..7d7bd8d 100644
--- a/src/core/geometry/qgsgeometryutils.h
+++ b/src/core/geometry/qgsgeometryutils.h
@@ -37,7 +37,8 @@ class CORE_EXPORT QgsGeometryUtils
      */
     static QList<QgsLineStringV2*> extractLineStrings( const QgsAbstractGeometryV2* geom );
 
-    /** Returns the closest vertex to a geometry for a specified point
+    /** Returns the closest vertex to a geometry for a specified point.
+     * On error null point will be returned and "id" argument will be invalid.
      */
     static QgsPointV2 closestVertex( const QgsAbstractGeometryV2& geom, const QgsPointV2& pt, QgsVertexId& id );
 
@@ -227,7 +228,7 @@ class CORE_EXPORT QgsGeometryUtils
       for ( int i = 0; i < container.size(); ++i )
       {
         sqrDist = container.at( i )->closestSegment( pt, segmentPt, vertexAfter, leftOf, epsilon );
-        if ( sqrDist < minDist )
+        if ( sqrDist >= 0 && sqrDist < minDist )
         {
           minDist = sqrDist;
           minDistSegmentX = segmentPt.x();
@@ -257,6 +258,9 @@ class CORE_EXPORT QgsGeometryUtils
         }
       }
 
+      if ( minDist == std::numeric_limits<double>::max() )
+        return -1;  // error: no segments
+
       segmentPt.setX( minDistSegmentX );
       segmentPt.setY( minDistSegmentY );
       vertexAfter = minDistVertexAfter;
diff --git a/src/core/geometry/qgslinestringv2.cpp b/src/core/geometry/qgslinestringv2.cpp
index 13534a8..d39b61f 100644
--- a/src/core/geometry/qgslinestringv2.cpp
+++ b/src/core/geometry/qgslinestringv2.cpp
@@ -744,16 +744,10 @@ double QgsLineStringV2::closestSegment( const QgsPointV2& pt, QgsPointV2& segmen
   double segmentPtX, segmentPtY;
 
   int size = mX.size();
-  if ( size == 0 )
+  if ( size == 0 || size == 1 )
   {
     vertexAfter = QgsVertexId( 0, 0, 0 );
-    return sqrDist;
-  }
-  else if ( size == 1 )
-  {
-    segmentPt = pointN( 0 );
-    vertexAfter = QgsVertexId( 0, 0, 1 );
-    return QgsGeometryUtils::sqrDistance2D( pt, segmentPt );
+    return -1;
   }
   for ( int i = 1; i < size; ++i )
   {
diff --git a/src/core/geometry/qgslinestringv2.h b/src/core/geometry/qgslinestringv2.h
index 16677d5..f43a915 100644
--- a/src/core/geometry/qgslinestringv2.h
+++ b/src/core/geometry/qgslinestringv2.h
@@ -159,6 +159,7 @@ class CORE_EXPORT QgsLineStringV2: public QgsCurveV2
     virtual QgsLineStringV2* curveToLine() const override;
 
     int numPoints() const override;
+    virtual int nCoordinates() const override { return mX.size(); }
     void points( QgsPointSequenceV2 &pt ) const override;
 
     void draw( QPainter& p ) const override;
diff --git a/src/core/geometry/qgsmultipointv2.h b/src/core/geometry/qgsmultipointv2.h
index 977a2e7..67e5566 100644
--- a/src/core/geometry/qgsmultipointv2.h
+++ b/src/core/geometry/qgsmultipointv2.h
@@ -39,6 +39,7 @@ class CORE_EXPORT QgsMultiPointV2: public QgsGeometryCollectionV2
     QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const override;
     QString asJSON( int precision = 17 ) const override;
 
+    virtual int nCoordinates() const override { return mGeometries.size(); }
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
     virtual bool addGeometry( QgsAbstractGeometryV2* g ) override;
diff --git a/src/core/geometry/qgspointv2.h b/src/core/geometry/qgspointv2.h
index 2ed1e64..ac2900c 100644
--- a/src/core/geometry/qgspointv2.h
+++ b/src/core/geometry/qgspointv2.h
@@ -168,6 +168,7 @@ class CORE_EXPORT QgsPointV2: public QgsAbstractGeometryV2
     void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform ) override;
     void transform( const QTransform& t ) override;
     virtual QgsCoordinateSequenceV2 coordinateSequence() const override;
+    virtual int nCoordinates() const override { return 1; }
 
     //low-level editing
     virtual bool insertVertex( QgsVertexId position, const QgsPointV2& vertex ) override { Q_UNUSED( position ); Q_UNUSED( vertex ); return false; }
diff --git a/src/core/qgsconditionalstyle.cpp b/src/core/qgsconditionalstyle.cpp
index 3cfa6b5..169af65 100644
--- a/src/core/qgsconditionalstyle.cpp
+++ b/src/core/qgsconditionalstyle.cpp
@@ -195,7 +195,7 @@ void QgsConditionalStyle::setSymbol( QgsSymbolV2* value )
 bool QgsConditionalStyle::matches( const QVariant& value, QgsExpressionContext& context ) const
 {
   QgsExpression exp( mRule );
-  context.lastScope()->setVariable( "value", value );
+  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "value" ), value, true ) );
   return exp.evaluate( &context ).toBool();
 }
 
diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp
index 2b2c98a..8a09c1a 100644
--- a/src/core/qgsexpression.cpp
+++ b/src/core/qgsexpression.cpp
@@ -4313,7 +4313,7 @@ QVariant QgsExpression::NodeColumnRef::eval( QgsExpression *parent, const QgsExp
 
   if ( context && context->hasVariable( QgsExpressionContext::EXPR_FEATURE ) )
   {
-    QgsFeature feature = qvariant_cast<QgsFeature>( context->variable( QgsExpressionContext::EXPR_FEATURE ) );
+    QgsFeature feature = context->feature();
     if ( index >= 0 )
       return feature.attribute( index );
     else
diff --git a/src/core/qgsexpressioncontext.cpp b/src/core/qgsexpressioncontext.cpp
index c5512cc..501fc12 100644
--- a/src/core/qgsexpressioncontext.cpp
+++ b/src/core/qgsexpressioncontext.cpp
@@ -191,12 +191,12 @@ void QgsExpressionContextScope::addFunction( const QString& name, QgsScopedExpre
 
 void QgsExpressionContextScope::setFeature( const QgsFeature &feature )
 {
-  setVariable( QgsExpressionContext::EXPR_FEATURE, QVariant::fromValue( feature ) );
+  addVariable( StaticVariable( QgsExpressionContext::EXPR_FEATURE, QVariant::fromValue( feature ), true ) );
 }
 
 void QgsExpressionContextScope::setFields( const QgsFields &fields )
 {
-  setVariable( QgsExpressionContext::EXPR_FIELDS, QVariant::fromValue( fields ) );
+  addVariable( StaticVariable( QgsExpressionContext::EXPR_FIELDS, QVariant::fromValue( fields ), true ) );
 }
 
 
diff --git a/src/core/qgsogcutils.cpp b/src/core/qgsogcutils.cpp
index 05e4852..46e254b 100644
--- a/src/core/qgsogcutils.cpp
+++ b/src/core/qgsogcutils.cpp
@@ -1669,7 +1669,7 @@ static bool isSpatialOperator( const QString& tagName )
   static QStringList spatialOps;
   if ( spatialOps.isEmpty() )
   {
-    spatialOps << "BBOX" << "Intersects" << "Contians" << "Crosses" << "Equals"
+    spatialOps << "BBOX" << "Intersects" << "Contains" << "Crosses" << "Equals"
     << "Disjoint" << "Overlaps" << "Touches" << "Within";
   }
 
diff --git a/src/core/qgspointlocator.cpp b/src/core/qgspointlocator.cpp
index 66b2647..ecad55e 100644
--- a/src/core/qgspointlocator.cpp
+++ b/src/core/qgspointlocator.cpp
@@ -100,6 +100,8 @@ class QgsPointLocator_VisitorNearestVertex : public IVisitor
       int vertexIndex, beforeVertex, afterVertex;
       double sqrDist;
       QgsPoint pt = geom->closestVertex( mSrcPoint, vertexIndex, beforeVertex, afterVertex, sqrDist );
+      if ( sqrDist < 0 )
+        return;  // probably empty geometry
 
       QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, sqrt( sqrDist ), pt, vertexIndex );
       // in range queries the filter may reject some matches
diff --git a/src/core/qgsvectorlayerrenderer.cpp b/src/core/qgsvectorlayerrenderer.cpp
index 6c7e477..126869b 100644
--- a/src/core/qgsvectorlayerrenderer.cpp
+++ b/src/core/qgsvectorlayerrenderer.cpp
@@ -333,7 +333,7 @@ void QgsVectorLayerRenderer::drawRendererV2( QgsFeatureIterator& fit )
           }
         }
         // new labeling engine
-        if ( mContext.labelingEngineV2() )
+        if ( mContext.labelingEngineV2() && ( mLabelProvider || mDiagramProvider ) )
         {
           QScopedPointer<QgsGeometry> obstacleGeometry;
           QgsSymbolV2List symbols = mRendererV2->originalSymbolsForFeature( fet, mContext );
diff --git a/src/core/qgswebpage.h b/src/core/qgswebpage.h
index c5de9dc..f84ca7f 100644
--- a/src/core/qgswebpage.h
+++ b/src/core/qgswebpage.h
@@ -174,6 +174,10 @@ class CORE_EXPORT QWebPage : public QObject
       return new QMenu();
     }
 
+    void setForwardUnsupportedContent( bool )
+    {
+    }
+
   signals:
 
   public slots:
diff --git a/src/core/symbology-ng/qgssymbolv2.cpp b/src/core/symbology-ng/qgssymbolv2.cpp
index aa3a84b..45a1f83 100644
--- a/src/core/symbology-ng/qgssymbolv2.cpp
+++ b/src/core/symbology-ng/qgssymbolv2.cpp
@@ -738,8 +738,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
   {
     context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
     QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
-    mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, segmentizedGeometry->geometry()->partCount() );
-    mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1 );
+    mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, segmentizedGeometry->geometry()->partCount(), true ) );
+    mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1, true ) );
   }
 
   switch ( QgsWKBTypes::flatType( segmentizedGeometry->geometry()->wkbType() ) )
@@ -806,7 +806,7 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
 
       for ( int i = 0; i < mp->numGeometries(); ++i )
       {
-        mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1 );
+        mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1, true ) );
 
         const QgsPointV2* point = static_cast< const QgsPointV2* >( mp->geometryN( i ) );
         _getPoint( pt, context, point );
@@ -836,7 +836,7 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
 
       for ( unsigned int i = 0; i < num && wkbPtr; ++i )
       {
-        mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1 );
+        mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1, true ) );
 
         if ( geomCollection )
         {
@@ -870,7 +870,7 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
 
       for ( unsigned int i = 0; i < num && wkbPtr; ++i )
       {
-        mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1 );
+        mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1, true ) );
 
         if ( geomCollection )
         {
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 151da0e..435dc89 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -272,6 +272,7 @@ SET(QGIS_GUI_SRCS
   qgsuserinputdockwidget.cpp
   qgsvariableeditorwidget.cpp
   qgsvertexmarker.cpp
+  qgsfiledownloader.cpp
 )
 
 IF (WITH_QTWEBKIT)
@@ -403,6 +404,7 @@ SET(QGIS_GUI_MOC_HDRS
   qgsunitselectionwidget.h
   qgsuserinputdockwidget.h
   qgsvariableeditorwidget.h
+  qgsfiledownloader.h
 
   raster/qgsmultibandcolorrendererwidget.h
   raster/qgspalettedrendererwidget.h
@@ -579,6 +581,7 @@ SET(QGIS_GUI_HDRS
   qgsuserinputdockwidget.h
   qgsvectorlayertools.h
   qgsvertexmarker.h
+  qgsfiledownloader.h
 
   attributetable/qgsfeaturemodel.h
 
diff --git a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
index ffc158e..ded4665 100644
--- a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
+++ b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
@@ -63,7 +63,7 @@ void QgsFieldConditionalFormatWidget::setExpression()
   context << QgsExpressionContextUtils::globalScope()
   << QgsExpressionContextUtils::projectScope()
   << QgsExpressionContextUtils::layerScope( mLayer );
-  context.lastScope()->setVariable( "value", 0 );
+  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "value" ), 0, true ) );
   context.setHighlightedVariables( QStringList() << "value" );
 
   QgsExpressionBuilderDialog dlg( mLayer, mRuleEdit->text(), this, "generic", context );
diff --git a/src/gui/layertree/qgslayertreeview.cpp b/src/gui/layertree/qgslayertreeview.cpp
index 7530c26..74b298b 100644
--- a/src/gui/layertree/qgslayertreeview.cpp
+++ b/src/gui/layertree/qgslayertreeview.cpp
@@ -313,3 +313,49 @@ void QgsLayerTreeView::refreshLayerSymbology( const QString& layerId )
   if ( nodeLayer )
     layerTreeModel()->refreshLayerLegend( nodeLayer );
 }
+
+
+static void _expandAllLegendNodes( QgsLayerTreeLayer* nodeLayer, bool expanded, QgsLayerTreeModel* model )
+{
+  // for layers we also need to find out with legend nodes contain some children and make them expanded/collapsed
+  // if we are collapsing, we just write out an empty list
+  QStringList lst;
+  if ( expanded )
+  {
+    Q_FOREACH ( QgsLayerTreeModelLegendNode* legendNode, model->layerLegendNodes( nodeLayer ) )
+    {
+      QString parentKey = legendNode->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString();
+      if ( !parentKey.isEmpty() && !lst.contains( parentKey ) )
+        lst << parentKey;
+    }
+  }
+  nodeLayer->setCustomProperty( "expandedLegendNodes", lst );
+}
+
+
+static void _expandAllNodes( QgsLayerTreeGroup* parent, bool expanded, QgsLayerTreeModel* model )
+{
+  Q_FOREACH ( QgsLayerTreeNode* node, parent->children() )
+  {
+    node->setExpanded( expanded );
+    if ( QgsLayerTree::isGroup( node ) )
+      _expandAllNodes( QgsLayerTree::toGroup( node ), expanded, model );
+    else if ( QgsLayerTree::isLayer( node ) )
+      _expandAllLegendNodes( QgsLayerTree::toLayer( node ), expanded, model );
+  }
+}
+
+
+void QgsLayerTreeView::expandAllNodes()
+{
+  // unfortunately expandAll() does not emit expanded() signals
+  _expandAllNodes( layerTreeModel()->rootGroup(), true, layerTreeModel() );
+  expandAll();
+}
+
+void QgsLayerTreeView::collapseAllNodes()
+{
+  // unfortunately collapseAll() does not emit collapsed() signals
+  _expandAllNodes( layerTreeModel()->rootGroup(), false, layerTreeModel() );
+  collapseAll();
+}
diff --git a/src/gui/layertree/qgslayertreeview.h b/src/gui/layertree/qgslayertreeview.h
index bf3fe5f..2a3878c 100644
--- a/src/gui/layertree/qgslayertreeview.h
+++ b/src/gui/layertree/qgslayertreeview.h
@@ -91,6 +91,14 @@ class GUI_EXPORT QgsLayerTreeView : public QTreeView
     //! Force refresh of layer symbology. Normally not needed as the changes of layer's renderer are monitored by the model
     void refreshLayerSymbology( const QString& layerId );
 
+    //! Enhancement of QTreeView::expandAll() that also records expanded state in layer tree nodes
+    //! @note added in QGIS 2.18
+    void expandAllNodes();
+
+    //! Enhancement of QTreeView::collapseAll() that also records expanded state in layer tree nodes
+    //! @note added in QGIS 2.18
+    void collapseAllNodes();
+
   signals:
     //! Emitted when a current layer is changed
     void currentLayerChanged( QgsMapLayer* layer );
diff --git a/src/gui/qgsattributedialog.cpp b/src/gui/qgsattributedialog.cpp
index ee1b9cf..ee71f2e 100644
--- a/src/gui/qgsattributedialog.cpp
+++ b/src/gui/qgsattributedialog.cpp
@@ -87,11 +87,8 @@ void QgsAttributeDialog::accept()
   QDialog::accept();
 }
 
-void QgsAttributeDialog::show( bool autoDelete )
+void QgsAttributeDialog::show()
 {
-  if ( autoDelete )
-    setAttribute( Qt::WA_DeleteOnClose );
-
   QDialog::show();
   raise();
   activateWindow();
diff --git a/src/gui/qgsattributedialog.h b/src/gui/qgsattributedialog.h
index e7dd32b..4263f32 100644
--- a/src/gui/qgsattributedialog.h
+++ b/src/gui/qgsattributedialog.h
@@ -130,9 +130,8 @@ class GUI_EXPORT QgsAttributeDialog : public QDialog
   public slots:
     void accept() override;
 
-    //! Show the dialog non-blocking. Reparents this dialog to be a child of the dialog form and is deleted when
-    //! closed.
-    void show( bool autoDelete = true );
+    //! Show the dialog non-blocking. Reparents this dialog to be a child of the dialog form
+    void show();
 
   private:
     void init( QgsVectorLayer* layer, QgsFeature* feature, const QgsAttributeEditorContext& context );
diff --git a/src/gui/qgsextentgroupbox.h b/src/gui/qgsextentgroupbox.h
index b8ca133..19c545e 100644
--- a/src/gui/qgsextentgroupbox.h
+++ b/src/gui/qgsextentgroupbox.h
@@ -70,7 +70,7 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
     //! set output extent to be the same as current extent (may be transformed to output CRS)
     void setOutputExtentFromCurrent();
 
-    //! set output extent to custom extent (may be transformed to outut CRS)
+    //! set output extent to custom extent (may be transformed to output CRS)
     void setOutputExtentFromUser( const QgsRectangle& extent, const QgsCoordinateReferenceSystem& crs );
 
   signals:
diff --git a/src/gui/qgsfiledownloader.cpp b/src/gui/qgsfiledownloader.cpp
new file mode 100644
index 0000000..36cde63
--- /dev/null
+++ b/src/gui/qgsfiledownloader.cpp
@@ -0,0 +1,203 @@
+/***************************************************************************
+  qgsfiledownloader.cpp
+  --------------------------------------
+  Date                 : November 2016
+  Copyright            : (C) 2016 by Alessandro Pasotti
+  Email                : apasotti at boundlessgeo dot com
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "qgsfiledownloader.h"
+#include "qgsnetworkaccessmanager.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QMessageBox>
+#ifndef QT_NO_OPENSSL
+#include <QSslError>
+#endif
+
+QgsFileDownloader::QgsFileDownloader( QUrl url, QString outputFileName, bool enableGuiNotifications )
+    : mUrl( url )
+    , mReply( nullptr )
+    , mProgressDialog( nullptr )
+    , mDownloadCanceled( false )
+    , mErrors()
+    , mGuiNotificationsEnabled( enableGuiNotifications )
+{
+  mFile.setFileName( outputFileName );
+  startDownload();
+}
+
+
+QgsFileDownloader::~QgsFileDownloader()
+{
+  if ( mReply )
+  {
+    mReply->abort();
+    mReply->deleteLater();
+  }
+  if ( mProgressDialog )
+  {
+    mProgressDialog->deleteLater();
+  }
+}
+
+
+void QgsFileDownloader::startDownload()
+{
+  QgsNetworkAccessManager* nam = QgsNetworkAccessManager::instance();
+
+  QNetworkRequest request( mUrl );
+
+  mReply = nam->get( request );
+
+  connect( mReply, SIGNAL( readyRead() ), this, SLOT( onReadyRead() ) );
+  connect( mReply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( onNetworkError( QNetworkReply::NetworkError ) ) );
+  connect( mReply, SIGNAL( finished() ), this, SLOT( onFinished() ) );
+  connect( mReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( onDownloadProgress( qint64, qint64 ) ) );
+  connect( nam, SIGNAL( requestTimedOut( QNetworkReply* ) ), this, SLOT( onRequestTimedOut() ) );
+#ifndef QT_NO_OPENSSL
+  connect( nam, SIGNAL( sslErrors( QNetworkReply*, QList<QSslError> ) ), this, SLOT( onSslErrors( QNetworkReply*, QList<QSslError> ) ) );
+#endif
+  if ( mGuiNotificationsEnabled )
+  {
+    mProgressDialog = new QProgressDialog();
+    mProgressDialog->setWindowTitle( tr( "Download" ) );
+    mProgressDialog->setLabelText( tr( "Downloading %1." ).arg( mFile.fileName() ) );
+    mProgressDialog->show();
+    connect( mProgressDialog, SIGNAL( canceled() ), this, SLOT( onDownloadCanceled() ) );
+  }
+}
+
+void QgsFileDownloader::onDownloadCanceled()
+{
+  mDownloadCanceled = true;
+  emit downloadCanceled();
+  onFinished();
+}
+
+void QgsFileDownloader::onRequestTimedOut()
+{
+  error( tr( "Network request %1 timed out" ).arg( mUrl.toString() ) );
+}
+
+#ifndef QT_NO_OPENSSL
+void QgsFileDownloader::onSslErrors( QNetworkReply *reply, const QList<QSslError> &errors )
+{
+  Q_UNUSED( reply );
+  QStringList errorMessages;
+  errorMessages <<  "SSL Errors: ";
+  for ( int end = errors.size(), i = 0; i != end; ++i )
+  {
+    errorMessages << errors[i].errorString();
+  }
+  error( errorMessages );
+}
+#endif
+
+
+void QgsFileDownloader::error( QStringList errorMessages )
+{
+  for ( int end = errorMessages.size(), i = 0; i != end; ++i )
+  {
+    mErrors.append( errorMessages[i] );
+  }
+  // Show error
+  if ( mGuiNotificationsEnabled )
+  {
+    QMessageBox::warning( nullptr, tr( "Download failed" ), mErrors.join( "<br>" ) );
+  }
+  emit downloadError( mErrors );
+}
+
+void QgsFileDownloader::error( QString errorMessage )
+{
+  error( QStringList() << errorMessage );
+}
+
+void QgsFileDownloader::onReadyRead()
+{
+  Q_ASSERT( mReply );
+  if ( ! mFile.isOpen() && ! mFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
+  {
+    error( tr( "Cannot open output file: %1" ).arg( mFile.fileName() ) );
+    onFinished();
+  }
+  else
+  {
+    QByteArray data = mReply->readAll();
+    mFile.write( data );
+  }
+}
+
+void QgsFileDownloader::onFinished()
+{
+  // when canceled
+  if ( ! mErrors.isEmpty() || mDownloadCanceled )
+  {
+    mFile.close();
+    mFile.remove();
+    if ( mGuiNotificationsEnabled )
+      mProgressDialog->hide();
+  }
+  else
+  {
+    // download finished normally
+    if ( mGuiNotificationsEnabled )
+      mProgressDialog->hide();
+    mFile.flush();
+    mFile.close();
+
+    // get redirection url
+    QVariant redirectionTarget = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
+    if ( mReply->error() )
+    {
+      mFile.remove();
+      error( tr( "Download failed: %1." ).arg( mReply->errorString() ) );
+    }
+    else if ( !redirectionTarget.isNull() )
+    {
+      QUrl newUrl = mUrl.resolved( redirectionTarget.toUrl() );
+      mUrl = newUrl;
+      mReply->deleteLater();
+      mFile.open( QIODevice::WriteOnly );
+      mFile.resize( 0 );
+      mFile.close();
+      startDownload();
+      return;
+    }
+    // All done
+    emit downloadCompleted();
+  }
+  emit downloadExited();
+  this->deleteLater();
+}
+
+void QgsFileDownloader::onNetworkError( QNetworkReply::NetworkError err )
+{
+  Q_ASSERT( mReply );
+  error( QString( "Network error %1: %2" ).arg( err ).arg( mReply->errorString() ) );
+}
+
+void QgsFileDownloader::onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
+{
+  if ( mDownloadCanceled )
+  {
+    return;
+  }
+  if ( mGuiNotificationsEnabled )
+  {
+    mProgressDialog->setMaximum( bytesTotal );
+    mProgressDialog->setValue( bytesReceived );
+  }
+  emit downloadProgress( bytesReceived, bytesTotal );
+}
+
diff --git a/src/gui/qgsfiledownloader.h b/src/gui/qgsfiledownloader.h
new file mode 100644
index 0000000..62e6d29
--- /dev/null
+++ b/src/gui/qgsfiledownloader.h
@@ -0,0 +1,112 @@
+/***************************************************************************
+  qgsfiledownloader.h
+  --------------------------------------
+  Date                 : November 2016
+  Copyright            : (C) 2016 by Alessandro Pasotti
+  Email                : apasotti at boundlessgeo dot com
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef QGSFILEDOWNLOADER_H
+#define QGSFILEDOWNLOADER_H
+
+#include <QObject>
+#include <QFile>
+#include <QNetworkReply>
+#include <QProgressDialog>
+#ifndef QT_NO_OPENSSL
+#include <QSslError>
+#endif
+
+/** \ingroup gui
+ * QgsFileDownloader is a utility class for downloading files.
+ *
+ * To use this class, it is necessary to pass the URL and an output file name as
+ * arguments to the constructor, the download will start immediately.
+ * The download is asynchronous and depending on the guiNotificationsEnabled
+ * parameter accepted by the constructor (default = true) the class will
+ * show a progress dialog and report all errors in a QMessageBox::warning dialog.
+ * If the guiNotificationsEnabled parameter is set to false, the class can still
+ * be used through the signals and slots mechanism.
+ * The object will destroy itself when the request completes, errors or is canceled.
+ *
+ * @note added in QGIS 2.18.1
+ */
+class GUI_EXPORT QgsFileDownloader : public QObject
+{
+    Q_OBJECT
+  public:
+    /**
+     * QgsFileDownloader
+     * @param url the download url
+     * @param outputFileName file name where the downloaded content will be stored
+     * @param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
+     */
+    QgsFileDownloader( QUrl url, QString outputFileName, bool guiNotificationsEnabled = true );
+
+  signals:
+    /** Emitted when the download has completed successfully */
+    void downloadCompleted();
+    /** Emitted always when the downloader exits  */
+    void downloadExited();
+    /** Emitted when the download was canceled by the user */
+    void downloadCanceled();
+    /** Emitted when an error makes the download fail  */
+    void downloadError( QStringList errorMessages );
+    /** Emitted when data ready to be processed */
+    void downloadProgress( qint64 bytesReceived, qint64 bytesTotal );
+
+  public slots:
+    /**
+     * Called when a download is canceled by the user
+     * this slot aborts the download and deletes
+     * the object
+     */
+    void onDownloadCanceled();
+
+  private slots:
+    /** Called when the network reply data are ready */
+    void onReadyRead();
+    /** Called when the network reply has finished */
+    void onFinished();
+    /** Called on Network Error */
+    void onNetworkError( QNetworkReply::NetworkError err );
+    /** Called on data ready to be processed */
+    void onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal );
+    /** Called when a network request times out  */
+    void onRequestTimedOut();
+    /** Called to start the download */
+    void startDownload();
+#ifndef QT_NO_OPENSSL
+    /**
+     * Called on SSL network Errors
+     * @param reply
+     * @param errors
+     */
+    void onSslErrors( QNetworkReply *reply, const QList<QSslError> &errors );
+#endif
+
+  private:
+    ~QgsFileDownloader();
+    /**
+     * Abort current request and show an error if the instance has GUI
+     * notifications enabled.
+     */
+    void error( QStringList errorMessages );
+    void error( QString errorMessage );
+    QUrl mUrl;
+    QNetworkReply* mReply;
+    QFile mFile;
+    QProgressDialog* mProgressDialog;
+    bool mDownloadCanceled;
+    QStringList mErrors;
+    bool mGuiNotificationsEnabled;
+};
+
+#endif // QGSFILEDOWNLOADER_H
diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp
index 856d16b..7308b05 100644
--- a/src/gui/qgsmapcanvas.cpp
+++ b/src/gui/qgsmapcanvas.cpp
@@ -663,9 +663,6 @@ void QgsMapCanvas::refreshMap()
 
   stopRendering(); // if any...
 
-  // from now on we can accept refresh requests again
-  mRefreshScheduled = false;
-
   //build the expression context
   QgsExpressionContext expressionContext;
   expressionContext << QgsExpressionContextUtils::globalScope()
@@ -698,6 +695,14 @@ void QgsMapCanvas::refreshMap()
 
   mJob->start();
 
+  // from now on we can accept refresh requests again
+  // this must be reset only after the job has been started, because
+  // some providers (yes, it's you WCS and AMS!) during preparation
+  // do network requests and start an internal event loop, which may
+  // end up calling refresh() and would schedule another refresh,
+  // deleting the one we have just started.
+  mRefreshScheduled = false;
+
   mMapUpdateTimer.start();
 
   emit renderStarting();
diff --git a/src/gui/symbology-ng/qgssymbollayerv2widget.cpp b/src/gui/symbology-ng/qgssymbollayerv2widget.cpp
index ed76136..dab2ceb 100644
--- a/src/gui/symbology-ng/qgssymbollayerv2widget.cpp
+++ b/src/gui/symbology-ng/qgssymbollayerv2widget.cpp
@@ -80,7 +80,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
   {
     //cheat a bit - set the symbol color variable to match the symbol layer's color (when we should really be using the *symbols*
     //color, but that's not accessible here). 99% of the time these will be the same anyway
-    symbolScope->setVariable( QgsExpressionContext::EXPR_SYMBOL_COLOR, symbolLayer->color() );
+    symbolScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_SYMBOL_COLOR, symbolLayer->color(), true ) );
   }
   expContext << symbolScope;
   expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, 1, true ) );
diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp
index 850a5ab..4335927 100644
--- a/src/providers/ogr/qgsogrprovider.cpp
+++ b/src/providers/ogr/qgsogrprovider.cpp
@@ -1153,8 +1153,12 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
 
   bool returnvalue = true;
 
+  QMap< QString, QgsField > mapFieldNameToOriginalField;
+
   for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
   {
+    mapFieldNameToOriginalField[ iter->name()] = *iter;
+
     OGRFieldType type;
 
     switch ( iter->type() )
@@ -1164,8 +1168,16 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
         break;
 #if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 2000000
       case QVariant::LongLong:
-        type = OFTInteger64;
+      {
+        const char* pszDataTypes = GDALGetMetadataItem( ogrDriver, GDAL_DMD_CREATIONFIELDDATATYPES, NULL );
+        if ( pszDataTypes && strstr( pszDataTypes, "Integer64" ) )
+          type = OFTInteger64;
+        else
+        {
+          type = OFTReal;
+        }
         break;
+      }
 #endif
       case QVariant::Double:
         type = OFTReal;
@@ -1203,6 +1215,26 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
     OGR_Fld_Destroy( fielddefn );
   }
   loadFields();
+
+  // The check in QgsVectorLayerEditBuffer::commitChanges() is questionable with
+  // real-world drivers that might only be able to satisfy request only partially.
+  // So to avoid erroring out, patch field type, width and precision to match
+  // what was requested.
+  // For example in case of Integer64->Real mapping so that QVariant::LongLong is
+  // still returned to the caller
+  // Or if a field width was specified but not strictly enforced by the driver (#15614)
+  for ( QMap< QString, QgsField >::const_iterator it = mapFieldNameToOriginalField.begin();
+        it != mapFieldNameToOriginalField.end(); ++it )
+  {
+    int idx = mAttributeFields.fieldNameIndex( it.key() );
+    if ( idx >= 0 )
+    {
+      mAttributeFields[ idx ].setType( it->type() );
+      mAttributeFields[ idx ].setLength( it->length() );
+      mAttributeFields[ idx ].setPrecision( it->precision() );
+    }
+  }
+
   return returnvalue;
 }
 
diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp
index 3d03f3d..ca9316f 100644
--- a/src/providers/oracle/qgsoracleprovider.cpp
+++ b/src/providers/oracle/qgsoracleprovider.cpp
@@ -581,13 +581,14 @@ bool QgsOracleProvider::loadFields()
 
     qry.finish();
 
-    if ( exec( qry, QString( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=%1 AND t.table_name=%2 AND t.column_name<>%3" )
+    if ( exec( qry, QString( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=%1 AND t.table_name=%2" )
                .arg( quotedValue( mOwnerName ) )
-               .arg( quotedValue( mTableName ) )
-               .arg( quotedValue( mGeometryColumn ) ) ) )
+               .arg( quotedValue( mTableName ) ) ) )
     {
       while ( qry.next() )
       {
+        if ( qry.value( 0 ).toString() == mGeometryColumn )
+          continue;
         comments.insert( qry.value( 0 ).toString(), qry.value( 1 ).toString() );
       }
     }
@@ -2004,7 +2005,7 @@ bool QgsOracleProvider::setSubsetString( const QString& theSQL, bool updateFeatu
   }
   qry.finish();
 
-  if ( mPrimaryKeyType == pktInt && !uniqueData( mQuery, mAttributeFields[ mPrimaryKeyAttrs[0] ].name() ) )
+  if ( mPrimaryKeyType == pktInt && !mUseEstimatedMetadata && !uniqueData( mQuery, mAttributeFields[ mPrimaryKeyAttrs[0] ].name() ) )
   {
     mSqlWhereClause = prevWhere;
     return false;
diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp
index 63468b3..1033f85 100644
--- a/src/providers/spatialite/qgsspatialiteprovider.cpp
+++ b/src/providers/spatialite/qgsspatialiteprovider.cpp
@@ -3653,9 +3653,6 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList & flist )
 
     for ( int i = 0; i < attributevec.count(); ++i )
     {
-      if ( !attributevec.at( i ).isValid() )
-        continue;
-
       if ( i >= attributeFields.count() )
         continue;
 
@@ -3711,8 +3708,6 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList & flist )
         for ( int i = 0; i < attributevec.count(); ++i )
         {
           QVariant v = attributevec.at( i );
-          if ( !v.isValid() )
-            continue;
 
           // binding values for each attribute
           if ( i >= attributeFields.count() )
@@ -3724,7 +3719,11 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList & flist )
 
           QVariant::Type type = attributeFields.at( i ).type();
 
-          if ( v.isNull() )
+          if ( !v.isValid() )
+          {
+            ++ia;
+          }
+          else if ( v.isNull() )
           {
             // binding a NULL value
             sqlite3_bind_null( stmt, ++ia );
diff --git a/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp b/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
index 8bcc0f4..8f875a8 100644
--- a/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
+++ b/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
@@ -30,7 +30,7 @@ QgsScopedSqlite::QgsScopedSqlite( const QString& path, bool withExtension )
     sqlite3_auto_extension( reinterpret_cast < void( * )() > ( qgsvlayerModuleInit ) );
   }
   int r;
-  r = sqlite3_open( path.toLocal8Bit().constData(), &db_ );
+  r = sqlite3_open( path.toUtf8().constData(), &db_ );
   if ( withExtension )
   {
     // reset the automatic extensions
@@ -41,7 +41,7 @@ QgsScopedSqlite::QgsScopedSqlite( const QString& path, bool withExtension )
   {
     QString err = QString( "%1 [%2]" ).arg( sqlite3_errmsg( db_ ), path );
     QgsDebugMsg( err );
-    throw std::runtime_error( err.toLocal8Bit().constData() );
+    throw std::runtime_error( err.toUtf8().constData() );
   }
   // enable extended result codes
   sqlite3_extended_result_codes( db_, 1 );
@@ -89,12 +89,12 @@ namespace Sqlite
 {
   Query::Query( sqlite3* db, const QString& q ) : db_( db ), nBind_( 1 )
   {
-    QByteArray ba( q.toLocal8Bit() );
+    QByteArray ba( q.toUtf8() );
     int r = sqlite3_prepare_v2( db, ba.constData(), ba.size(), &stmt_, nullptr );
     if ( r )
     {
       QString err = QString( "Query preparation error on %1" ).arg( q );
-      throw std::runtime_error( err.toLocal8Bit().constData() );
+      throw std::runtime_error( err.toUtf8().constData() );
     }
   }
 
@@ -107,7 +107,7 @@ namespace Sqlite
 
   Query& Query::bind( const QString& str, int idx )
   {
-    QByteArray ba( str.toLocal8Bit() );
+    QByteArray ba( str.toUtf8() );
     int r = sqlite3_bind_text( stmt_, idx, ba.constData(), ba.size(), SQLITE_TRANSIENT );
     if ( r )
     {
@@ -124,11 +124,11 @@ namespace Sqlite
   void Query::exec( sqlite3* db, const QString& sql )
   {
     char *errMsg = nullptr;
-    int r = sqlite3_exec( db, sql.toLocal8Bit().constData(), nullptr, nullptr, &errMsg );
+    int r = sqlite3_exec( db, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
     if ( r )
     {
       QString err = QString( "Query execution error on %1: %2 - %3" ).arg( sql ).arg( r ).arg( errMsg );
-      throw std::runtime_error( err.toLocal8Bit().constData() );
+      throw std::runtime_error( err.toUtf8().constData() );
     }
   }
 
diff --git a/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp b/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
index e9b76b6..478a2ad 100644
--- a/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
+++ b/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
@@ -56,7 +56,7 @@ void initVirtualLayerMetadata( sqlite3* db )
   char *errMsg;
   if ( create_meta )
   {
-    r = sqlite3_exec( db, QString( "CREATE TABLE _meta (version INT, url TEXT); INSERT INTO _meta (version) VALUES(%1);" ).arg( VIRTUAL_LAYER_VERSION ).toLocal8Bit().constData(), nullptr, nullptr, &errMsg );
+    r = sqlite3_exec( db, QString( "CREATE TABLE _meta (version INT, url TEXT); INSERT INTO _meta (version) VALUES(%1);" ).arg( VIRTUAL_LAYER_VERSION ).toUtf8().constData(), nullptr, nullptr, &errMsg );
     if ( r )
     {
       throw std::runtime_error( errMsg );
diff --git a/src/providers/wms/qgswmsdataitems.cpp b/src/providers/wms/qgswmsdataitems.cpp
index d7508a0..16b3f9d 100644
--- a/src/providers/wms/qgswmsdataitems.cpp
+++ b/src/providers/wms/qgswmsdataitems.cpp
@@ -115,46 +115,61 @@ QVector<QgsDataItem*> QgsWMSConnectionItem::createChildren()
       QgsDataItem *layerItem = l.styles.size() == 1 ? this : new QgsDataCollectionItem( this, title, mPath + '/' + l.identifier );
       if ( layerItem != this )
       {
+        layerItem->setCapabilities( layerItem->capabilities2() & ~QgsDataItem::Fertile );
+        layerItem->setState( QgsDataItem::Populated );
         layerItem->setToolTip( title );
-        addChildItem( layerItem );
+        children << layerItem;
       }
 
       Q_FOREACH ( const QgsWmtsStyle &style, l.styles )
       {
         QString styleName = style.title.isEmpty() ? style.identifier : style.title;
         if ( layerItem == this )
-          styleName.prepend( title + " - " );
+          styleName = title;  // just one style so no need to display it
 
         QgsDataItem *styleItem = l.setLinks.size() == 1 ? layerItem : new QgsDataCollectionItem( layerItem, styleName, layerItem->path() + '/' + style.identifier );
         if ( styleItem != layerItem )
         {
+          styleItem->setCapabilities( styleItem->capabilities2() & ~QgsDataItem::Fertile );
+          styleItem->setState( QgsDataItem::Populated );
           styleItem->setToolTip( styleName );
-          layerItem->addChildItem( styleItem );
+          if ( layerItem == this )
+            children << styleItem;
+          else
+            layerItem->addChildItem( styleItem );
         }
 
         Q_FOREACH ( const QgsWmtsTileMatrixSetLink &setLink, l.setLinks )
         {
           QString linkName = setLink.tileMatrixSet;
           if ( styleItem == layerItem )
-            linkName.prepend( styleName + " - " );
+            linkName = styleName;  // just one link so no need to display it
 
           QgsDataItem *linkItem = l.formats.size() == 1 ? styleItem : new QgsDataCollectionItem( styleItem, linkName, styleItem->path() + '/' + setLink.tileMatrixSet );
           if ( linkItem != styleItem )
           {
+            linkItem->setCapabilities( linkItem->capabilities2() & ~QgsDataItem::Fertile );
+            linkItem->setState( QgsDataItem::Populated );
             linkItem->setToolTip( linkName );
-            styleItem->addChildItem( linkItem );
+            if ( styleItem == this )
+              children << linkItem;
+            else
+              styleItem->addChildItem( linkItem );
           }
 
           Q_FOREACH ( const QString& format, l.formats )
           {
             QString name = format;
             if ( linkItem == styleItem )
-              name.prepend( linkName + " - " );
+              name = linkName;  // just one format so no need to display it
 
-            QgsDataItem *layerItem = new QgsWMTSLayerItem( linkItem, name, linkItem->path() + '/' + name, uri,
+            QgsDataItem *tileLayerItem = new QgsWMTSLayerItem( linkItem, name, linkItem->path() + '/' + name, uri,
                 l.identifier, format, style.identifier, setLink.tileMatrixSet, tileMatrixSets[ setLink.tileMatrixSet ].crs, title );
-            layerItem->setToolTip( name );
-            linkItem->addChildItem( layerItem );
+            tileLayerItem->setToolTip( name );
+            if ( linkItem == this )
+              children << tileLayerItem;
+            else
+              linkItem->addChildItem( tileLayerItem );
           }
         }
       }
diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp
index 9a72362..48718bc 100644
--- a/src/providers/wms/qgswmsprovider.cpp
+++ b/src/providers/wms/qgswmsprovider.cpp
@@ -2677,7 +2677,7 @@ QgsRasterIdentifyResult QgsWmsProvider::identify( const QgsPoint & thePoint, Qgs
             QString crsType = result.property( "crs" ).property( "type" ).toString();
             QString crsText;
             if ( crsType == "name" )
-              crsText = result.property( "crs" ).property( "name" ).toString();
+              crsText = result.property( "crs" ).property( "properties" ).property( "name" ).toString();
             else if ( crsType == "EPSG" )
               crsText = QString( "%1:%2" ).arg( crsType, result.property( "crs" ).property( "properties" ).property( "code" ).toString() );
             else
@@ -2773,6 +2773,7 @@ QgsRasterIdentifyResult QgsWmsProvider::identify( const QgsPoint & thePoint, Qgs
         catch ( const QString &err )
         {
           QgsDebugMsg( QString( "JSON error: %1\nResult: %2" ).arg( err, QString::fromUtf8( mIdentifyResultBodies.value( jsonPart ) ) ) );
+          results.insert( results.size(), err );  // string returned for format type "feature" means error
         }
 
         delete coordinateTransform;
diff --git a/src/server/qgshostedrdsbuilder.cpp b/src/server/qgshostedrdsbuilder.cpp
index acba4e6..72b236d 100644
--- a/src/server/qgshostedrdsbuilder.cpp
+++ b/src/server/qgshostedrdsbuilder.cpp
@@ -61,7 +61,7 @@ QgsMapLayer* QgsHostedRDSBuilder::createMapLayer( const QDomElement& elem,
     {
       rl = qobject_cast<QgsRasterLayer*>( QgsMSLayerCache::instance()->searchLayer( uri, layerName ) );
     }
-    if ( !rl )
+    if ( !rl || !rl->isValid() )
     {
       QgsDebugMsg( "hostedrds layer not in cache, so create and insert it" );
       rl = new QgsRasterLayer( uri, layerNameFromUri( uri ) );
diff --git a/src/server/qgsserverprojectparser.cpp b/src/server/qgsserverprojectparser.cpp
index f3807a0..860bec2 100644
--- a/src/server/qgsserverprojectparser.cpp
+++ b/src/server/qgsserverprojectparser.cpp
@@ -80,6 +80,17 @@ QgsServerProjectParser::QgsServerProjectParser( QDomDocument* xmlDoc, const QStr
   {
     QgsProject::instance()->setFileName( mProjectPath );
   }
+
+  // Set the project scope variables
+  QStringList variableNames = readListEntry( "Variables", "variableNames" );
+  QStringList variableValues = readListEntry( "Variables", "variableValues" );
+
+  //read values
+
+  //append standard values
+
+  QgsProject::instance()->writeEntry( "Variables", "/variableNames", variableNames );
+  QgsProject::instance()->writeEntry( "Variables", "/variableValues", variableValues );
 }
 
 QgsServerProjectParser::QgsServerProjectParser()
@@ -278,6 +289,10 @@ QgsMapLayer* QgsServerProjectParser::createLayerFromElement( const QDomElement&
     layer->readLayerXML( const_cast<QDomElement&>( elem ) ); //should be changed to const in QgsMapLayer
     //layer->setLayerName( layerName( elem ) );
 
+    if ( !layer->isValid() )
+    {
+      return nullptr;
+    }
     // Insert layer in registry and cache before addValueRelationLayersForLayer
     if ( !QgsMapLayerRegistry::instance()->mapLayer( id ) )
       QgsMapLayerRegistry::instance()->addMapLayer( layer, false, false );
@@ -1608,4 +1623,34 @@ void QgsServerProjectParser::addGetFeatureLayers( const QDomElement& layerElem )
   }
 }
 
+QStringList QgsServerProjectParser::readListEntry( const QString& scope, const QString& key ) const
+{
+  QStringList entryList;
+  QDomElement propertiesElement = propertiesElem();
+  if ( propertiesElement.isNull() )
+  {
+    return entryList;
+  }
+
+  QDomElement scopeElem = propertiesElement.firstChildElement( scope );
+  if ( scopeElem.isNull() )
+  {
+    return entryList;
+  }
+
+  QDomElement keyElem = scopeElem.firstChildElement( key );
+  if ( keyElem.isNull() )
+  {
+    return entryList;
+  }
+
+  QDomNodeList valueNodeList = keyElem.elementsByTagName( "value" );
+  for ( int i = 0; i < valueNodeList.size(); ++i )
+  {
+    entryList.append( valueNodeList.at( i ).toElement().text() );
+  }
+
+  return entryList;
+}
+
 
diff --git a/src/server/qgsserverprojectparser.h b/src/server/qgsserverprojectparser.h
index da8b5d5..79d09df 100644
--- a/src/server/qgsserverprojectparser.h
+++ b/src/server/qgsserverprojectparser.h
@@ -175,6 +175,9 @@ class SERVER_EXPORT QgsServerProjectParser
 
     /** Adds sublayers of an embedded group to layer set*/
     static void sublayersOfEmbeddedGroup( const QString& projectFilePath, const QString& groupName, QSet<QString>& layerSet );
+
+    /** Reads list entry from project properties*/
+    QStringList readListEntry( const QString& scope, const QString& key ) const;
 };
 
 #endif // QGSSERVERPROJECTPARSER_H
diff --git a/src/server/qgswfsserver.cpp b/src/server/qgswfsserver.cpp
index a7773be..c682167 100644
--- a/src/server/qgswfsserver.cpp
+++ b/src/server/qgswfsserver.cpp
@@ -656,6 +656,29 @@ int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format
               {
                 throw QgsMapServiceException( "RequestNotWellFormed", filter->parserErrorString() );
               }
+              QgsFeatureRequest req;
+              if ( filter->needsGeometry() )
+              {
+                req.setFlags( QgsFeatureRequest::NoFlags );
+              }
+              else
+              {
+                req.setFlags( QgsFeatureRequest::ExactIntersect | ( mWithGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) );
+              }
+              req.setFilterExpression( filter->expression() );
+#ifdef HAVE_SERVER_PYTHON_PLUGINS
+              mAccessControl->filterFeatures( layer, req );
+
+              QStringList attributes = QStringList();
+              Q_FOREACH ( int idx, attrIndexes )
+              {
+                attributes.append( layer->pendingFields().field( idx ).name() );
+              }
+              req.setSubsetOfAttributes(
+                mAccessControl->layerAttributes( layer, attributes ),
+                layer->pendingFields() );
+#endif
+              QgsFeatureIterator fit = layer->getFeatures( req );
               while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) )
               {
                 expressionContext.setFeature( feature );
diff --git a/src/server/qgswmsconfigparser.cpp b/src/server/qgswmsconfigparser.cpp
index 96e4f72..6a7d890 100644
--- a/src/server/qgswmsconfigparser.cpp
+++ b/src/server/qgswmsconfigparser.cpp
@@ -201,15 +201,21 @@ QgsComposition* QgsWMSConfigParser::createPrintComposition( const QString& compo
         continue;
       }
 
+      //The layer set has entries if the composer map has a locked layer set or
+      //if the composer map layer set has been constrained in the WMS request.
+      //If this is not the case, there is no need to modify the layers in the legend here
+      QStringList layerSet = map->layerSet();
+      if ( layerSet.size() < 1 )
+      {
+        continue;
+      }
+
       // get model and layer tree root of the legend
       QgsLegendModelV2* model = currentLegend->modelV2();
       QgsLayerTreeGroup* root = model->rootGroup();
 
-
       // get layerIds find in the layer tree root
       QStringList layerIds = root->findLayerIds();
-      // get map layerIds
-      QStringList layerSet = map->layerSet();
 
       // get map scale
       double scale = map->scale();
diff --git a/src/server/qgswmsprojectparser.cpp b/src/server/qgswmsprojectparser.cpp
index 7d05d0e..e2c5321 100644
--- a/src/server/qgswmsprojectparser.cpp
+++ b/src/server/qgswmsprojectparser.cpp
@@ -1768,7 +1768,14 @@ void QgsWMSProjectParser::addOWSLayers( QDomDocument &doc,
         layerElem.appendChild( metaUrlElem );
       }
 
-      parentElem.appendChild( layerElem );
+      if ( parentElem.hasChildNodes() )
+      {
+        parentElem.insertBefore( layerElem, parentElem.firstChild() );
+      }
+      else
+      {
+        parentElem.appendChild( layerElem );
+      }
     }
     else
     {
diff --git a/src/server/qgswmsserver.cpp b/src/server/qgswmsserver.cpp
index 441fd77..ffa24f5 100644
--- a/src/server/qgswmsserver.cpp
+++ b/src/server/qgswmsserver.cpp
@@ -2079,6 +2079,16 @@ int QgsWMSServer::configureMapRender( const QPaintDevice* paintDevice ) const
     mMapRenderer->setOutputUnits( QgsMapRenderer::Pixels ); //SLD units are in pixels normally
   }
 
+  //Clear expression context and set project variables
+  QgsRenderContext* ctx = mMapRenderer->rendererContext();
+  if ( ctx )
+  {
+    QgsExpressionContext newContext;
+    newContext.appendScope( QgsExpressionContextUtils::globalScope() );
+    newContext.appendScope( QgsExpressionContextUtils::projectScope() );
+    ctx->setExpressionContext( newContext );
+  }
+
   return 0;
 }
 
diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp
index 785c4ab..905c0a2 100644
--- a/tests/src/core/testqgsgeometry.cpp
+++ b/tests/src/core/testqgsgeometry.cpp
@@ -2010,11 +2010,10 @@ void TestQgsGeometry::lineStringV2()
   //closest segment
   QgsLineStringV2 l35;
   bool leftOf = false;
+  p = QgsPointV2(); // reset all coords to zero
   ( void )l35.closestSegment( QgsPointV2( 1, 2 ), p, v, 0, 0 ); //empty line, just want no crash
   l35.setPoints( QgsPointSequenceV2() << QgsPointV2( 5, 10 ) );
-  QVERIFY( qgsDoubleNear( l35.closestSegment( QgsPointV2( 5, 10 ), p, v, 0, 0 ), 0 ) );
-  QCOMPARE( p, QgsPointV2( 5, 10 ) );
-  QCOMPARE( v, QgsVertexId( 0, 0, 1 ) );
+  QVERIFY( l35.closestSegment( QgsPointV2( 5, 10 ), p, v, 0, 0 ) < 0 );
   l35.setPoints( QgsPointSequenceV2() << QgsPointV2( 5, 10 ) << QgsPointV2( 10, 10 ) );
   QVERIFY( qgsDoubleNear( l35.closestSegment( QgsPointV2( 4, 11 ), p, v, &leftOf, 0 ), 2.0 ) );
   QCOMPARE( p, QgsPointV2( 5, 10 ) );
diff --git a/tests/src/core/testqgspointlocator.cpp b/tests/src/core/testqgspointlocator.cpp
index cb7e805..090c705 100644
--- a/tests/src/core/testqgspointlocator.cpp
+++ b/tests/src/core/testqgspointlocator.cpp
@@ -23,6 +23,7 @@
 #include "qgsgeometry.h"
 #include "qgsmaplayerregistry.h"
 #include "qgspointlocator.h"
+#include "qgspolygonv2.h"
 
 
 struct FilterExcludePoint : public QgsPointLocator::MatchFilter
@@ -256,6 +257,48 @@ class TestQgsPointLocator : public QObject
       QVERIFY( m2.isValid() );
       QCOMPARE( m2.point(), QgsPoint( 1, 1 ) );
     }
+
+    void testNullGeometries()
+    {
+      QgsVectorLayer* vlNullGeom = new QgsVectorLayer( "Polygon", "x", "memory" );
+      QgsFeature ff( 0 );
+      ff.setGeometry( QgsGeometry() );
+      QgsFeatureList flist;
+      flist << ff;
+      vlNullGeom->dataProvider()->addFeatures( flist );
+
+      QgsPointLocator loc( vlNullGeom, 0, nullptr );
+
+      QgsPointLocator::Match m1 = loc.nearestVertex( QgsPoint( 2, 2 ), std::numeric_limits<double>::max() );
+      QVERIFY( !m1.isValid() );
+
+      QgsPointLocator::Match m2 = loc.nearestEdge( QgsPoint( 2, 2 ), std::numeric_limits<double>::max() );
+      QVERIFY( !m2.isValid() );
+
+      delete vlNullGeom;
+    }
+
+    void testEmptyGeometries()
+    {
+      QgsVectorLayer* vlEmptyGeom = new QgsVectorLayer( "Polygon", "x", "memory" );
+      QgsFeature ff( 0 );
+      QgsGeometry g;
+      g.setGeometry( new QgsPolygonV2() );
+      ff.setGeometry( g );
+      QgsFeatureList flist;
+      flist << ff;
+      vlEmptyGeom->dataProvider()->addFeatures( flist );
+
+      QgsPointLocator loc( vlEmptyGeom, 0, nullptr );
+
+      QgsPointLocator::Match m1 = loc.nearestVertex( QgsPoint( 2, 2 ), std::numeric_limits<double>::max() );
+      QVERIFY( !m1.isValid() );
+
+      QgsPointLocator::Match m2 = loc.nearestEdge( QgsPoint( 2, 2 ), std::numeric_limits<double>::max() );
+      QVERIFY( !m2.isValid() );
+
+      delete vlEmptyGeom;
+    }
 };
 
 QTEST_MAIN( TestQgsPointLocator )
diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt
index 4da57a5..4d23245 100644
--- a/tests/src/gui/CMakeLists.txt
+++ b/tests/src/gui/CMakeLists.txt
@@ -134,5 +134,5 @@ ADD_QGIS_TEST(qgsguitest testqgsgui.cpp)
 ADD_QGIS_TEST(rubberbandtest testqgsrubberband.cpp)
 ADD_QGIS_TEST(scalecombobox testqgsscalecombobox.cpp)
 ADD_QGIS_TEST(spinbox testqgsspinbox.cpp)
-
+ADD_QGIS_TEST(filedownloader testqgsfiledownloader.cpp)
 
diff --git a/tests/src/gui/testqgsfiledownloader.cpp b/tests/src/gui/testqgsfiledownloader.cpp
new file mode 100644
index 0000000..fa9a7dc
--- /dev/null
+++ b/tests/src/gui/testqgsfiledownloader.cpp
@@ -0,0 +1,251 @@
+/***************************************************************************
+    testqgsfilefiledownloader.cpp
+     --------------------------------------
+    Date                 : 11.8.2016
+    Copyright            : (C) 2016 Alessandro Pasotti
+    Email                : apasotti at boundlessgeo dot com
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+
+#include <QtTest/QtTest>
+#include <QObject>
+#include <QTemporaryFile>
+#include <QUrl>
+#include <QEventLoop>
+#include <QTimer>
+
+#include <qgsapplication.h>
+#include <qgsfiledownloader.h>
+
+class TestQgsFileDownloader: public QObject
+{
+    Q_OBJECT
+  public:
+    TestQgsFileDownloader()
+        : mTempFile( nullptr )
+        , mErrorMessage()
+        , mCanceled( false )
+        , mProgress( false )
+        , mError( false )
+        , mCompleted( false )
+        , mExited( false )
+        , mFileDownloader( nullptr )
+    {}
+
+  public slots:
+    /** Called when the download has completed successfully */
+    void downloadCompleted()
+    {
+      mCompleted = true;
+    }
+    /** Called when the download exits */
+    void downloadExited()
+    {
+      mExited = true;
+    }
+    /** Called when the download was canceled by the user */
+    void downloadCanceled()
+    {
+      mCanceled = true;
+    }
+    /** Called when an error makes the download fail */
+    void downloadError( QStringList errorMessages )
+    {
+      mError = true;
+      errorMessages.sort();
+      mErrorMessage = errorMessages.join( ";" );
+    }
+    /** Called when data ready to be processed */
+    void downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
+    {
+      Q_UNUSED( bytesReceived );
+      Q_UNUSED( bytesTotal );
+      mProgress = true;
+    }
+
+  private slots:
+    void initTestCase(); // will be called before the first testfunction is executed.
+    void cleanupTestCase(); // will be called after the last testfunction was executed.
+    void init(); // will be called before each testfunction is executed.
+    void cleanup(); // will be called after every testfunction.
+
+    void testValidDownload();
+    void testInValidDownload();
+    void testCanceledDownload();
+    void testInvalidFile();
+    void testInvalidUrl();
+    void testBlankUrl();
+#ifndef QT_NO_OPENSSL
+    void testSslError_data();
+    void testSslError();
+#endif
+
+  private:
+    void makeCall( QUrl url , QString fileName, bool cancel = false );
+    QTemporaryFile *mTempFile;
+    QString mErrorMessage;
+    bool mCanceled;
+    bool mProgress;
+    bool mError;
+    bool mCompleted;
+    bool mExited;
+    QgsFileDownloader *mFileDownloader;
+};
+
+void TestQgsFileDownloader::makeCall( QUrl url, QString fileName, bool cancel )
+{
+  QEventLoop loop;
+
+  mFileDownloader = new QgsFileDownloader( url, fileName, false );
+  connect( mFileDownloader, SIGNAL( downloadCompleted() ), this, SLOT( downloadCompleted() ) );
+  connect( mFileDownloader, SIGNAL( downloadCanceled() ), this, SLOT( downloadCanceled() ) );
+  connect( mFileDownloader, SIGNAL( downloadExited() ), this, SLOT( downloadExited() ) );
+  connect( mFileDownloader, SIGNAL( downloadError( QStringList ) ), this, SLOT( downloadError( QStringList ) ) );
+  connect( mFileDownloader, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( downloadProgress( qint64, qint64 ) ) );
+
+  connect( mFileDownloader, SIGNAL( downloadExited() ), &loop, SLOT( quit() ) );
+
+  if ( cancel )
+    QTimer::singleShot( 1000, mFileDownloader, SLOT( onDownloadCanceled() ) );
+
+  loop.exec();
+
+}
+
+void TestQgsFileDownloader::initTestCase()
+{
+  QgsApplication::init();
+  QgsApplication::initQgis();
+
+}
+
+void TestQgsFileDownloader::cleanupTestCase()
+{
+  QgsApplication::exitQgis();
+}
+
+void TestQgsFileDownloader::init()
+{
+  mErrorMessage.clear();
+  mCanceled = false;
+  mProgress = false;
+  mError = false;
+  mCompleted = false;
+  mExited = false;
+  mTempFile = new QTemporaryFile( );
+  Q_ASSERT( mTempFile->open() );
+  mTempFile->close();
+}
+
+
+
+void TestQgsFileDownloader::cleanup()
+{
+  delete mTempFile;
+}
+
+void TestQgsFileDownloader::testValidDownload()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "http://www.qgis.org" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( mCompleted );
+  QVERIFY( mProgress );
+  QVERIFY( !mError );
+  QVERIFY( !mCanceled );
+  QVERIFY( mTempFile->size() > 0 );
+}
+
+void TestQgsFileDownloader::testInValidDownload()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "http://www.doesnotexistofthatimsure.qgis" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QVERIFY( mTempFile->size() == 0 );
+  QCOMPARE( mErrorMessage, QString( "Network error 3: Host www.doesnotexistofthatimsure.qgis not found" ) );
+}
+
+void TestQgsFileDownloader::testCanceledDownload()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "https://github.com/qgis/QGIS/archive/master.zip" ), mTempFile->fileName(), true );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( !mError );
+  QVERIFY( mProgress );
+  QVERIFY( mCanceled );
+  QVERIFY( mTempFile->size() == 0 );
+}
+
+void TestQgsFileDownloader::testInvalidFile()
+{
+  makeCall( QUrl( "https://github.com/qgis/QGIS/archive/master.zip" ), QString() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QCOMPARE( mErrorMessage, QString( "Cannot open output file: " ) );
+}
+
+void TestQgsFileDownloader::testInvalidUrl()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "xyz://www" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QCOMPARE( mErrorMessage, QString( "Network error 301: Protocol \"xyz\" is unknown" ) );
+}
+
+void TestQgsFileDownloader::testBlankUrl()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QCOMPARE( mErrorMessage, QString( "Network error 301: Protocol \"\" is unknown" ) );
+}
+
+#ifndef QT_NO_OPENSSL
+void TestQgsFileDownloader::testSslError_data()
+{
+  QTest::addColumn<QString>( "url" );
+  QTest::addColumn<QString>( "result" );
+
+  QTest::newRow( "expired" ) << "https://expired.badssl.com/" << "Network error 6: SSL handshake failed;SSL Errors: ;The certificate has expired";
+  QTest::newRow( "self-signed" ) << "https://self-signed.badssl.com/" << "Network error 6: SSL handshake failed;SSL Errors: ;The certificate is self-signed, and untrusted";
+  QTest::newRow( "untrusted-root" ) << "https://untrusted-root.badssl.com/" << "Network error 6: SSL handshake failed;No certificates could be verified;SSL Errors: ;The issuer certificate of a locally looked up certificate could not be found;The root CA certificate is not trusted for this purpose";
+}
+
+void TestQgsFileDownloader::testSslError()
+{
+  QFETCH( QString, url );
+  QFETCH( QString, result );
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( url ), mTempFile->fileName() );
+  QCOMPARE( mErrorMessage, result );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+}
+
+#endif
+
+
+QTEST_MAIN( TestQgsFileDownloader )
+#include "testqgsfiledownloader.moc"
+
+
diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt
index 23fcf0b..eb66174 100644
--- a/tests/src/python/CMakeLists.txt
+++ b/tests/src/python/CMakeLists.txt
@@ -79,6 +79,7 @@ ADD_PYTHON_TEST(PyQgsMapLayerRegistry test_qgsmaplayerregistry.py)
 ADD_PYTHON_TEST(PyQgsVirtualLayerProvider test_provider_virtual.py)
 ADD_PYTHON_TEST(PyQgsVirtualLayerDefinition test_qgsvirtuallayerdefinition.py)
 ADD_PYTHON_TEST(PyQgsLayerDefinition test_qgslayerdefinition.py)
+ADD_PYTHON_TEST(PyQgsFileDownloader test_qgsfiledownloader.py)
 
 IF (NOT WIN32)
   ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)
@@ -114,4 +115,7 @@ ENDIF (WITH_APIDOC)
 IF (WITH_SERVER)
   ADD_PYTHON_TEST(PyQgsServer test_qgsserver.py)
   ADD_PYTHON_TEST(PyQgsServerAccessControl test_qgsserver_accesscontrol.py)
+  ADD_PYTHON_TEST(PyQgsAuthManagerPasswordOWSTest test_authmanager_password_ows.py)
+  #ADD_PYTHON_TEST(PyQgsAuthManagerPKIOWSTest test_authmanager_pki_ows.py)
+  ADD_PYTHON_TEST(PyQgsAuthManagerPKIPostgresTest test_authmanager_pki_postgres.py)
 ENDIF (WITH_SERVER)
diff --git a/tests/src/python/qgis_wrapped_server.py b/tests/src/python/qgis_wrapped_server.py
new file mode 100644
index 0000000..0ec7bc5
--- /dev/null
+++ b/tests/src/python/qgis_wrapped_server.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+"""
+QGIS Server HTTP wrapper
+
+This script launches a QGIS Server listening on port 8081 or on the port
+specified on the environment variable QGIS_SERVER_PORT.
+QGIS_SERVER_HOST (defaults to 127.0.0.1)
+
+For testing purposes, HTTP Basic can be enabled by setting the following
+environment variables:
+
+  * QGIS_SERVER_HTTP_BASIC_AUTH (default not set, set to anything to enable)
+  * QGIS_SERVER_USERNAME (default ="username")
+  * QGIS_SERVER_PASSWORD (default ="password")
+
+PKI authentication with HTTPS can be enabled with:
+
+  * QGIS_SERVER_PKI_CERTIFICATE (server certificate)
+  * QGIS_SERVER_PKI_KEY (server private key)
+  * QGIS_SERVER_PKI_AUTHORITY (root CA)
+  * QGIS_SERVER_PKI_USERNAME (valid username)
+
+ Sample run:
+
+ QGIS_SERVER_PKI_USERNAME=Gerardus QGIS_SERVER_PORT=47547 QGIS_SERVER_HOST=localhost \
+    QGIS_SERVER_PKI_KEY=/home/dev/QGIS/tests/testdata/auth_system/certs_keys/localhost_ssl_key.pem \
+    QGIS_SERVER_PKI_CERTIFICATE=/home/dev/QGIS/tests/testdata/auth_system/certs_keys/localhost_ssl_cert.pem \
+    QGIS_SERVER_PKI_AUTHORITY=/home/dev/QGIS/tests/testdata/auth_system/certs_keys/chains_subissuer-issuer-root_issuer2-root2.pem \
+    python /home/dev/QGIS/tests/src/python/qgis_wrapped_server.py
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+from __future__ import print_function
+from future import standard_library
+standard_library.install_aliases()
+
+__author__ = 'Alessandro Pasotti'
+__date__ = '05/15/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+
+import os
+import sys
+import ssl
+import urllib.parse
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from qgis.server import QgsServer, QgsServerFilter
+
+QGIS_SERVER_PORT = int(os.environ.get('QGIS_SERVER_PORT', '8081'))
+QGIS_SERVER_HOST = os.environ.get('QGIS_SERVER_HOST', '127.0.0.1')
+# PKI authentication
+QGIS_SERVER_PKI_CERTIFICATE = os.environ.get('QGIS_SERVER_PKI_CERTIFICATE')
+QGIS_SERVER_PKI_KEY = os.environ.get('QGIS_SERVER_PKI_KEY')
+QGIS_SERVER_PKI_AUTHORITY = os.environ.get('QGIS_SERVER_PKI_AUTHORITY')
+QGIS_SERVER_PKI_USERNAME = os.environ.get('QGIS_SERVER_PKI_USERNAME')
+
+# Check if PKI - https is enabled
+https = (QGIS_SERVER_PKI_CERTIFICATE is not None and
+         os.path.isfile(QGIS_SERVER_PKI_CERTIFICATE) and
+         QGIS_SERVER_PKI_KEY is not None and
+         os.path.isfile(QGIS_SERVER_PKI_KEY) and
+         QGIS_SERVER_PKI_AUTHORITY is not None and
+         os.path.isfile(QGIS_SERVER_PKI_AUTHORITY) and
+         QGIS_SERVER_PKI_USERNAME)
+
+qgs_server = QgsServer()
+
+if os.environ.get('QGIS_SERVER_HTTP_BASIC_AUTH') is not None:
+    import base64
+
+    class HTTPBasicFilter(QgsServerFilter):
+
+        def responseComplete(self):
+            request = self.serverInterface().requestHandler()
+            if self.serverInterface().getEnv('HTTP_AUTHORIZATION'):
+                username, password = base64.b64decode(self.serverInterface().getEnv('HTTP_AUTHORIZATION')[6:]).split(':')
+                if (username == os.environ.get('QGIS_SERVER_USERNAME', 'username')
+                        and password == os.environ.get('QGIS_SERVER_PASSWORD', 'password')):
+                    return
+            # No auth ...
+            request.clearHeaders()
+            request.setHeader('Status', '401 Authorization required')
+            request.setHeader('WWW-Authenticate', 'Basic realm="QGIS Server"')
+            request.clearBody()
+            request.appendBody('<h1>Authorization required</h1>')
+
+    filter = HTTPBasicFilter(qgs_server.serverInterface())
+    qgs_server.serverInterface().registerFilter(filter)
+
+
+class Handler(BaseHTTPRequestHandler):
+
+    def do_GET(self):
+        # For PKI: check the username from client certificate
+        if https:
+            try:
+                ssl.match_hostname(self.connection.getpeercert(), QGIS_SERVER_PKI_USERNAME)
+            except Exception as ex:
+                print("SSL Exception %s" % ex)
+                self.send_response(401)
+                self.end_headers()
+                self.wfile.write('UNAUTHORIZED')
+                return
+        # CGI vars:
+        for k, v in self.headers.items():
+            # Uncomment to print debug info about env vars passed into QGIS Server env
+            #print('Setting ENV var %s to %s' % ('HTTP_%s' % k.replace(' ', '-').replace('-', '_').replace(' ', '-').upper(), v))
+            qgs_server.putenv('HTTP_%s' % k.replace(' ', '-').replace('-', '_').replace(' ', '-').upper(), v)
+        qgs_server.putenv('SERVER_PORT', str(self.server.server_port))
+        qgs_server.putenv('SERVER_NAME', self.server.server_name)
+        qgs_server.putenv('REQUEST_URI', self.path)
+        parsed_path = urllib.parse.urlparse(self.path)
+        headers, body = qgs_server.handleRequest(parsed_path.query)
+        headers_dict = dict(h.split(': ', 1) for h in headers.decode().split('\n') if h)
+        try:
+            self.send_response(int(headers_dict['Status'].split(' ')[0]))
+        except:
+            self.send_response(200)
+        for k, v in headers_dict.items():
+            self.send_header(k, v)
+        self.end_headers()
+        self.wfile.write(body)
+        return
+
+    def do_POST(self):
+        content_len = int(self.headers.get('content-length', 0))
+        post_body = self.rfile.read(content_len).decode()
+        request = post_body[1:post_body.find(' ')]
+        self.path = self.path + '&REQUEST_BODY=' + \
+            post_body.replace('&', '') + '&REQUEST=' + request
+        return self.do_GET()
+
+
+if __name__ == '__main__':
+    server = HTTPServer((QGIS_SERVER_HOST, QGIS_SERVER_PORT), Handler)
+    if https:
+        server.socket = ssl.wrap_socket(server.socket,
+                                        certfile=QGIS_SERVER_PKI_CERTIFICATE,
+                                        keyfile=QGIS_SERVER_PKI_KEY,
+                                        ca_certs=QGIS_SERVER_PKI_AUTHORITY,
+                                        cert_reqs=ssl.CERT_REQUIRED,
+                                        server_side=True,
+                                        ssl_version=ssl.PROTOCOL_TLSv1)
+    message = 'Starting server on %s://%s:%s, use <Ctrl-C> to stop' % \
+              ('https' if https else 'http', QGIS_SERVER_HOST, server.server_port)
+    try:
+        print(message, flush=True)
+    except:
+        print(message)
+        sys.stdout.flush()
+    server.serve_forever()
diff --git a/tests/src/python/test_authmanager_password_ows.py b/tests/src/python/test_authmanager_password_ows.py
new file mode 100644
index 0000000..2f2f97a
--- /dev/null
+++ b/tests/src/python/test_authmanager_password_ows.py
@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for auth manager WMS/WFS using QGIS Server through HTTP Basic
+enabled qgis_wrapped_server.py.
+
+This is an integration test for QGIS Desktop Auth Manager WFS and WMS provider
+and QGIS Server WFS/WMS that check if QGIS can use a stored auth manager auth
+configuration to access an HTTP Basic protected endpoint.
+
+
+From build dir, run: ctest -R PyQgsAuthManagerPasswordOWSTest -V
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+import os
+import sys
+import re
+import subprocess
+import tempfile
+import random
+import string
+try:
+    from urllib.parse import quote
+except:
+    from urllib import quote
+
+__author__ = 'Alessandro Pasotti'
+__date__ = '18/09/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+from shutil import rmtree
+
+from utilities import unitTestDataPath, waitServer
+from qgis.core import (
+    QgsAuthManager,
+    QgsAuthMethodConfig,
+    QgsVectorLayer,
+    QgsRasterLayer,
+)
+from qgis.testing import (
+    start_app,
+    unittest,
+)
+
+try:
+    QGIS_SERVER_ENDPOINT_PORT = os.environ['QGIS_SERVER_ENDPOINT_PORT']
+except:
+    QGIS_SERVER_ENDPOINT_PORT = '0'  # Auto
+
+
+QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
+
+os.environ['QGIS_AUTH_DB_DIR_PATH'] = QGIS_AUTH_DB_DIR_PATH
+
+qgis_app = start_app()
+
+
+class TestAuthManager(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        """Run before all tests:
+        Creates an auth configuration"""
+        cls.port = QGIS_SERVER_ENDPOINT_PORT
+        # Clean env just to be sure
+        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
+        for ev in env_vars:
+            try:
+                del os.environ[ev]
+            except KeyError:
+                pass
+        cls.testdata_path = unitTestDataPath('qgis_server') + '/'
+        cls.project_path = cls.testdata_path + "test_project.qgs"
+        # Enable auth
+        #os.environ['QGIS_AUTH_PASSWORD_FILE'] = QGIS_AUTH_PASSWORD_FILE
+        authm = QgsAuthManager.instance()
+        assert (authm.setMasterPassword('masterpassword', True))
+        cls.auth_config = QgsAuthMethodConfig('Basic')
+        cls.auth_config.setName('test_auth_config')
+        cls.username = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
+        cls.password = cls.username[::-1] # reversed
+        cls.auth_config.setConfig('username', cls.username)
+        cls.auth_config.setConfig('password', cls.password)
+        assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
+        cls.hostname = '127.0.0.1'
+        cls.protocol = 'http'
+
+        os.environ['QGIS_SERVER_HTTP_BASIC_AUTH'] = '1'
+        os.environ['QGIS_SERVER_USERNAME'] = cls.username
+        os.environ['QGIS_SERVER_PASSWORD'] = cls.password
+        os.environ['QGIS_SERVER_PORT'] = str(cls.port)
+        os.environ['QGIS_SERVER_HOST'] = cls.hostname
+        server_path = os.path.dirname(os.path.realpath(__file__)) + \
+            '/qgis_wrapped_server.py'
+        cls.server = subprocess.Popen([sys.executable, server_path],
+                                      env=os.environ, stdout=subprocess.PIPE)
+
+        line = cls.server.stdout.readline()
+        cls.port = int(re.findall(b':(\d+)', line)[0])
+        assert cls.port != 0
+        # Wait for the server process to start
+        assert waitServer('%s://%s:%s' % (cls.protocol, cls.hostname, cls.port)), "Server is not responding! '%s://%s:%s" % (cls.protocol, cls.hostname, cls.port)
+
+    @classmethod
+    def tearDownClass(cls):
+        """Run after all tests"""
+        cls.server.terminate()
+        rmtree(QGIS_AUTH_DB_DIR_PATH)
+        del cls.server
+
+    def setUp(self):
+        """Run before each test."""
+        pass
+
+    def tearDown(self):
+        """Run after each test."""
+        pass
+
+    @classmethod
+    def _getWFSLayer(cls, type_name, layer_name=None, authcfg=None):
+        """
+        WFS layer factory
+        """
+        if layer_name is None:
+            layer_name = 'wfs_' + type_name
+        parms = {
+            'srsname': 'EPSG:4326',
+            'typename': type_name.decode('utf-8').replace(' ', '_'),
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
+            'version': '1.0.0',
+            'table': '',
+        }
+        uri = u'%(url)s&SERVICE=WFS&VERSION=%(version)s&REQUEST=GetFeature&TYPENAME=%(typename)s&SRSNAME=%(srsname)s' % parms
+        if authcfg is not None:
+            uri = uri + "&authcfg=%s" % authcfg
+        wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
+        return wfs_layer
+
+    @classmethod
+    def _getWMSLayer(cls, layers, layer_name=None, authcfg=None):
+        """
+        WMS layer factory
+        """
+        if layer_name is None:
+            layer_name = 'wms_' + layers.replace(',', '')
+        parms = {
+            'crs': 'EPSG:4326',
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
+            'format': 'image/png',
+            'layers': quote(layers),
+            'styles': '',
+            'version': 'auto',
+            #'sql': '',
+        }
+        if authcfg is not None:
+            parms.update({'authcfg': authcfg})
+        uri = '&'.join([("%s=%s" % (k, v.replace('=', '%3D'))) for k, v in list(parms.items())])
+        wms_layer = QgsRasterLayer(uri, layer_name, 'wms')
+        return wms_layer
+
+    def testValidAuthAccess(self):
+        """
+        Access the HTTP Basic protected layer with valid credentials
+        """
+        wfs_layer = self._getWFSLayer('testlayer èé', authcfg=self.auth_config.id())
+        self.assertTrue(wfs_layer.isValid())
+        wms_layer = self._getWMSLayer('testlayer èé', authcfg=self.auth_config.id())
+        self.assertTrue(wms_layer.isValid())
+
+    def testInvalidAuthAccess(self):
+        """
+        Access the HTTP Basic protected layer with no credentials
+        """
+        wfs_layer = self._getWFSLayer('testlayer èé')
+        self.assertFalse(wfs_layer.isValid())
+        wms_layer = self._getWMSLayer('testlayer èé')
+        self.assertFalse(wms_layer.isValid())
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_authmanager_pki_ows.py b/tests/src/python/test_authmanager_pki_ows.py
new file mode 100644
index 0000000..a57966a
--- /dev/null
+++ b/tests/src/python/test_authmanager_pki_ows.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for auth manager WMS/WFS using QGIS Server through PKI
+enabled qgis_wrapped_server.py.
+
+This is an integration test for QGIS Desktop Auth Manager WFS and WMS provider
+and QGIS Server WFS/WMS that check if QGIS can use a stored auth manager auth
+configuration to access an HTTP Basic protected endpoint.
+
+
+From build dir, run: ctest -R PyQgsAuthManagerPKIOWSTest -V
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+import os
+import sys
+import re
+import subprocess
+import tempfile
+try:
+    from urllib.parse import quote
+except:
+    from urllib import quote
+import stat
+
+__author__ = 'Alessandro Pasotti'
+__date__ = '25/10/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+from shutil import rmtree
+
+from utilities import unitTestDataPath, waitServer
+from qgis.core import (
+    QgsAuthManager,
+    QgsAuthMethodConfig,
+    QgsVectorLayer,
+    QgsRasterLayer,
+)
+
+from qgis.PyQt.QtNetwork import QSslCertificate
+
+from qgis.testing import (
+    start_app,
+    unittest,
+)
+
+try:
+    QGIS_SERVER_ENDPOINT_PORT = os.environ['QGIS_SERVER_ENDPOINT_PORT']
+except:
+    QGIS_SERVER_ENDPOINT_PORT = '0'  # Auto
+
+
+QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
+
+os.environ['QGIS_AUTH_DB_DIR_PATH'] = QGIS_AUTH_DB_DIR_PATH
+
+qgis_app = start_app()
+
+
+class TestAuthManager(unittest.TestCase):
+
+    @classmethod
+    def setUpAuth(cls):
+        """Run before all tests and set up authentication"""
+        authm = QgsAuthManager.instance()
+        assert (authm.setMasterPassword('masterpassword', True))
+        cls.sslrootcert_path = os.path.join(cls.certsdata_path, 'chains_subissuer-issuer-root_issuer2-root2.pem')
+        cls.sslcert = os.path.join(cls.certsdata_path, 'gerardus_cert.pem')
+        cls.sslkey = os.path.join(cls.certsdata_path, 'gerardus_key.pem')
+        assert os.path.isfile(cls.sslcert)
+        assert os.path.isfile(cls.sslkey)
+        assert os.path.isfile(cls.sslrootcert_path)
+        os.chmod(cls.sslcert, stat.S_IRUSR)
+        os.chmod(cls.sslkey, stat.S_IRUSR)
+        os.chmod(cls.sslrootcert_path, stat.S_IRUSR)
+        cls.auth_config = QgsAuthMethodConfig("PKI-Paths")
+        cls.auth_config.setConfig('certpath', cls.sslcert)
+        cls.auth_config.setConfig('keypath', cls.sslkey)
+        cls.auth_config.setName('test_pki_auth_config')
+        cls.username = 'Gerardus'
+        cls.sslrootcert = QSslCertificate.fromPath(cls.sslrootcert_path)
+        assert cls.sslrootcert is not None
+        authm.storeCertAuthorities(cls.sslrootcert)
+        authm.rebuildCaCertsCache()
+        authm.rebuildTrustedCaCertsCache()
+        assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
+        assert cls.auth_config.isValid()
+
+        cls.server_cert = os.path.join(cls.certsdata_path, 'localhost_ssl_cert.pem')
+        cls.server_key = os.path.join(cls.certsdata_path, 'localhost_ssl_key.pem')
+        cls.server_rootcert = cls.sslrootcert_path
+        os.chmod(cls.server_cert, stat.S_IRUSR)
+        os.chmod(cls.server_key, stat.S_IRUSR)
+        os.chmod(cls.server_rootcert, stat.S_IRUSR)
+
+        os.environ['QGIS_SERVER_HOST'] = cls.hostname
+        os.environ['QGIS_SERVER_PORT'] = str(cls.port)
+        os.environ['QGIS_SERVER_PKI_KEY'] = cls.server_key
+        os.environ['QGIS_SERVER_PKI_CERTIFICATE'] = cls.server_cert
+        os.environ['QGIS_SERVER_PKI_USERNAME'] = cls.username
+        os.environ['QGIS_SERVER_PKI_AUTHORITY'] = cls.server_rootcert
+
+    @classmethod
+    def setUpClass(cls):
+        """Run before all tests:
+        Creates an auth configuration"""
+        cls.port = QGIS_SERVER_ENDPOINT_PORT
+        # Clean env just to be sure
+        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
+        for ev in env_vars:
+            try:
+                del os.environ[ev]
+            except KeyError:
+                pass
+        cls.testdata_path = unitTestDataPath('qgis_server')
+        cls.certsdata_path = os.path.join(unitTestDataPath('auth_system'), 'certs_keys')
+        cls.project_path = os.path.join(cls.testdata_path, "test_project.qgs")
+        cls.hostname = 'localhost'
+        cls.protocol = 'https'
+
+        cls.setUpAuth()
+
+        server_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   'qgis_wrapped_server.py')
+        cls.server = subprocess.Popen([sys.executable, server_path],
+                                      env=os.environ, stdout=subprocess.PIPE)
+        line = cls.server.stdout.readline()
+        cls.port = int(re.findall(b':(\d+)', line)[0])
+        assert cls.port != 0
+        # Wait for the server process to start
+        assert waitServer('%s://%s:%s' % (cls.protocol, cls.hostname, cls.port)), "Server is not responding! %s://%s:%s" % (cls.protocol, cls.hostname, cls.port)
+
+    @classmethod
+    def tearDownClass(cls):
+        """Run after all tests"""
+        cls.server.terminate()
+        rmtree(QGIS_AUTH_DB_DIR_PATH)
+        del cls.server
+
+    def setUp(self):
+        """Run before each test."""
+        pass
+
+    def tearDown(self):
+        """Run after each test."""
+        pass
+
+    def _getWFSLayer(cls, type_name, layer_name=None, authcfg=None):
+        """
+        WFS layer factory
+        """
+        if layer_name is None:
+            layer_name = 'wfs_' + type_name
+        parms = {
+            'srsname': 'EPSG:4326',
+            'typename': type_name.decode('utf-8').replace(' ', '_'),
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
+            'version': '1.0.0',
+            'table': '',
+        }
+        uri = u'%(url)s&SERVICE=WFS&VERSION=%(version)s&REQUEST=GetFeature&TYPENAME=%(typename)s&SRSNAME=%(srsname)s' % parms
+        if authcfg is not None:
+            uri = uri + "&authcfg=%s" % authcfg
+        wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
+        return wfs_layer
+
+    @classmethod
+    def _getWMSLayer(cls, layers, layer_name=None, authcfg=None):
+        """
+        WMS layer factory
+        """
+        if layer_name is None:
+            layer_name = 'wms_' + layers.replace(',', '')
+        parms = {
+            'crs': 'EPSG:4326',
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
+            'format': 'image/png',
+            'layers': quote(layers),
+            'styles': '',
+            'version': 'auto',
+            #'sql': '',
+        }
+        if authcfg is not None:
+            parms.update({'authcfg': authcfg})
+        uri = '&'.join([("%s=%s" % (k, v.replace('=', '%3D'))) for k, v in list(parms.items())])
+        wms_layer = QgsRasterLayer(uri, layer_name, 'wms')
+        return wms_layer
+
+    def testValidAuthAccess(self):
+        """
+        Access the protected layer with valid credentials
+        Note: cannot test invalid access in a separate test  because
+              it would fail the subsequent (valid) calls due to cached connections
+        """
+        wfs_layer = self._getWFSLayer('testlayer èé', authcfg=self.auth_config.id())
+        self.assertTrue(wfs_layer.isValid())
+        wms_layer = self._getWMSLayer('testlayer èé', authcfg=self.auth_config.id())
+        self.assertTrue(wms_layer.isValid())
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_authmanager_pki_postgres.py b/tests/src/python/test_authmanager_pki_postgres.py
new file mode 100644
index 0000000..8b332c0
--- /dev/null
+++ b/tests/src/python/test_authmanager_pki_postgres.py
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for auth manager PKI access to postgres.
+
+This is an integration test for QGIS Desktop Auth Manager postgres provider that
+checks if QGIS can use a stored auth manager auth configuration to access
+a PKI protected postgres.
+
+Configuration form the environment:
+
+    * QGIS_POSTGRES_SERVER_PORT (default: 55432)
+    * QGIS_POSTGRES_EXECUTABLE_PATH (default: /usr/lib/postgresql/9.4/bin)
+
+
+From build dir, run: ctest -R PyQgsAuthManagerPKIPostgresTest -V
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+import os
+import time
+import signal
+import stat
+import subprocess
+import tempfile
+
+from shutil import rmtree
+
+from utilities import unitTestDataPath
+from qgis.core import (
+    QgsAuthManager,
+    QgsAuthMethodConfig,
+    QgsVectorLayer,
+    QgsDataSourceURI,
+    QgsWKBTypes,
+)
+
+
+from qgis.PyQt.QtNetwork import QSslCertificate
+
+from qgis.testing import (
+    start_app,
+    unittest,
+)
+
+
+__author__ = 'Alessandro Pasotti'
+__date__ = '25/10/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+QGIS_POSTGRES_SERVER_PORT = os.environ.get('QGIS_POSTGRES_SERVER_PORT', '55432')
+QGIS_POSTGRES_EXECUTABLE_PATH = os.environ.get('QGIS_POSTGRES_EXECUTABLE_PATH', '/usr/lib/postgresql/9.4/bin')
+
+assert os.path.exists(QGIS_POSTGRES_EXECUTABLE_PATH)
+
+QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
+
+# Postgres test path
+QGIS_PG_TEST_PATH = tempfile.mkdtemp()
+
+os.environ['QGIS_AUTH_DB_DIR_PATH'] = QGIS_AUTH_DB_DIR_PATH
+
+qgis_app = start_app()
+
+QGIS_POSTGRES_CONF_TEMPLATE = """
+hba_file = '%(tempfolder)s/pg_hba.conf'
+listen_addresses = '*'
+port = %(port)s
+max_connections = 100
+unix_socket_directories = '%(tempfolder)s'
+ssl = true
+ssl_ciphers = 'DEFAULT:!LOW:!EXP:!MD5:@STRENGTH'	# allowed SSL ciphers
+ssl_cert_file = '%(server_cert)s'
+ssl_key_file = '%(server_key)s'
+ssl_ca_file = '%(sslrootcert_path)s'
+password_encryption = on
+"""
+
+QGIS_POSTGRES_HBA_TEMPLATE = """
+hostssl    all           all             0.0.0.0/0              cert clientcert=1
+hostssl    all           all             ::1/0                  cert clientcert=1
+host       all           all             127.0.0.1/32           trust
+host       all           all             ::1/32                 trust
+"""
+
+
+class TestAuthManager(unittest.TestCase):
+
+    @classmethod
+    def setUpAuth(cls):
+        """Run before all tests and set up authentication"""
+        authm = QgsAuthManager.instance()
+        assert (authm.setMasterPassword('masterpassword', True))
+        cls.pg_conf = os.path.join(cls.tempfolder, 'postgresql.conf')
+        cls.pg_hba = os.path.join(cls.tempfolder, 'pg_hba.conf')
+        # Client side
+        cls.sslrootcert_path = os.path.join(cls.certsdata_path, 'chains_subissuer-issuer-root_issuer2-root2.pem')
+        cls.sslcert = os.path.join(cls.certsdata_path, 'gerardus_cert.pem')
+        cls.sslkey = os.path.join(cls.certsdata_path, 'gerardus_key.pem')
+        assert os.path.isfile(cls.sslcert)
+        assert os.path.isfile(cls.sslkey)
+        assert os.path.isfile(cls.sslrootcert_path)
+        os.chmod(cls.sslcert, stat.S_IRUSR)
+        os.chmod(cls.sslkey, stat.S_IRUSR)
+        os.chmod(cls.sslrootcert_path, stat.S_IRUSR)
+        cls.auth_config = QgsAuthMethodConfig("PKI-Paths")
+        cls.auth_config.setConfig('certpath', cls.sslcert)
+        cls.auth_config.setConfig('keypath', cls.sslkey)
+        cls.auth_config.setName('test_pki_auth_config')
+        cls.username = 'Gerardus'
+        cls.sslrootcert = QSslCertificate.fromPath(cls.sslrootcert_path)
+        assert cls.sslrootcert is not None
+        authm.storeCertAuthorities(cls.sslrootcert)
+        authm.rebuildCaCertsCache()
+        authm.rebuildTrustedCaCertsCache()
+        authm.rebuildCertTrustCache()
+        assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
+        assert cls.auth_config.isValid()
+
+        # Server side
+        cls.server_cert = os.path.join(cls.certsdata_path, 'localhost_ssl_cert.pem')
+        cls.server_key = os.path.join(cls.certsdata_path, 'localhost_ssl_key.pem')
+        cls.server_rootcert = cls.sslrootcert_path
+        os.chmod(cls.server_cert, stat.S_IRUSR)
+        os.chmod(cls.server_key, stat.S_IRUSR)
+        os.chmod(cls.server_rootcert, stat.S_IRUSR)
+
+        # Place conf in the data folder
+        with open(cls.pg_conf, 'w+') as f:
+            f.write(QGIS_POSTGRES_CONF_TEMPLATE % {
+                'port': cls.port,
+                'tempfolder': cls.tempfolder,
+                'server_cert': cls.server_cert,
+                'server_key': cls.server_key,
+                'sslrootcert_path': cls.sslrootcert_path,
+            })
+
+        with open(cls.pg_hba, 'w+') as f:
+            f.write(QGIS_POSTGRES_HBA_TEMPLATE)
+
+    @classmethod
+    def setUpClass(cls):
+        """Run before all tests:
+        Creates an auth configuration"""
+        cls.port = QGIS_POSTGRES_SERVER_PORT
+        cls.dbname = 'test_pki'
+        cls.tempfolder = QGIS_PG_TEST_PATH
+        cls.certsdata_path = os.path.join(unitTestDataPath('auth_system'), 'certs_keys')
+        cls.hostname = 'localhost'
+        cls.data_path = os.path.join(cls.tempfolder, 'data')
+        os.mkdir(cls.data_path)
+
+        cls.setUpAuth()
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'initdb'), '-D', cls.data_path])
+
+        cls.server = subprocess.Popen([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'postgres'), '-D',
+                                       cls.data_path, '-c',
+                                       "config_file=%s" % cls.pg_conf],
+                                      env=os.environ,
+                                      stdout=subprocess.PIPE,
+                                      stderr=subprocess.PIPE)
+        # Wait max 10 secs for the server to start
+        end = time.time() + 10
+        while True:
+            line = cls.server.stderr.readline()
+            print(line)
+            if line.find(b"database system is ready to accept") != -1:
+                break
+            if time.time() > end:
+                raise Exception("Timeout connecting to postgresql")
+        # Create a DB
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'createdb'), '-h', 'localhost', '-p', cls.port, 'test_pki'])
+        # Inject test SQL from test path
+        test_sql = os.path.join(unitTestDataPath('provider'), 'testdata_pg.sql')
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'psql'), '-h', 'localhost', '-p', cls.port, '-f', test_sql, cls.dbname])
+        # Create a role
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'psql'), '-h', 'localhost', '-p', cls.port, '-c', 'CREATE ROLE "%s" WITH SUPERUSER LOGIN' % cls.username, cls.dbname])
+
+    @classmethod
+    def tearDownClass(cls):
+        """Run after all tests"""
+        cls.server.terminate()
+        os.kill(cls.server.pid, signal.SIGABRT)
+        del cls.server
+        time.sleep(2)
+        rmtree(QGIS_AUTH_DB_DIR_PATH)
+        rmtree(cls.tempfolder)
+
+    def setUp(self):
+        """Run before each test."""
+        pass
+
+    def tearDown(self):
+        """Run after each test."""
+        pass
+
+    @classmethod
+    def _getPostGISLayer(cls, type_name, layer_name=None, authcfg=None):
+        """
+        PG layer factory
+        """
+        if layer_name is None:
+            layer_name = 'pg_' + type_name
+        uri = QgsDataSourceURI()
+        uri.setWkbType(QgsWKBTypes.Point)
+        uri.setConnection("localhost", cls.port, cls.dbname, "", "", QgsDataSourceURI.SSLrequire, authcfg)
+        uri.setKeyColumn('pk')
+        uri.setSrid('EPSG:4326')
+        uri.setDataSource('qgis_test', 'someData', "geom", "", "pk")
+        # Note: do not expand here!
+        layer = QgsVectorLayer(uri.uri(False), layer_name, 'postgres')
+        return layer
+
+    def testValidAuthAccess(self):
+        """
+        Access the protected layer with valid credentials
+        """
+        pg_layer = self._getPostGISLayer('testlayer_èé', authcfg=self.auth_config.id())
+        self.assertTrue(pg_layer.isValid())
+
+    def testInvalidAuthAccess(self):
+        """
+        Access the protected layer with not valid credentials
+        """
+        pg_layer = self._getPostGISLayer('testlayer_èé')
+        self.assertFalse(pg_layer.isValid())
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py
index d2e0f0c..d2ce668 100644
--- a/tests/src/python/test_provider_ogr_gpkg.py
+++ b/tests/src/python/test_provider_ogr_gpkg.py
@@ -20,12 +20,11 @@ import shutil
 import glob
 from osgeo import gdal, ogr
 
+from qgis.PyQt.QtCore import QCoreApplication, QSettings
 from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsFeatureRequest, QgsRectangle
 from qgis.testing import start_app, unittest
 from utilities import unitTestDataPath
 
-start_app()
-
 
 def GDAL_COMPUTE_VERSION(maj, min, rev):
     return ((maj) * 1000000 + (min) * 10000 + (rev) * 100)
@@ -48,10 +47,17 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
         # Create test layer
         cls.basetestpath = tempfile.mkdtemp()
 
+        QCoreApplication.setOrganizationName("QGIS_Test")
+        QCoreApplication.setOrganizationDomain("TestPyQgsOGRProviderGpkg.com")
+        QCoreApplication.setApplicationName("TestPyQgsOGRProviderGpkg")
+        QSettings().clear()
+        start_app()
+
     @classmethod
     def tearDownClass(cls):
         """Run after all tests"""
         shutil.rmtree(cls.basetestpath, True)
+        QSettings().clear()
 
     def XXXXtestSingleToMultiPolygonPromotion(self):
 
@@ -149,10 +155,10 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
         self.internalTestBug15351('closeIter_commit_closeProvider')
 
     # We need GDAL 2.0 to issue PRAGMA journal_mode
-    def testBug15351_closeIter_commit_closeProvider(self):
+    def testBug15351_commit_closeProvider_closeIter(self):
         if int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 0, 0):
             return
-        self.internalTestBug15351('closeIter_commit_closeProvider')
+        self.internalTestBug15351('commit_closeProvider_closeIter')
 
     # We need GDAL 2.0 to issue PRAGMA journal_mode
     def testBug15351_commit_closeIter_closeProvider(self):
@@ -160,9 +166,10 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
             return
         self.internalTestBug15351('commit_closeIter_closeProvider')
 
-    @unittest.expectedFailure(int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 1, 2))
     def testGeopackageExtentUpdate(self):
         ''' test http://hub.qgis.org/issues/15273 '''
+        if int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 1, 2):
+            return
         tmpfile = os.path.join(self.basetestpath, 'testGeopackageExtentUpdate.gpkg')
         ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
         lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint)
@@ -205,5 +212,44 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
         self.assertTrue(QgsGeometry.compare(provider_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001),
                         provider_extent.asPolygon()[0])
 
+    def testDisablewalForSqlite3(self):
+        ''' Test disabling walForSqlite3 setting '''
+        QSettings().setValue("/qgis/walForSqlite3", False)
+
+        tmpfile = os.path.join(self.basetestpath, 'testDisablewalForSqlite3.gpkg')
+        ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
+        lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint)
+        lyr.CreateField(ogr.FieldDefn('attr0', ogr.OFTInteger))
+        lyr.CreateField(ogr.FieldDefn('attr1', ogr.OFTInteger))
+        f = ogr.Feature(lyr.GetLayerDefn())
+        f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(0 0)'))
+        lyr.CreateFeature(f)
+        f = None
+        ds = None
+
+        vl = QgsVectorLayer(u'{}'.format(tmpfile), u'test', u'ogr')
+
+        # Test that we are using default delete mode and not WAL
+        ds = ogr.Open(tmpfile)
+        lyr = ds.ExecuteSQL('PRAGMA journal_mode')
+        f = lyr.GetNextFeature()
+        res = f.GetField(0)
+        ds.ReleaseResultSet(lyr)
+        ds = None
+        self.assertEqual(res, 'delete')
+
+        self.assertTrue(vl.startEditing())
+        feature = vl.getFeatures().next()
+        self.assertTrue(vl.changeAttributeValue(feature.id(), 1, 1001))
+
+        # Commit changes
+        cbk = ErrorReceiver()
+        vl.dataProvider().raiseError.connect(cbk.receiveError)
+        self.assertTrue(vl.commitChanges())
+        self.assertIsNone(cbk.msg)
+        vl = None
+
+        QSettings().setValue("/qgis/walForSqlite3", None)
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/src/python/test_qgsfiledownloader.py b/tests/src/python/test_qgsfiledownloader.py
new file mode 100644
index 0000000..776ad49
--- /dev/null
+++ b/tests/src/python/test_qgsfiledownloader.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+"""
+Test the QgsFileDownloader class
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+from __future__ import print_function
+from future import standard_library
+import os
+import tempfile
+from functools import partial
+from qgis.PyQt.QtCore import QEventLoop, QUrl, QTimer
+from qgis.gui import (QgsFileDownloader,)
+from qgis.testing import start_app, unittest
+
+standard_library.install_aliases()
+
+__author__ = 'Alessandro Pasotti'
+__date__ = '08/11/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+
+start_app()
+
+
+class TestQgsFileDownloader(unittest.TestCase):
+
+    """
+    This class tests the QgsFileDownloader class
+    """
+
+    def _make_download(self, url, destination, cancel=False):
+        self.completed_was_called = False
+        self.error_was_called = False
+        self.canceled_was_called = False
+        self.progress_was_called = False
+        self.exited_was_called = False
+
+        loop = QEventLoop()
+
+        downloader = QgsFileDownloader(QUrl(url), destination, False)
+        downloader.downloadCompleted.connect(partial(self._set_slot, 'completed'))
+        downloader.downloadExited.connect(partial(self._set_slot, 'exited'))
+        downloader.downloadCanceled.connect(partial(self._set_slot, 'canceled'))
+        downloader.downloadError.connect(partial(self._set_slot, 'error'))
+        downloader.downloadProgress.connect(partial(self._set_slot, 'progress'))
+
+        downloader.downloadExited.connect(loop.quit)
+
+        if cancel:
+            downloader.downloadProgress.connect(downloader.onDownloadCanceled)
+
+        loop.exec_()
+
+    def test_validDownload(self):
+        """Tests a valid download"""
+        destination = tempfile.mktemp()
+        self._make_download('http://www.qgis.org', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertTrue(self.completed_was_called)
+        self.assertTrue(self.progress_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertFalse(self.error_was_called)
+        self.assertTrue(os.path.isfile(destination))
+        self.assertGreater(os.path.getsize(destination), 0)
+
+    def test_inValidDownload(self):
+        """Tests an invalid download"""
+        destination = tempfile.mktemp()
+        self._make_download('http://www.doesnotexistofthatimsure.qgis', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertTrue(self.progress_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertEqual(self.error_args[1], [u'Network error 3: Host www.doesnotexistofthatimsure.qgis not found'])
+        self.assertFalse(os.path.isfile(destination))
+
+    def test_dowloadCanceled(self):
+        """Tests user canceled download"""
+        destination = tempfile.mktemp()
+        self._make_download('https://github.com/qgis/QGIS/archive/master.zip', destination, True)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertTrue(self.canceled_was_called)
+        self.assertFalse(self.error_was_called)
+        self.assertFalse(os.path.isfile(destination))
+
+    def test_InvalidUrl(self):
+        destination = tempfile.mktemp()
+        self._make_download('xyz://www', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertFalse(os.path.isfile(destination))
+        self.assertEqual(self.error_args[1], [u"Network error 301: Protocol \"xyz\" is unknown"])
+
+    def test_InvalidFile(self):
+        self._make_download('https://github.com/qgis/QGIS/archive/master.zip', "")
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertEqual(self.error_args[1], [u"Cannot open output file: "])
+
+    def test_BlankUrl(self):
+        destination = tempfile.mktemp()
+        self._make_download('', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertFalse(os.path.isfile(destination))
+        self.assertEqual(self.error_args[1], [u"Network error 301: Protocol \"\" is unknown"])
+
+    def ssl_compare(self, name, url, error):
+        destination = tempfile.mktemp()
+        self._make_download(url, destination)
+        msg = "Failed in %s: %s" % (name, url)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called, msg)
+        self.assertFalse(self.canceled_was_called, msg)
+        self.assertTrue(self.error_was_called, msg)
+        self.assertFalse(os.path.isfile(destination), msg)
+        result = sorted(self.error_args[1])
+        result = ';'.join(result)
+        self.assertEqual(result, error, msg + "expected:\n%s\nactual:\n%s\n" % (result, error))
+
+    def test_sslExpired(self):
+        self.ssl_compare("expired", "https://expired.badssl.com/", "Network error 6: SSL handshake failed;SSL Errors: ;The certificate has expired")
+        self.ssl_compare("self-signed", "https://self-signed.badssl.com/", "Network error 6: SSL handshake failed;SSL Errors: ;The certificate is self-signed, and untrusted")
+        self.ssl_compare("untrusted-root", "https://untrusted-root.badssl.com/", "Network error 6: SSL handshake failed;No certificates could be verified;SSL Errors: ;The issuer certificate of a locally looked up certificate could not be found;The root CA certificate is not trusted for this purpose")
+
+    def _set_slot(self, *args, **kwargs):
+        #print('_set_slot(%s) called' % args[0])
+        setattr(self, args[0] + '_was_called', True)
+        setattr(self, args[0] + '_args', args)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_qgsserver.py b/tests/src/python/test_qgsserver.py
index ef25238..99a3ec4 100644
--- a/tests/src/python/test_qgsserver.py
+++ b/tests/src/python/test_qgsserver.py
@@ -152,7 +152,7 @@ class TestQgsServer(unittest.TestCase):
 
     # WMS tests
     def wms_request_compare(self, request, extra=None, reference_file=None):
-        project = self.testdata_path + "test+project.qgs"
+        project = self.testdata_path + "test_project.qgs"
         assert os.path.exists(project), "Project file not found: " + project
 
         query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (urllib.quote(project), request)
@@ -206,7 +206,7 @@ class TestQgsServer(unittest.TestCase):
 
     def wms_inspire_request_compare(self, request):
         """WMS INSPIRE tests"""
-        project = self.testdata_path + "test+project_inspire.qgs"
+        project = self.testdata_path + "test_project_inspire.qgs"
         assert os.path.exists(project), "Project file not found: " + project
 
         query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.quote(project), request)
@@ -235,7 +235,7 @@ class TestQgsServer(unittest.TestCase):
 
     # WFS tests
     def wfs_request_compare(self, request):
-        project = self.testdata_path + "test+project_wfs.qgs"
+        project = self.testdata_path + "test_project_wfs.qgs"
         assert os.path.exists(project), "Project file not found: " + project
 
         query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
@@ -269,7 +269,7 @@ class TestQgsServer(unittest.TestCase):
             self.wfs_request_compare(request)
 
     def wfs_getfeature_compare(self, requestid, request):
-        project = self.testdata_path + "test+project_wfs.qgs"
+        project = self.testdata_path + "test_project_wfs.qgs"
         assert os.path.exists(project), "Project file not found: " + project
 
         query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
@@ -316,7 +316,7 @@ class TestQgsServer(unittest.TestCase):
             self.wfs_getfeature_compare(id, req)
 
     def wfs_getfeature_post_compare(self, requestid, request):
-        project = self.testdata_path + "test+project_wfs.qgs"
+        project = self.testdata_path + "test_project_wfs.qgs"
         assert os.path.exists(project), "Project file not found: " + project
 
         query_string = 'MAP={}'.format(urllib.quote(project))
@@ -361,7 +361,7 @@ class TestQgsServer(unittest.TestCase):
     def test_getLegendGraphics(self):
         """Test that does not return an exception but an image"""
         parms = {
-            'MAP': self.testdata_path + "test%2Bproject.qgs",
+            'MAP': self.testdata_path + "test_project.qgs",
             'SERVICE': 'WMS',
             'VERSIONE': '1.0.0',
             'REQUEST': 'GetLegendGraphic',
@@ -378,7 +378,7 @@ class TestQgsServer(unittest.TestCase):
     def test_getLegendGraphics_layertitle(self):
         """Test that does not return an exception but an image"""
         parms = {
-            'MAP': self.testdata_path + "test%2Bproject.qgs",
+            'MAP': self.testdata_path + "test_project.qgs",
             'SERVICE': 'WMS',
             'VERSION': '1.3.0',
             'REQUEST': 'GetLegendGraphic',
@@ -393,7 +393,7 @@ class TestQgsServer(unittest.TestCase):
         self._img_diff_error(r, h, "WMS_GetLegendGraphic_test", 250, QSize(10, 10))
 
         parms = {
-            'MAP': self.testdata_path + "test%2Bproject.qgs",
+            'MAP': self.testdata_path + "test_project.qgs",
             'SERVICE': 'WMS',
             'VERSION': '1.3.0',
             'REQUEST': 'GetLegendGraphic',
diff --git a/tests/src/python/utilities.py b/tests/src/python/utilities.py
index b6faa51..0d6f88a 100644
--- a/tests/src/python/utilities.py
+++ b/tests/src/python/utilities.py
@@ -17,6 +17,10 @@ import sys
 import glob
 import platform
 import tempfile
+try:
+    from urllib2 import urlopen, HTTPError, URLError
+except ImportError:
+    from urllib.request import urlopen, HTTPError, URLError
 
 from PyQt4.QtCore import QSize, QDir
 from PyQt4.QtGui import QWidget
@@ -748,3 +752,22 @@ class DoxygenParser():
             if doc is not None and list(doc):
                 return True
         return False
+
+
+def waitServer(url, timeout=10):
+    """ Wait for a server to be online and to respond
+        HTTP errors are ignored
+        @param timeout: in seconds
+        @return: True of False
+    """
+    from time import time as now
+    end = now() + timeout
+    while True:
+        try:
+            urlopen(url, timeout=1)
+            return True
+        except (HTTPError, URLError):
+            return True
+        except Exception as e:
+            if now() > end:
+                return False
diff --git a/tests/testdata/qgis_server/getprojectsettings.txt b/tests/testdata/qgis_server/getprojectsettings.txt
index cda888d..7d25c2e 100644
--- a/tests/testdata/qgis_server/getprojectsettings.txt
+++ b/tests/testdata/qgis_server/getprojectsettings.txt
@@ -2,7 +2,7 @@ Content-Length: 6268
 Content-Type: text/xml; charset=utf-8
 
 <?xml version="1.0" encoding="utf-8"?>
-<WMS_Capabilities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.3.0" xmlns="http://www.opengis.net/wms" xsi:schemaLocation="http://www.opengis.net/wms http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd http://www.qgis.org/wms http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&SERVICE=WMS&REQUEST=GetSchemaExtension" xmlns:sld="http://www.opengis. [...]
+<WMS_Capabilities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.3.0" xmlns="http://www.opengis.net/wms" xsi:schemaLocation="http://www.opengis.net/wms http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd http://www.qgis.org/wms http:?&SERVICE=WMS&REQUEST=GetSchemaExtension" xmlns:sld="http://www.opengis.net/sld" xmlns:qgs="http://www.qgis.org/wms">
  <Service>
   <Name>WMS</Name>
   <Title>QGIS TestProject</Title>
@@ -30,7 +30,7 @@ Content-Type: text/xml; charset=utf-8
     <DCPType>
      <HTTP>
       <Get>
-       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&"/>
+       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&"/>
       </Get>
      </HTTP>
     </DCPType>
@@ -45,7 +45,7 @@ Content-Type: text/xml; charset=utf-8
     <DCPType>
      <HTTP>
       <Get>
-       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&"/>
+       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&"/>
       </Get>
      </HTTP>
     </DCPType>
@@ -59,7 +59,7 @@ Content-Type: text/xml; charset=utf-8
     <DCPType>
      <HTTP>
       <Get>
-       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&"/>
+       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&"/>
       </Get>
      </HTTP>
     </DCPType>
@@ -70,7 +70,7 @@ Content-Type: text/xml; charset=utf-8
     <DCPType>
      <HTTP>
       <Get>
-       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&"/>
+       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&"/>
       </Get>
      </HTTP>
     </DCPType>
@@ -80,7 +80,7 @@ Content-Type: text/xml; charset=utf-8
     <DCPType>
      <HTTP>
       <Get>
-       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&"/>
+       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&"/>
       </Get>
      </HTTP>
     </DCPType>
@@ -90,7 +90,7 @@ Content-Type: text/xml; charset=utf-8
     <DCPType>
      <HTTP>
       <Get>
-       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&"/>
+       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&"/>
       </Get>
      </HTTP>
     </DCPType>
@@ -102,7 +102,7 @@ Content-Type: text/xml; charset=utf-8
     <DCPType>
      <HTTP>
       <Get>
-       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&"/>
+       <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&"/>
       </Get>
      </HTTP>
     </DCPType>
@@ -112,6 +112,9 @@ Content-Type: text/xml; charset=utf-8
    <Format>text/xml</Format>
   </Exception>
   <sld:UserDefinedSymbolization RemoteWFS="0" RemoteWCS="0" InlineFeature="0" UserStyle="1" SupportSLD="1" UserLayer="0"/>
+  <WFSLayers>
+   <WFSLayer name="testlayer èé"/>
+  </WFSLayers>
   <Layer queryable="1">
    <Name>QGIS Test Project</Name>
    <Title>QGIS Test Project</Title>
@@ -145,7 +148,7 @@ Content-Type: text/xml; charset=utf-8
      <Title>default</Title>
      <LegendURL>
       <Format>image/png</Format>
-      <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test%2Bproject.qgs&&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetLegendGraphic&LAYER=testlayer èé&FORMAT=image/png&STYLE=default&SLD_VERSION=1.1.0"/>
+      <OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http:?&&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetLegendGraphic&LAYER=testlayer èé&FORMAT=image/png&STYLE=default&SLD_VERSION=1.1.0"/>
      </LegendURL>
     </Style>
     <TreeName>testlayer èé</TreeName>
diff --git a/tests/testdata/qgis_server/test+project.qgs b/tests/testdata/qgis_server/test_project.qgs
similarity index 79%
rename from tests/testdata/qgis_server/test+project.qgs
rename to tests/testdata/qgis_server/test_project.qgs
index c3dbec9..368e132 100644
--- a/tests/testdata/qgis_server/test+project.qgs
+++ b/tests/testdata/qgis_server/test_project.qgs
@@ -1,6 +1,8 @@
 <!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
-<qgis projectname="QGIS Test Project" version="2.9.0-Master">
+<qgis projectname="QGIS Test Project" version="2.17.0-Master">
   <title>QGIS Test Project</title>
+  <autotransaction active="0"/>
+  <evaluateDefaultValues active="0"/>
   <layer-tree-group expanded="1" checked="Qt::Checked" name="">
     <customproperties/>
     <layer-tree-layer expanded="1" checked="Qt::Checked" id="testlayer20150528120452665" name="testlayer èé">
@@ -30,11 +32,11 @@
         <geographicflag>true</geographicflag>
       </spatialrefsys>
     </destinationsrs>
+    <rendermaptile>0</rendermaptile>
     <layer_coordinate_transform_info>
       <layer_coordinate_transform destAuthId="EPSG:4326" srcAuthId="EPSG:4326" srcDatumTransform="-1" destDatumTransform="-1" layerid="testlayer20150528120452665"/>
     </layer_coordinate_transform_info>
   </mapcanvas>
-  <visibility-presets/>
   <layer-tree-canvas>
     <custom-order enabled="0">
       <item>testlayer20150528120452665</item>
@@ -48,7 +50,7 @@
     </legendlayer>
   </legend>
   <projectlayers>
-    <maplayer minimumScale="-4.65661e-10" maximumScale="1e+08" simplifyDrawingHints="0" minLabelScale="0" maxLabelScale="1e+08" simplifyDrawingTol="1" geometry="Point" simplifyMaxScale="1" type="vector" hasScaleBasedVisibilityFlag="0" simplifyLocal="1" scaleBasedLabelVisibilityFlag="0">
+    <maplayer simplifyAlgorithm="0" minimumScale="-4.65661e-10" maximumScale="1e+08" simplifyDrawingHints="0" readOnly="0" minLabelScale="0" maxLabelScale="1e+08" simplifyDrawingTol="1" geometry="Point" simplifyMaxScale="1" type="vector" hasScaleBasedVisibilityFlag="0" simplifyLocal="1" scaleBasedLabelVisibilityFlag="0">
       <id>testlayer20150528120452665</id>
       <datasource>./testlayer.shp</datasource>
       <title>A test vector layer</title>
@@ -72,40 +74,42 @@
       <provider encoding="UTF-8">ogr</provider>
       <previewExpression></previewExpression>
       <vectorjoins/>
+      <layerDependencies/>
       <expressionfields/>
       <map-layer-style-manager current="">
         <map-layer-style name=""/>
       </map-layer-style-manager>
       <edittypes>
         <edittype widgetv2type="TextEdit" name="id">
-          <widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
+          <widgetv2config IsMultiline="0" fieldEditable="1" constraint="" UseHtml="0" labelOnTop="0" constraintDescription="" notNull="0"/>
         </edittype>
         <edittype widgetv2type="TextEdit" name="name">
-          <widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
+          <widgetv2config IsMultiline="0" fieldEditable="1" constraint="" UseHtml="0" labelOnTop="0" constraintDescription="" notNull="0"/>
         </edittype>
         <edittype widgetv2type="TextEdit" name="utf8nameè">
-          <widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
+          <widgetv2config IsMultiline="0" fieldEditable="1" constraint="" UseHtml="0" labelOnTop="0" constraintDescription="" notNull="0"/>
         </edittype>
       </edittypes>
-      <renderer-v2 symbollevels="0" type="singleSymbol">
+      <renderer-v2 forceraster="0" symbollevels="0" type="singleSymbol" enableorderby="0">
         <symbols>
           <symbol alpha="1" clip_to_extent="1" type="marker" name="0">
             <layer pass="0" class="SimpleMarker" locked="0">
               <prop k="angle" v="0"/>
               <prop k="color" v="102,164,67,255"/>
               <prop k="horizontal_anchor_point" v="1"/>
+              <prop k="joinstyle" v="bevel"/>
               <prop k="name" v="circle"/>
               <prop k="offset" v="0,0"/>
-              <prop k="offset_map_unit_scale" v="0,0"/>
+              <prop k="offset_map_unit_scale" v="0,0,0,0,0,0"/>
               <prop k="offset_unit" v="MM"/>
               <prop k="outline_color" v="0,0,0,255"/>
               <prop k="outline_style" v="solid"/>
               <prop k="outline_width" v="0"/>
-              <prop k="outline_width_map_unit_scale" v="0,0"/>
+              <prop k="outline_width_map_unit_scale" v="0,0,0,0,0,0"/>
               <prop k="outline_width_unit" v="MM"/>
               <prop k="scale_method" v="area"/>
               <prop k="size" v="2"/>
-              <prop k="size_map_unit_scale" v="0,0"/>
+              <prop k="size_map_unit_scale" v="0,0,0,0,0,0"/>
               <prop k="size_unit" v="MM"/>
               <prop k="vertical_anchor_point" v="1"/>
               <effect enabled="0" type="effectStack">
@@ -120,7 +124,7 @@
           </symbol>
         </symbols>
         <rotation/>
-        <sizescale scalemethod="area"/>
+        <sizescale scalemethod="diameter"/>
         <effect enabled="0" type="effectStack">
           <effect type="drawSource">
             <prop k="blend_mode" v="0"/>
@@ -130,6 +134,7 @@
           </effect>
         </effect>
       </renderer-v2>
+      <labeling type="simple"/>
       <customproperties>
         <property key="labeling" value="pal"/>
         <property key="labeling/addDirectionSymbol" value="false"/>
@@ -298,82 +303,115 @@
         <multilineenabled fieldname="" on=""/>
         <selectedonly on=""/>
       </labelattributes>
-      <SingleCategoryDiagramRenderer diagramType="Pie">
-        <DiagramCategory penColor="#000000" labelPlacementMethod="XHeight" penWidth="0" diagramOrientation="Up" minimumSize="0" barWidth="5" penAlpha="255" maxScaleDenominator="1e+08" font="Ubuntu,9,-1,5,50,0,0,0,0,0" backgroundColor="#ffffff" transparency="0" width="15" scaleDependency="Area" backgroundAlpha="255" angleOffset="1440" scaleBasedVisibility="0" enabled="0" height="15" sizeType="MM" minScaleDenominator="-4.65661e-10"/>
+      <SingleCategoryDiagramRenderer diagramType="Pie" sizeLegend="0" attributeLegend="1">
+        <DiagramCategory penColor="#000000" labelPlacementMethod="XHeight" penWidth="0" diagramOrientation="Up" sizeScale="0,0,0,0,0,0" minimumSize="0" barWidth="5" penAlpha="255" maxScaleDenominator="1e+08" backgroundColor="#ffffff" transparency="0" width="15" scaleDependency="Area" backgroundAlpha="255" angleOffset="1440" scaleBasedVisibility="0" enabled="0" height="15" lineSizeScale="0,0,0,0,0,0" sizeType="MM" lineSizeType="MM" minScaleDenominator="-4.65661e-10">
+          <fontProperties description="Ubuntu,9,-1,5,50,0,0,0,0,0" style=""/>
+          <attribute field="" color="#000000" label=""/>
+        </DiagramCategory>
+        <symbol alpha="1" clip_to_extent="1" type="marker" name="sizeSymbol">
+          <layer pass="0" class="SimpleMarker" locked="0">
+            <prop k="angle" v="0"/>
+            <prop k="color" v="255,0,0,255"/>
+            <prop k="horizontal_anchor_point" v="1"/>
+            <prop k="joinstyle" v="bevel"/>
+            <prop k="name" v="circle"/>
+            <prop k="offset" v="0,0"/>
+            <prop k="offset_map_unit_scale" v="0,0,0,0,0,0"/>
+            <prop k="offset_unit" v="MM"/>
+            <prop k="outline_color" v="0,0,0,255"/>
+            <prop k="outline_style" v="solid"/>
+            <prop k="outline_width" v="0"/>
+            <prop k="outline_width_map_unit_scale" v="0,0,0,0,0,0"/>
+            <prop k="outline_width_unit" v="MM"/>
+            <prop k="scale_method" v="diameter"/>
+            <prop k="size" v="2"/>
+            <prop k="size_map_unit_scale" v="0,0,0,0,0,0"/>
+            <prop k="size_unit" v="MM"/>
+            <prop k="vertical_anchor_point" v="1"/>
+          </layer>
+        </symbol>
       </SingleCategoryDiagramRenderer>
-      <DiagramLayerSettings yPosColumn="-1" linePlacementFlags="10" placement="0" dist="0" xPosColumn="-1" priority="0" obstacle="0" showAll="1"/>
-      <editform></editform>
+      <DiagramLayerSettings yPosColumn="-1" showColumn="0" linePlacementFlags="10" placement="0" dist="0" xPosColumn="-1" priority="0" obstacle="0" zIndex="0" showAll="1"/>
+      <annotationform>.</annotationform>
+      <excludeAttributesWMS/>
+      <excludeAttributesWFS/>
+      <attributeactions default="0"/>
+      <attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
+        <columns/>
+      </attributetableconfig>
+      <editform>.</editform>
       <editforminit/>
+      <editforminitcodesource>0</editforminitcodesource>
+      <editforminitfilepath></editforminitfilepath>
+      <editforminitcode><![CDATA[]]></editforminitcode>
       <featformsuppress>0</featformsuppress>
-      <annotationform></annotationform>
       <editorlayout>generatedlayout</editorlayout>
-      <excludeAttributesWMS/>
-      <excludeAttributesWFS/>
-      <attributeactions/>
-      <edittypes>
-        <edittype widgetv2type="TextEdit" name="id">
-          <widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
-        </edittype>
-        <edittype widgetv2type="TextEdit" name="name">
-          <widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
-        </edittype>
-        <edittype widgetv2type="TextEdit" name="utf8nameè">
-          <widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
-        </edittype>
-      </edittypes>
+      <widgets/>
+      <conditionalstyles>
+        <rowstyles/>
+        <fieldstyles/>
+      </conditionalstyles>
     </maplayer>
   </projectlayers>
   <properties>
-    <WMSContactPerson type="QString">Alessandro Pasotti</WMSContactPerson>
-    <WMSOnlineResource type="QString"></WMSOnlineResource>
-    <WMSUseLayerIDs type="bool">false</WMSUseLayerIDs>
-    <WMSContactOrganization type="QString">QGIS dev team</WMSContactOrganization>
+    <WMSUrl type="QString"></WMSUrl>
+    <SpatialRefSys>
+      <ProjectCRSProj4String type="QString">+proj=longlat +datum=WGS84 +no_defs</ProjectCRSProj4String>
+      <ProjectCrs type="QString">EPSG:4326</ProjectCrs>
+      <ProjectCRSID type="int">3452</ProjectCRSID>
+      <ProjectionsEnabled type="int">1</ProjectionsEnabled>
+    </SpatialRefSys>
+    <Measurement>
+      <DistanceUnits type="QString">meters</DistanceUnits>
+      <AreaUnits type="QString">m2</AreaUnits>
+    </Measurement>
+    <Legend>
+      <filterByMap type="bool">false</filterByMap>
+    </Legend>
     <WMSExtent type="QStringList">
       <value>8.20315414376310059</value>
       <value>44.901236559338642</value>
       <value>8.204164917965862</value>
       <value>44.90159838674664172</value>
     </WMSExtent>
-    <WMSKeywordList type="QStringList">
-      <value></value>
-    </WMSKeywordList>
-    <WFSUrl type="QString"></WFSUrl>
-    <Paths>
-      <Absolute type="bool">false</Absolute>
-    </Paths>
-    <WMSServiceTitle type="QString">QGIS TestProject</WMSServiceTitle>
-    <WFSLayers type="QStringList"/>
+    <DefaultStyles>
+      <Fill type="QString"></Fill>
+      <Line type="QString"></Line>
+      <Marker type="QString"></Marker>
+      <RandomColors type="bool">true</RandomColors>
+      <AlphaInt type="int">255</AlphaInt>
+      <ColorRamp type="QString"></ColorRamp>
+    </DefaultStyles>
+    <WMSAccessConstraints type="QString">None</WMSAccessConstraints>
     <WMSContactMail type="QString">elpaso at itopen.it</WMSContactMail>
+    <WMSImageQuality type="int">90</WMSImageQuality>
+    <WFSLayersPrecision>
+      <testlayer20150528120452665 type="int">8</testlayer20150528120452665>
+    </WFSLayersPrecision>
     <WMSRestrictedComposers type="QStringList"/>
-    <WMSRestrictedLayers type="QStringList"/>
-    <PositionPrecision>
-      <DecimalPlaces type="int">2</DecimalPlaces>
-      <Automatic type="bool">true</Automatic>
-      <DegreeFormat type="QString">D</DegreeFormat>
-    </PositionPrecision>
-    <WCSUrl type="QString"></WCSUrl>
-    <WMSServiceCapabilities type="bool">true</WMSServiceCapabilities>
+    <WMSServiceTitle type="QString">QGIS TestProject</WMSServiceTitle>
     <WMSContactPhone type="QString"></WMSContactPhone>
-    <WMSServiceAbstract type="QString">Some UTF8 text èòù</WMSServiceAbstract>
-    <WMSAddWktGeometry type="bool">true</WMSAddWktGeometry>
-    <Measure>
-      <Ellipsoid type="QString">WGS84</Ellipsoid>
-    </Measure>
-    <WMSPrecision type="QString">4</WMSPrecision>
     <WFSTLayers>
-      <Insert type="QStringList"/>
-      <Update type="QStringList"/>
-      <Delete type="QStringList"/>
+      <Insert type="QStringList">
+        <value>testlayer20150528120452665</value>
+      </Insert>
+      <Update type="QStringList">
+        <value>testlayer20150528120452665</value>
+      </Update>
+      <Delete type="QStringList">
+        <value>testlayer20150528120452665</value>
+      </Delete>
     </WFSTLayers>
-    <Gui>
-      <SelectionColorBluePart type="int">0</SelectionColorBluePart>
-      <CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
-      <CanvasColorRedPart type="int">255</CanvasColorRedPart>
-      <SelectionColorRedPart type="int">255</SelectionColorRedPart>
-      <SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
-      <SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
-      <CanvasColorBluePart type="int">255</CanvasColorBluePart>
-    </Gui>
+    <WCSLayers type="QStringList"/>
+    <WMSRestrictedLayers type="QStringList"/>
+    <WMSFees type="QString">conditions unknown</WMSFees>
+    <Macros>
+      <pythonCode type="QString"></pythonCode>
+    </Macros>
+    <WMSAddWktGeometry type="bool">true</WMSAddWktGeometry>
+    <WCSUrl type="QString"></WCSUrl>
+    <WMSOnlineResource type="QString"></WMSOnlineResource>
+    <WMSPrecision type="QString">4</WMSPrecision>
     <Digitizing>
       <DefaultSnapToleranceUnit type="int">2</DefaultSnapToleranceUnit>
       <LayerSnappingList type="QStringList"/>
@@ -389,30 +427,43 @@
     <Identify>
       <disabledLayers type="QStringList"/>
     </Identify>
-    <Macros>
-      <pythonCode type="QString"></pythonCode>
-    </Macros>
-    <WMSAccessConstraints type="QString"></WMSAccessConstraints>
-    <WCSLayers type="QStringList"/>
-    <Legend>
-      <filterByMap type="bool">false</filterByMap>
-    </Legend>
-    <SpatialRefSys>
-      <ProjectCRSProj4String type="QString">+proj=longlat +datum=WGS84 +no_defs</ProjectCRSProj4String>
-      <ProjectCrs type="QString">EPSG:4326</ProjectCrs>
-      <ProjectCRSID type="int">3452</ProjectCRSID>
-      <ProjectionsEnabled type="int">1</ProjectionsEnabled>
-    </SpatialRefSys>
-    <DefaultStyles>
-      <Fill type="QString"></Fill>
-      <Line type="QString"></Line>
-      <Marker type="QString"></Marker>
-      <RandomColors type="bool">true</RandomColors>
-      <AlphaInt type="int">255</AlphaInt>
-      <ColorRamp type="QString"></ColorRamp>
-    </DefaultStyles>
-    <WMSFees type="QString"></WMSFees>
-    <WMSImageQuality type="int">90</WMSImageQuality>
-    <WMSUrl type="QString"></WMSUrl>
+    <WMSContactPerson type="QString">Alessandro Pasotti</WMSContactPerson>
+    <WMSContactOrganization type="QString">QGIS dev team</WMSContactOrganization>
+    <WMSKeywordList type="QStringList">
+      <value></value>
+    </WMSKeywordList>
+    <Paths>
+      <Absolute type="bool">false</Absolute>
+    </Paths>
+    <Variables>
+      <variableNames type="QStringList"/>
+      <variableValues type="QStringList"/>
+    </Variables>
+    <WMSContactPosition type="QString"></WMSContactPosition>
+    <PositionPrecision>
+      <DecimalPlaces type="int">2</DecimalPlaces>
+      <Automatic type="bool">true</Automatic>
+      <DegreeFormat type="QString">D</DegreeFormat>
+    </PositionPrecision>
+    <Gui>
+      <SelectionColorBluePart type="int">0</SelectionColorBluePart>
+      <CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
+      <CanvasColorRedPart type="int">255</CanvasColorRedPart>
+      <SelectionColorRedPart type="int">255</SelectionColorRedPart>
+      <SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
+      <SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
+      <CanvasColorBluePart type="int">255</CanvasColorBluePart>
+    </Gui>
+    <Measure>
+      <Ellipsoid type="QString">WGS84</Ellipsoid>
+    </Measure>
+    <WMSServiceAbstract type="QString">Some UTF8 text èòù</WMSServiceAbstract>
+    <WFSLayers type="QStringList">
+      <value>testlayer20150528120452665</value>
+    </WFSLayers>
+    <WFSUrl type="QString"></WFSUrl>
+    <WMSServiceCapabilities type="bool">true</WMSServiceCapabilities>
+    <WMSUseLayerIDs type="bool">false</WMSUseLayerIDs>
   </properties>
+  <visibility-presets/>
 </qgis>
diff --git a/tests/testdata/qgis_server/test+project_inspire.qgs b/tests/testdata/qgis_server/test_project_inspire.qgs
similarity index 100%
rename from tests/testdata/qgis_server/test+project_inspire.qgs
rename to tests/testdata/qgis_server/test_project_inspire.qgs
diff --git a/tests/testdata/qgis_server/test+project_wfs.qgs b/tests/testdata/qgis_server/test_project_wfs.qgs
similarity index 100%
rename from tests/testdata/qgis_server/test+project_wfs.qgs
rename to tests/testdata/qgis_server/test_project_wfs.qgs

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/qgis.git



More information about the Pkg-grass-devel mailing list