diff --git a/.travis.yml b/.travis.yml
index 2b3e2a6ce759b8f3b3d3feb1adf8b18b17977607..f3a65a7f60038d1cee4833ce6ffff35578f9170d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -117,7 +117,7 @@ before_script:
   - mysql -e "SELECT VERSION();"
 
   # configure mysql
-  - mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION'" # Travis default
+  - mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES'" # Travis default
 
   # Uncomment to enable sql_mode STRICT_TRANS_TABLES (new default in Mysql 5.6)
   #- mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES'"
diff --git a/composer.json b/composer.json
index ada86611922be463cd51d8e892c831a1fcedfad2..f68897fe60f38e95725f47ac7588b1eea2041431 100644
--- a/composer.json
+++ b/composer.json
@@ -18,6 +18,18 @@
         "wiki": "http://dev.piwik.org/",
         "source": "https://github.com/piwik/piwik"
     },
+    "autoload": {
+        "psr-4": {
+            "Piwik\\Plugins\\": "plugins/",
+            "Piwik\\": "core/"
+        },
+        "psr-0": {
+            "Zend_": "libs/",
+            "HTML_": "libs/",
+            "PEAR_": "libs/",
+            "Archive_": "libs/"
+        }
+    },
     "require": {
         "php": ">=5.3.2",
         "twig/twig": "1.*",
diff --git a/composer.lock b/composer.lock
index 257eeb64a423b7554e369397230483e576e6876e..7f47a22d7fe291c9087a7bec56836f07b68b374e 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "ba9388aa9f61e6a370364aadbf0316ad",
+    "hash": "aca9f46b026a9e629b4a11e3e57428c0",
     "packages": [
         {
             "name": "leafo/lessphp",
@@ -96,16 +96,16 @@
         },
         {
             "name": "piwik/device-detector",
-            "version": "2.3",
+            "version": "2.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/piwik/device-detector.git",
-                "reference": "c881d3592b55253c4e8968245e7b162f453ca4b9"
+                "reference": "9cd0338be126aaf947f20fd08a6381c0c658dca5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/piwik/device-detector/zipball/c881d3592b55253c4e8968245e7b162f453ca4b9",
-                "reference": "c881d3592b55253c4e8968245e7b162f453ca4b9",
+                "url": "https://api.github.com/repos/piwik/device-detector/zipball/9cd0338be126aaf947f20fd08a6381c0c658dca5",
+                "reference": "9cd0338be126aaf947f20fd08a6381c0c658dca5",
                 "shasum": ""
             },
             "require": {
@@ -123,7 +123,7 @@
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "GPL-3.0+"
+                "LGPL-3.0+"
             ],
             "authors": [
                 {
@@ -139,21 +139,21 @@
                 "parser",
                 "useragent"
             ],
-            "time": "2014-07-29 10:06:51"
+            "time": "2014-08-11 20:21:18"
         },
         {
             "name": "symfony/console",
-            "version": "v2.5.0",
+            "version": "v2.5.3",
             "target-dir": "Symfony/Component/Console",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/Console.git",
-                "reference": "ef4ca73b0b3a10cbac653d3ca482d0cdd4502b2c"
+                "reference": "cd2d1e4bac2206b337326b0140ff475fe9ad5f63"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Console/zipball/ef4ca73b0b3a10cbac653d3ca482d0cdd4502b2c",
-                "reference": "ef4ca73b0b3a10cbac653d3ca482d0cdd4502b2c",
+                "url": "https://api.github.com/repos/symfony/Console/zipball/cd2d1e4bac2206b337326b0140ff475fe9ad5f63",
+                "reference": "cd2d1e4bac2206b337326b0140ff475fe9ad5f63",
                 "shasum": ""
             },
             "require": {
@@ -183,20 +183,18 @@
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com",
-                    "homepage": "http://fabien.potencier.org",
-                    "role": "Lead Developer"
-                },
                 {
                     "name": "Symfony Community",
                     "homepage": "http://symfony.com/contributors"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
                 }
             ],
             "description": "Symfony Console Component",
             "homepage": "http://symfony.com",
-            "time": "2014-05-22 08:54:24"
+            "time": "2014-08-05 09:00:40"
         },
         {
             "name": "tedivm/jshrink",
@@ -241,16 +239,16 @@
         },
         {
             "name": "twig/twig",
-            "version": "v1.15.1",
+            "version": "v1.16.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/fabpot/Twig.git",
-                "reference": "1fb5784662f438d7d96a541e305e28b812e2eeed"
+                "reference": "8ce37115802e257a984a82d38254884085060024"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/fabpot/Twig/zipball/1fb5784662f438d7d96a541e305e28b812e2eeed",
-                "reference": "1fb5784662f438d7d96a541e305e28b812e2eeed",
+                "url": "https://api.github.com/repos/fabpot/Twig/zipball/8ce37115802e257a984a82d38254884085060024",
+                "reference": "8ce37115802e257a984a82d38254884085060024",
                 "shasum": ""
             },
             "require": {
@@ -259,7 +257,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.15-dev"
+                    "dev-master": "1.16-dev"
                 }
             },
             "autoload": {
@@ -294,22 +292,173 @@
             "keywords": [
                 "templating"
             ],
-            "time": "2014-02-13 10:19:29"
+            "time": "2014-07-05 12:19:05"
         }
     ],
     "packages-dev": [
+        {
+            "name": "facebook/xhprof",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phacility/xhprof.git",
+                "reference": "0a7a5f69f5d762c34056164eb08dd13d41156015"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phacility/xhprof/zipball/0a7a5f69f5d762c34056164eb08dd13d41156015",
+                "reference": "0a7a5f69f5d762c34056164eb08dd13d41156015",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "xhprof_lib/utils/xhprof_lib.php",
+                    "xhprof_lib/utils/xhprof_runs.php"
+                ]
+            },
+            "license": [
+                "Apache-2.0"
+            ],
+            "description": "XHProf: A Hierarchical Profiler for PHP",
+            "homepage": "http://pecl.php.net/package/xhprof",
+            "keywords": [
+                "performance",
+                "profiling"
+            ],
+            "support": {
+                "source": "https://github.com/phacility/xhprof/tree/master"
+            },
+            "time": "2014-04-22 20:02:16"
+        },
+        {
+            "name": "ocramius/instantiator",
+            "version": "1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Ocramius/Instantiator.git",
+                "reference": "a7abbb5fc9df6e7126af741dd6c140d1a7369435"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Ocramius/Instantiator/zipball/a7abbb5fc9df6e7126af741dd6c140d1a7369435",
+                "reference": "a7abbb5fc9df6e7126af741dd6c140d1a7369435",
+                "shasum": ""
+            },
+            "require": {
+                "ocramius/lazy-map": "1.0.*",
+                "php": "~5.3"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "2.0.*@ALPHA"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Instantiator\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/Ocramius/Instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2014-08-14 15:10:55"
+        },
+        {
+            "name": "ocramius/lazy-map",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Ocramius/LazyMap.git",
+                "reference": "7fe3d347f5e618bcea7d39345ff83f3651d8b752"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Ocramius/LazyMap/zipball/7fe3d347f5e618bcea7d39345ff83f3651d8b752",
+                "reference": "7fe3d347f5e618bcea7d39345ff83f3651d8b752",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.6",
+                "phpmd/phpmd": "1.5.*",
+                "phpunit/phpunit": ">=3.7",
+                "satooshi/php-coveralls": "~0.6",
+                "squizlabs/php_codesniffer": "1.4.*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "LazyMap\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A library that provides lazy instantiation logic for a map of objects",
+            "homepage": "https://github.com/Ocramius/LazyMap",
+            "keywords": [
+                "lazy",
+                "lazy instantiation",
+                "lazy loading",
+                "map",
+                "service location"
+            ],
+            "time": "2013-11-09 22:30:54"
+        },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "2.0.9",
+            "version": "2.0.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "ed8ac99ce38c3fd134128c898f7ca74665abef7f"
+                "reference": "6d196af48e8c100a3ae881940123e693da5a9217"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ed8ac99ce38c3fd134128c898f7ca74665abef7f",
-                "reference": "ed8ac99ce38c3fd134128c898f7ca74665abef7f",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6d196af48e8c100a3ae881940123e693da5a9217",
+                "reference": "6d196af48e8c100a3ae881940123e693da5a9217",
                 "shasum": ""
             },
             "require": {
@@ -361,7 +510,7 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2014-06-29 08:14:40"
+            "time": "2014-08-06 06:39:42"
         },
         {
             "name": "phpunit/php-file-iterator",
@@ -548,16 +697,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "4.1.3",
+            "version": "4.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "939cb801b3b2aa253aedd0b279f40bb8f35cec91"
+                "reference": "a33fa68ece9f8c68589bfc2da8d2794e27b820bc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/939cb801b3b2aa253aedd0b279f40bb8f35cec91",
-                "reference": "939cb801b3b2aa253aedd0b279f40bb8f35cec91",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a33fa68ece9f8c68589bfc2da8d2794e27b820bc",
+                "reference": "a33fa68ece9f8c68589bfc2da8d2794e27b820bc",
                 "shasum": ""
             },
             "require": {
@@ -571,7 +720,7 @@
                 "phpunit/php-file-iterator": "~1.3.1",
                 "phpunit/php-text-template": "~1.2",
                 "phpunit/php-timer": "~1.0.2",
-                "phpunit/phpunit-mock-objects": "~2.1",
+                "phpunit/phpunit-mock-objects": "~2.2",
                 "sebastian/comparator": "~1.0",
                 "sebastian/diff": "~1.1",
                 "sebastian/environment": "~1.0",
@@ -588,7 +737,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "4.1.x-dev"
+                    "dev-master": "4.2.x-dev"
                 }
             },
             "autoload": {
@@ -618,28 +767,29 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2014-06-11 14:15:47"
+            "time": "2014-08-18 05:12:30"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
-            "version": "2.1.5",
+            "version": "2.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "7878b9c41edb3afab92b85edf5f0981014a2713a"
+                "reference": "42e589e08bc86e3e9bdf20d385e948347788505b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/7878b9c41edb3afab92b85edf5f0981014a2713a",
-                "reference": "7878b9c41edb3afab92b85edf5f0981014a2713a",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/42e589e08bc86e3e9bdf20d385e948347788505b",
+                "reference": "42e589e08bc86e3e9bdf20d385e948347788505b",
                 "shasum": ""
             },
             "require": {
+                "ocramius/instantiator": "~1.0",
                 "php": ">=5.3.3",
                 "phpunit/php-text-template": "~1.2"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.1"
+                "phpunit/phpunit": "4.2.*@dev"
             },
             "suggest": {
                 "ext-soap": "*"
@@ -647,7 +797,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.1.x-dev"
+                    "dev-master": "2.2.x-dev"
                 }
             },
             "autoload": {
@@ -675,7 +825,7 @@
                 "mock",
                 "xunit"
             ],
-            "time": "2014-06-12 07:22:15"
+            "time": "2014-08-02 13:50:58"
         },
         {
             "name": "sebastian/comparator",
@@ -946,17 +1096,17 @@
         },
         {
             "name": "symfony/yaml",
-            "version": "v2.5.0",
+            "version": "v2.5.3",
             "target-dir": "Symfony/Component/Yaml",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/Yaml.git",
-                "reference": "b4b09c68ec2f2727574544ef0173684281a5033c"
+                "reference": "5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Yaml/zipball/b4b09c68ec2f2727574544ef0173684281a5033c",
-                "reference": "b4b09c68ec2f2727574544ef0173684281a5033c",
+                "url": "https://api.github.com/repos/symfony/Yaml/zipball/5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f",
+                "reference": "5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f",
                 "shasum": ""
             },
             "require": {
@@ -978,29 +1128,28 @@
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com",
-                    "homepage": "http://fabien.potencier.org",
-                    "role": "Lead Developer"
-                },
                 {
                     "name": "Symfony Community",
                     "homepage": "http://symfony.com/contributors"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
                 }
             ],
             "description": "Symfony Yaml Component",
             "homepage": "http://symfony.com",
-            "time": "2014-05-16 14:25:18"
+            "time": "2014-08-05 09:00:40"
         }
     ],
     "aliases": [
 
     ],
     "minimum-stability": "stable",
-    "stability-flags": [
-
-    ],
+    "stability-flags": {
+        "facebook/xhprof": 20
+    },
+    "prefer-stable": false,
     "platform": {
         "php": ">=5.3.2"
     },
diff --git a/console b/console
index bb901dfed7867e05026a59953d0e4464b9c9fffc..41b4ec5835a6ed628f42086bca7d90afc3278b7d 100755
--- a/console
+++ b/console
@@ -17,7 +17,6 @@ require_once PIWIK_INCLUDE_PATH . '/core/testMinimumPhpVersion.php';
 require_once file_exists(PIWIK_INCLUDE_PATH . '/vendor/autoload.php')
     ? PIWIK_INCLUDE_PATH . '/vendor/autoload.php' // Piwik is the main project
     : PIWIK_INCLUDE_PATH . '/../../autoload.php'; // Piwik is installed as a dependency
-require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
 require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/upgrade.php';
 
 Piwik\Translate::loadEnglishTranslation();
diff --git a/core/Archive.php b/core/Archive.php
index 63f0a61dc87799ade938d2d62b2ebfeaab41b5a4..65b5b915324871f3c8e212c261e798f38645bdff 100644
--- a/core/Archive.php
+++ b/core/Archive.php
@@ -11,7 +11,7 @@ namespace Piwik;
 use Piwik\Archive\Parameters;
 use Piwik\ArchiveProcessor\Rules;
 use Piwik\DataAccess\ArchiveSelector;
-use Piwik\Period\Factory;
+use Piwik\Period\Factory as PeriodFactory;
 
 /**
  * The **Archive** class is used to query cached analytics statistics
@@ -200,10 +200,10 @@ class Archive
         $timezone = count($websiteIds) == 1 ? Site::getTimezoneFor($websiteIds[0]) : false;
 
         if (Period::isMultiplePeriod($strDate, $period)) {
-            $oPeriod = Factory::build($period, $strDate, $timezone);
+            $oPeriod = PeriodFactory::build($period, $strDate, $timezone);
             $allPeriods = $oPeriod->getSubperiods();
         } else {
-            $oPeriod = Factory::makePeriodFromQueryParams($timezone, $period, $strDate);
+            $oPeriod = PeriodFactory::makePeriodFromQueryParams($timezone, $period, $strDate);
             $allPeriods = array($oPeriod);
         }
         $segment = new Segment($segment, $websiteIds);
diff --git a/core/Common.php b/core/Common.php
index 3b49dc9bd9963e32af49077f52ee4238668cd7f2..ecfefabcec0e4d2cf3f9438192e2d6a0c5ca5e04 100644
--- a/core/Common.php
+++ b/core/Common.php
@@ -857,7 +857,7 @@ class Common
             }
         }
 
-        if (is_null($browserLang)) {
+        if (empty($browserLang)) {
             // a fallback might be to infer the language in HTTP_USER_AGENT (i.e., localized build)
             $browserLang = "";
         } else {
diff --git a/core/CronArchive.php b/core/CronArchive.php
index b6a3dffdb0209425e880ecb097d0de42a6ce56bf..97a7f430108ed265cf74d8e99d5228d90b4e2f65 100644
--- a/core/CronArchive.php
+++ b/core/CronArchive.php
@@ -12,7 +12,7 @@ use Exception;
 use Piwik\ArchiveProcessor\Rules;
 use Piwik\CronArchive\FixedSiteIds;
 use Piwik\CronArchive\SharedSiteIds;
-use Piwik\Period\Factory;
+use Piwik\Period\Factory as PeriodFactory;
 use Piwik\Plugins\CoreAdminHome\API as APICoreAdminHome;
 use Piwik\Plugins\SitesManager\API as APISitesManager;
 
@@ -1288,7 +1288,7 @@ class CronArchive
     private function getPeriodsToProcess()
     {
         $this->restrictToPeriods = array_intersect($this->restrictToPeriods, $this->getDefaultPeriodsToProcess());
-        $this->restrictToPeriods = array_intersect($this->restrictToPeriods, Factory::getPeriodsEnabledForAPI());
+        $this->restrictToPeriods = array_intersect($this->restrictToPeriods, PeriodFactory::getPeriodsEnabledForAPI());
         return $this->restrictToPeriods;
     }
 
diff --git a/core/DataTable/Renderer.php b/core/DataTable/Renderer.php
index ddcde91cf39611cce27b675a4735ba327a2d19fa..aea71e4da863ae42bd4f49912db0911cb9e8d8c9 100644
--- a/core/DataTable/Renderer.php
+++ b/core/DataTable/Renderer.php
@@ -10,9 +10,9 @@ namespace Piwik\DataTable;
 
 use Exception;
 use Piwik\DataTable;
-use Piwik\Loader;
 use Piwik\Metrics;
 use Piwik\Piwik;
+use Piwik\Factory;
 
 /**
  * A DataTable Renderer can produce an output given a DataTable object.
@@ -22,7 +22,7 @@ use Piwik\Piwik;
  *  $render->setTable($dataTable);
  *  echo $render;
  */
-abstract class Renderer
+abstract class Renderer extends Factory
 {
     protected $table;
 
@@ -156,25 +156,17 @@ abstract class Renderer
         return self::$availableRenderers;
     }
 
-    /**
-     * Returns the DataTable associated to the output format $name
-     *
-     * @param string $name
-     * @throws Exception If the renderer is unknown
-     * @return \Piwik\DataTable\Renderer
-     */
-    public static function factory($name)
+    protected static function getClassNameFromClassId($id)
     {
-        $className = ucfirst(strtolower($name));
+        $className = ucfirst(strtolower($id));
         $className = 'Piwik\DataTable\Renderer\\' . $className;
-        try {
-            Loader::loadClass($className);
-            return new $className;
-        } catch (Exception $e) {
-            $availableRenderers = implode(', ', self::getRenderers());
-            @header('Content-Type: text/plain; charset=utf-8');
-            throw new Exception(Piwik::translate('General_ExceptionInvalidRendererFormat', array($className, $availableRenderers)));
-        }
+        return $className;
+    }
+
+    protected static function getInvalidClassIdExceptionMessage($id)
+    {
+        $availableRenderers = implode(', ', self::getRenderers());
+        return Piwik::translate('General_ExceptionInvalidRendererFormat', array(self::getClassNameFromClassId($id), $availableRenderers));
     }
 
     /**
diff --git a/core/Db/Adapter.php b/core/Db/Adapter.php
index 6bafa3a653f779946dc9d0c0b764bc3b08f4db9f..342a320d723c38d58b29d7940521d9e92e7af7a2 100644
--- a/core/Db/Adapter.php
+++ b/core/Db/Adapter.php
@@ -8,7 +8,6 @@
  */
 namespace Piwik\Db;
 
-use Piwik\Loader;
 use Zend_Db_Table;
 
 /**
@@ -39,8 +38,6 @@ class Adapter
         }
 
         $className = self::getAdapterClassName($adapterName);
-        Loader::loadClass($className);
-
         $adapter = new $className($dbInfos);
 
         if ($connect) {
diff --git a/core/Factory.php b/core/Factory.php
new file mode 100644
index 0000000000000000000000000000000000000000..480f9313c17c80ae035044eb1eb9f7f45ba449f1
--- /dev/null
+++ b/core/Factory.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik;
+
+use Exception;
+
+/**
+ * Base class for all factory types.
+ * 
+ * Factory types are base classes that contain a **factory** method. This method is used to instantiate
+ * concrete instances by a specified string ID. Fatal errors do not occur if a class does not exist.
+ * Instead an exception is thrown.
+ *
+ * Derived classes should override the **getClassNameFromClassId** and **getInvalidClassIdExceptionMessage**
+ * static methods. 
+ */
+abstract class Factory
+{
+    /**
+     * Creates a new instance of a class using a string ID.
+     *
+     * @param string $classId The ID of the class.
+     * @return Factory
+     * @throws Exception if $classId is invalid.
+     */
+    public static function factory($classId)
+    {
+        $className = static::getClassNameFromClassId($classId);
+
+        if (!class_exists($className)) {
+            @header('Content-Type: text/plain; charset=utf-8');
+            throw new Exception(static::getInvalidClassIdExceptionMessage($classId));
+        }
+
+        return new $className;
+    }
+
+    /**
+     * Should return a class name based on the class's associated string ID.
+     */
+    protected static function getClassNameFromClassId($id)
+    {
+        return $id;
+    }
+
+    /**
+     * Should return a message to use in an Exception when an invalid class ID is supplied to
+     * {@link factory()}.
+     */
+    protected static function getInvalidClassIdExceptionMessage($id)
+    {
+        return "Invalid class ID '$id' for " . get_called_class() . "::factory().";
+    }
+}
\ No newline at end of file
diff --git a/core/FrontController.php b/core/FrontController.php
index dc10f72e94e186d8905de9b5b6273ad21b5c0d57..db6f1772dc13385d38c51b33f75eea1aac21138c 100644
--- a/core/FrontController.php
+++ b/core/FrontController.php
@@ -126,7 +126,7 @@ class FrontController extends Singleton
             }
 
             if ($action === false) {
-                $this->triggerControllerActionNotFoundError($controller, $controllerAction);
+                $this->triggerControllerActionNotFoundError($module, $controllerAction);
             }
 
         }
diff --git a/core/Loader.php b/core/Loader.php
deleted file mode 100644
index 6e33987f1932488c020d1bfdc4a059d2ace63546..0000000000000000000000000000000000000000
--- a/core/Loader.php
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-/**
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
- */
-
-namespace Piwik;
-
-use Exception;
-
-/**
- * Piwik auto loader
- *
- */
-class Loader
-{
-    // our class search path; current directory is intentionally excluded
-    protected static $dirs = array('/core/', '/plugins/');
-
-    /**
-     * Get class file name
-     *
-     * @param string $class Class name
-     * @return string Class file name
-     * @throws Exception if class name is invalid
-     */
-    protected static function getClassFileName($class)
-    {
-        if (!preg_match('/^[A-Za-z0-9_\\\\]+$/D', $class)) {
-            throw new Exception("Invalid class name \"$class\".");
-        }
-
-        // prefixed class
-        $class = str_replace('_', '/', $class);
-
-        // namespace \Piwik\Common
-        $class = str_replace('\\', '/', $class);
-
-        if ($class == 'Piwik') {
-            return $class;
-        }
-
-        $class = self::removeFirstMatchingPrefix($class, array('/Piwik/', 'Piwik/'));
-        $class = self::removeFirstMatchingPrefix($class, array('/Plugins/', 'Plugins/'));
-
-        return $class;
-    }
-
-    protected static function removeFirstMatchingPrefix($class, $vendorPrefixesToRemove)
-    {
-        foreach ($vendorPrefixesToRemove as $prefix) {
-            if (strpos($class, $prefix) === 0) {
-                return substr($class, strlen($prefix));
-            }
-        }
-
-        return $class;
-    }
-
-    private static function isPluginClass($class)
-    {
-        return 0 === strpos($class, 'Piwik\Plugins') || 0 === strpos($class, '\Piwik\Plugins');
-    }
-
-    private static function usesPiwikNamespace($class)
-    {
-        return 0 === strpos($class, 'Piwik\\') || 0 === strpos($class, '\Piwik\\');
-    }
-
-    /**
-     * Load class by name
-     *
-     * @param string $class Class name
-     * @throws Exception if class not found
-     */
-    public static function loadClass($class)
-    {
-        $classPath = self::getClassFileName($class);
-
-        if (static::isPluginClass($class)) {
-            static::tryToLoadClass($class, '/plugins/', $classPath);
-        } elseif (static::usesPiwikNamespace($class)) {
-            static::tryToLoadClass($class, '/core/', $classPath);
-        } else {
-            // non-Piwik classes (e.g., Zend Framework) are in libs/
-            static::tryToLoadClass($class, '/libs/', $classPath);
-        }
-    }
-
-    private static function tryToLoadClass($class, $dir, $classPath)
-    {
-        $path = PIWIK_INCLUDE_PATH . $dir . $classPath . '.php';
-
-        if (file_exists($path)) {
-            require_once $path; // prefixed by PIWIK_INCLUDE_PATH
-
-            return class_exists($class, false) || interface_exists($class, false);
-        }
-
-        return false;
-    }
-
-    /**
-     * Autoloader
-     *
-     * @param string $class Class name
-     */
-    public static function autoload($class)
-    {
-        try {
-            self::loadClass($class);
-        } catch (Exception $e) {
-        }
-    }
-}
-
-// use the SPL autoload stack
-spl_autoload_register(array('Piwik\Loader', 'autoload'));
-
-// preserve any existing __autoload
-if (function_exists('__autoload')) {
-    spl_autoload_register('__autoload');
-}
diff --git a/core/Period.php b/core/Period.php
index 868ead7f13838a9a4c41b446eb3db01a26af0761..6898020ce704ca4447a79d9dc01105e41f67aba8 100644
--- a/core/Period.php
+++ b/core/Period.php
@@ -8,7 +8,7 @@
  */
 namespace Piwik;
 
-use Piwik\Period\Factory;
+use Piwik\Period\Factory as PeriodFactory;
 use Piwik\Period\Range;
 
 /**
@@ -64,7 +64,7 @@ abstract class Period
      */
     public static function factory($period, $date)
     {
-        return Factory::build($period, $date);
+        return PeriodFactory::build($period, $date);
     }
 
     /**
diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php
index c7d0164d5a2ee842ec1c58659a535b03e34fe5fa..114d79a1d60e8462b396faaf53a72296ba44cc0c 100644
--- a/core/Plugin/Manager.php
+++ b/core/Plugin/Manager.php
@@ -22,6 +22,7 @@ use Piwik\Theme;
 use Piwik\Tracker;
 use Piwik\Translate;
 use Piwik\Updater;
+use Piwik\SettingsServer;
 use Piwik\Plugin\Dimension\ActionDimension;
 use Piwik\Plugin\Dimension\ConversionDimension;
 use Piwik\Plugin\Dimension\VisitDimension;
@@ -957,7 +958,7 @@ class Manager extends Singleton
     private function loadTranslation($plugin, $langCode)
     {
         // we are in Tracker mode if Loader is not (yet) loaded
-        if (!class_exists('Piwik\\Loader', false)) {
+        if (SettingsServer::isTrackerApiRequest()) {
             return false;
         }
 
diff --git a/core/ReportRenderer.php b/core/ReportRenderer.php
index 44fc77f6326bc8afb971e8755a8902e579465705..e6ccc530ed818679f956d8eda91ddccbed0caa17 100644
--- a/core/ReportRenderer.php
+++ b/core/ReportRenderer.php
@@ -14,12 +14,13 @@ use Piwik\DataTable\Row;
 use Piwik\DataTable\Simple;
 use Piwik\DataTable;
 use Piwik\Plugins\ImageGraph\API;
+use Piwik\Factory;
 
 /**
  * A Report Renderer produces user friendly renderings of any given Piwik report.
  * All new Renderers must be copied in ReportRenderer and added to the $availableReportRenderers.
  */
-abstract class ReportRenderer
+abstract class ReportRenderer extends Factory
 {
     const DEFAULT_REPORT_FONT = 'dejavusans';
     const REPORT_TEXT_COLOR = "68,68,68";
@@ -39,32 +40,22 @@ abstract class ReportRenderer
         self::CSV_FORMAT,
     );
 
-    /**
-     * Return the ReportRenderer associated to the renderer type $rendererType
-     *
-     * @throws exception If the renderer is unknown
-     * @param string $rendererType
-     * @return \Piwik\ReportRenderer
-     */
-    public static function factory($rendererType)
+    protected static function getClassNameFromClassId($rendererType)
     {
-        $name = ucfirst(strtolower($rendererType));
-        $className = 'Piwik\ReportRenderer\\' . $name;
-
-        try {
-            Loader::loadClass($className);
-            return new $className;
-        } catch (Exception $e) {
+        return 'Piwik\ReportRenderer\\' . self::normalizeRendererType($rendererType);
+    }
 
-            @header('Content-Type: text/html; charset=utf-8');
+    protected static function getInvalidClassIdExceptionMessage($rendererType)
+    {
+        return Piwik::translate(
+            'General_ExceptionInvalidReportRendererFormat',
+            array(self::normalizeRendererType($rendererType), implode(', ', self::$availableReportRenderers))
+        );
+    }
 
-            throw new Exception(
-                Piwik::translate(
-                    'General_ExceptionInvalidReportRendererFormat',
-                    array($name, implode(', ', self::$availableReportRenderers))
-                )
-            );
-        }
+    protected static function normalizeRendererType($rendererType)
+    {
+        return ucfirst(strtolower($rendererType));
     }
 
     /**
diff --git a/core/Tracker.php b/core/Tracker.php
index a6ab0225ac02ec71df9852233945af39f789df38..d0502e3ca3e4b4a3bf03bd6d2509059433008924 100644
--- a/core/Tracker.php
+++ b/core/Tracker.php
@@ -407,7 +407,6 @@ class Tracker
             && self::$initTrackerMode === false
         ) {
             self::$initTrackerMode = true;
-            require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
             require_once PIWIK_INCLUDE_PATH . '/core/Option.php';
 
             Access::getInstance();
diff --git a/core/Tracker/GoalManager.php b/core/Tracker/GoalManager.php
index fbc73381aa2ca28286237ad80615bacf037a8d9b..12e3865d2c183eb72f395cfc536a24dacd01ab21 100644
--- a/core/Tracker/GoalManager.php
+++ b/core/Tracker/GoalManager.php
@@ -138,15 +138,29 @@ class GoalManager
                 || ($attribute == 'file' && $actionType != Action::TYPE_DOWNLOAD)
                 || ($attribute == 'external_website' && $actionType != Action::TYPE_OUTLINK)
                 || ($attribute == 'manually')
+                || in_array($attribute, array('event_action', 'event_name', 'event_category')) && $actionType != Action::TYPE_EVENT
             ) {
                 continue;
             }
 
             $url = $decodedActionUrl;
-            // Matching on Page Title
-            if ($attribute == 'title') {
-                $url = $action->getActionName();
+
+            switch ($attribute) {
+                case 'title':
+                    // Matching on Page Title
+                    $url = $action->getActionName();
+                    break;
+                case 'event_action':
+                    $url = $action->getEventAction();
+                    break;
+                case 'event_name':
+                    $url = $action->getEventName();
+                    break;
+                case 'event_category':
+                    $url = $action->getEventCategory();
+                    break;
             }
+
             $pattern_type = $goal['pattern_type'];
 
             $match = $this->isUrlMatchingGoal($goal, $pattern_type, $url);
diff --git a/index.php b/index.php
index cc6780eb9bb013314d8dbc03cda2e9274c603cb8..baee05066c4f636d2f193b673eba3d781e0f53df 100644
--- a/index.php
+++ b/index.php
@@ -38,10 +38,9 @@ session_cache_limiter('nocache');
 require_once file_exists(PIWIK_INCLUDE_PATH . '/vendor/autoload.php')
     ? PIWIK_INCLUDE_PATH . '/vendor/autoload.php' // Piwik is the main project
     : PIWIK_INCLUDE_PATH . '/../../autoload.php'; // Piwik is installed as a dependency
-require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
 
 if(!defined('PIWIK_PRINT_ERROR_BACKTRACE')) {
     define('PIWIK_PRINT_ERROR_BACKTRACE', false);
 }
 
-require_once PIWIK_INCLUDE_PATH . '/core/dispatch.php';
+require_once PIWIK_INCLUDE_PATH . '/core/dispatch.php';
\ No newline at end of file
diff --git a/js/tracker.php b/js/tracker.php
index 0a842b8c0fdcfefdb752442ebdce68a8bc978ff0..a8d768e6bcd2447160c70fd172109d4de73b0de6 100644
--- a/js/tracker.php
+++ b/js/tracker.php
@@ -27,7 +27,7 @@ define('PIWIK_DOCUMENT_ROOT', '..');
 define('PIWIK_USER_PATH', '..');
 
 require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/upgrade.php';
-require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
+require_once PIWIK_INCLUDE_PATH . '/vendor/autoload.php';
 
 $file = '../piwik.js';
 
diff --git a/lang/en.json b/lang/en.json
index abd9d25e9268ca273a2c35bb95ba7e2206553abc..b7dad2a8b2eea0dc05f82830d7c6247dc94c9c8c 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -930,6 +930,7 @@
         "CaseSensitive": "Case sensitive match",
         "ChooseGoal": "Choose Goal",
         "ClickOutlink": "Click on a Link to an external website",
+        "SendEvent": "Send an event",
         "ColumnAverageOrderRevenueDocumentation": "Average Order Value (AOV) is the total revenue from all Ecommerce Orders divided by the number of orders.",
         "ColumnAveragePriceDocumentation": "The average revenue for this %s.",
         "ColumnAverageQuantityDocumentation": "The average quantity of this %s sold in Ecommerce orders.",
diff --git a/misc/log-analytics/import_logs.py b/misc/log-analytics/import_logs.py
index 2328a56ca41ddd4930367d8b08236f6002801f41..a52b581c8c7a3f3cc6a741b6ce47578db0e326e1 100755
--- a/misc/log-analytics/import_logs.py
+++ b/misc/log-analytics/import_logs.py
@@ -360,11 +360,11 @@ class Configuration(object):
         )
         option_parser.add_option(
             '--exclude-path', dest='excluded_paths', action='append', default=[],
-            help="Paths to exclude. Can be specified multiple times"
+            help="Any URL path matching this exclude-path will not be imported in Piwik. Can be specified multiple times"
         )
         option_parser.add_option(
             '--exclude-path-from', dest='exclude_path_from',
-            help="Each line from this file is a path to exclude"
+            help="Each line from this file is a path to exclude (see: --exclude-path)"
         )
         option_parser.add_option(
             '--include-path', dest='included_paths', action='append', default=[],
diff --git a/misc/others/cli-script-bootstrap.php b/misc/others/cli-script-bootstrap.php
index a5c200f30cdce2c59df60a560ac9ad5e28a83a9b..991b531a943789fb1f5a53b4da390c7712a28758 100644
--- a/misc/others/cli-script-bootstrap.php
+++ b/misc/others/cli-script-bootstrap.php
@@ -28,7 +28,6 @@ set_time_limit(0);
 
 require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/upgrade.php';
 require_once PIWIK_INCLUDE_PATH . '/core/testMinimumPhpVersion.php';
-require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
 
 $GLOBALS['PIWIK_TRACKER_DEBUG'] = false;
 define('PIWIK_ENABLE_DISPATCH', false);
diff --git a/piwik.php b/piwik.php
index d992ef56a51f892415c197603fbc4dfa482b4f55..f5278f5d47ac1d97081368dbceeff1103d168924 100644
--- a/piwik.php
+++ b/piwik.php
@@ -83,7 +83,6 @@ require_once PIWIK_INCLUDE_PATH . '/core/Tracker/VisitorNotFoundInDb.php';
 require_once PIWIK_INCLUDE_PATH . '/core/CacheFile.php';
 require_once PIWIK_INCLUDE_PATH . '/core/Filesystem.php';
 require_once PIWIK_INCLUDE_PATH . '/core/Cookie.php';
-require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
 
 session_cache_limiter('nocache');
 @date_default_timezone_set('UTC');
@@ -96,8 +95,6 @@ if (!defined('PIWIK_ENABLE_TRACKING') || PIWIK_ENABLE_TRACKING) {
 
 $GLOBALS['PIWIK_TRACKER_DEBUG'] = (bool) \Piwik\Config::getInstance()->Tracker['debug'];
 if ($GLOBALS['PIWIK_TRACKER_DEBUG'] === true) {
-    require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
-
     require_once PIWIK_INCLUDE_PATH . '/core/Error.php';
     \Piwik\Error::setErrorHandler();
     require_once PIWIK_INCLUDE_PATH . '/core/ExceptionHandler.php';
diff --git a/plugins/CoreConsole/templates/travis.yml.twig b/plugins/CoreConsole/templates/travis.yml.twig
index 553f5ae7b44021f48eef711f150debee48d3d5d9..935eb2cd2f1b3311a5eea8540eca408f443623c8 100644
--- a/plugins/CoreConsole/templates/travis.yml.twig
+++ b/plugins/CoreConsole/templates/travis.yml.twig
@@ -107,7 +107,7 @@ before_script:
   - mysql -e "SELECT VERSION();"
 
   # configure mysql
-  - mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION'" # Travis default
+  - mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES'" # Travis default
 
   # Uncomment to enable sql_mode STRICT_TRANS_TABLES (new default in Mysql 5.6)
   #- mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES'"
@@ -171,4 +171,4 @@ after_success:
 
 
 {{ extraSections|trim|raw }}
-{%- endif -%}
\ No newline at end of file
+{%- endif -%}
diff --git a/plugins/CustomAlerts b/plugins/CustomAlerts
index 825e948c4e3a7b5f8800639d3617998aea88e1e1..c467c059942e0c4331e79d547c4e3acb4bbd0fa1 160000
--- a/plugins/CustomAlerts
+++ b/plugins/CustomAlerts
@@ -1 +1 @@
-Subproject commit 825e948c4e3a7b5f8800639d3617998aea88e1e1
+Subproject commit c467c059942e0c4331e79d547c4e3acb4bbd0fa1
diff --git a/plugins/Events/Actions/ActionEvent.php b/plugins/Events/Actions/ActionEvent.php
index 3791de794d418e336af58acbbfb9042329beddd2..9921c8d39bd6a2628f8744abf2787558139764c0 100644
--- a/plugins/Events/Actions/ActionEvent.php
+++ b/plugins/Events/Actions/ActionEvent.php
@@ -38,6 +38,21 @@ class ActionEvent extends Action
         return (strlen($eventCategory) > 0 && strlen($eventAction) > 0);
     }
 
+    public function getEventAction()
+    {
+        return $this->request->getParam('e_a');
+    }
+
+    public function getEventCategory()
+    {
+        return $this->request->getParam('e_c');
+    }
+
+    public function getEventName()
+    {
+        return $this->request->getParam('e_n');
+    }
+
     public function getCustomFloatValue()
     {
         return $this->eventValue;
diff --git a/plugins/Events/Events.php b/plugins/Events/Events.php
index 088eb213a1310f52c4defae7c8a6f10addbe7af8..b3a413ad5ff54ddd14e90d13d6abb033c07916f5 100644
--- a/plugins/Events/Events.php
+++ b/plugins/Events/Events.php
@@ -10,6 +10,7 @@ namespace Piwik\Plugins\Events;
 
 use Piwik\Common;
 use Piwik\Piwik;
+use Piwik\Plugin\ViewDataTable;
 
 class Events extends \Piwik\Plugin
 {
@@ -20,7 +21,8 @@ class Events extends \Piwik\Plugin
     {
         return array(
             'Metrics.getDefaultMetricTranslations' => 'addMetricTranslations',
-            'Metrics.getDefaultMetricDocumentationTranslations' => 'addMetricDocumentationTranslations'
+            'Metrics.getDefaultMetricDocumentationTranslations' => 'addMetricDocumentationTranslations',
+            'ViewDataTable.configure'               => 'configureViewDataTable',
         );
     }
 
@@ -142,6 +144,94 @@ class Events extends \Piwik\Plugin
         throw new \Exception("Translation not found for report $apiMethod");
     }
 
+    public function configureViewDataTable(ViewDataTable $view)
+    {
+        if ($view->requestConfig->getApiModuleToRequest() != 'Events') {
+            return;
+        }
+
+        // eg. 'Events.getCategory'
+        $apiMethod = $view->requestConfig->getApiMethodToRequest();
+
+        $secondaryDimension = $this->getSecondaryDimensionFromRequest();
+        $view->config->subtable_controller_action = API::getInstance()->getActionToLoadSubtables($apiMethod, $secondaryDimension);
+        $view->config->columns_to_display = array('label', 'nb_events', 'sum_event_value');
+        $view->config->show_flatten_table = true;
+        $view->config->show_table_all_columns = false;
+        $view->requestConfig->filter_sort_column = 'nb_events';
+
+        $labelTranslation = $this->getColumnTranslation($apiMethod);
+        $view->config->addTranslation('label', $labelTranslation);
+        $view->config->addTranslations($this->getMetricTranslations());
+        $this->addRelatedReports($view, $secondaryDimension);
+        $this->addTooltipEventValue($view);
+    }
+
+    private function addRelatedReports($view, $secondaryDimension)
+    {
+        if(empty($secondaryDimension)) {
+            // eg. Row Evolution
+            return;
+        }
+
+        $view->config->show_related_reports = true;
+
+        $apiMethod = $view->requestConfig->getApiMethodToRequest();
+        $secondaryDimensions = API::getInstance()->getSecondaryDimensions($apiMethod);
+
+        if(empty($secondaryDimensions)) {
+            return;
+        }
+
+        $secondaryDimensionTranslation = $this->getDimensionLabel($secondaryDimension);
+        $view->config->related_reports_title =
+            Piwik::translate('Events_SecondaryDimension', $secondaryDimensionTranslation)
+            . "<br/>"
+            . Piwik::translate('Events_SwitchToSecondaryDimension', '');
+
+        foreach($secondaryDimensions as $dimension) {
+            if($dimension == $secondaryDimension) {
+                // don't show as related report the currently selected dimension
+                continue;
+            }
+
+            $dimensionTranslation = $this->getDimensionLabel($dimension);
+            $view->config->addRelatedReport(
+                $view->requestConfig->apiMethodToRequestDataTable,
+                $dimensionTranslation,
+                array('secondaryDimension' => $dimension)
+            );
+        }
+
+    }
+
+    private function addTooltipEventValue($view)
+    {
+        // Creates the tooltip message for Event Value column
+        $tooltipCallback = function ($hits, $min, $max, $avg) {
+            if (!$hits) {
+                return false;
+            }
+            $msgEventMinMax = Piwik::translate("Events_EventValueTooltip", array($hits, "<br />", $min, $max));
+            $msgEventAvg = Piwik::translate("Events_AvgEventValue", $avg);
+            return $msgEventMinMax . "<br/>" . $msgEventAvg;
+        };
+
+        // Add tooltip metadata column to the DataTable
+        $view->config->filters[] = array('ColumnCallbackAddMetadata',
+            array(
+                array(
+                    'nb_events',
+                    'min_event_value',
+                    'max_event_value',
+                    'avg_event_value'
+                ),
+                'sum_event_value_tooltip',
+                $tooltipCallback
+            )
+        );
+    }
+
     /**
      * @return mixed
      */
diff --git a/plugins/Events/Reports/Base.php b/plugins/Events/Reports/Base.php
index 20441109ca204066f47d28740564b2bbfbc9c83d..c452120dc67e27178befc08afc174abeebadc001 100644
--- a/plugins/Events/Reports/Base.php
+++ b/plugins/Events/Reports/Base.php
@@ -25,93 +25,4 @@ abstract class Base extends \Piwik\Plugin\Report
         );
     }
 
-    public function configureView(ViewDataTable $view)
-    {
-        if ($view->requestConfig->getApiModuleToRequest() != 'Events') {
-            return;
-        }
-
-        // eg. 'Events.getCategory'
-        $apiMethod = $view->requestConfig->getApiMethodToRequest();
-
-        $events = new Events();
-        $secondaryDimension = $events->getSecondaryDimensionFromRequest();
-        $view->config->subtable_controller_action = API::getInstance()->getActionToLoadSubtables($apiMethod, $secondaryDimension);
-        $view->config->columns_to_display = array('label', 'nb_events', 'sum_event_value');
-        $view->config->show_flatten_table = true;
-        $view->config->show_table_all_columns = false;
-        $view->requestConfig->filter_sort_column = 'nb_events';
-
-        $labelTranslation = $events->getColumnTranslation($apiMethod);
-        $view->config->addTranslation('label', $labelTranslation);
-        $view->config->addTranslations($events->getMetricTranslations());
-        $this->addRelatedReports($view, $secondaryDimension);
-        $this->addTooltipEventValue($view);
-    }
-
-    private function addRelatedReports($view, $secondaryDimension)
-    {
-        if(empty($secondaryDimension)) {
-            // eg. Row Evolution
-            return;
-        }
-
-        $events = new Events();
-        $view->config->show_related_reports = true;
-
-        $apiMethod = $view->requestConfig->getApiMethodToRequest();
-        $secondaryDimensions = API::getInstance()->getSecondaryDimensions($apiMethod);
-
-        if(empty($secondaryDimensions)) {
-            return;
-        }
-
-        $secondaryDimensionTranslation = $events->getDimensionLabel($secondaryDimension);
-        $view->config->related_reports_title =
-            Piwik::translate('Events_SecondaryDimension', $secondaryDimensionTranslation)
-            . "<br/>"
-            . Piwik::translate('Events_SwitchToSecondaryDimension', '');
-
-        foreach($secondaryDimensions as $dimension) {
-            if($dimension == $secondaryDimension) {
-                // don't show as related report the currently selected dimension
-                continue;
-            }
-
-            $dimensionTranslation = $events->getDimensionLabel($dimension);
-            $view->config->addRelatedReport(
-                $view->requestConfig->apiMethodToRequestDataTable,
-                $dimensionTranslation,
-                array('secondaryDimension' => $dimension)
-            );
-        }
-
-    }
-
-    private function addTooltipEventValue($view)
-    {
-        // Creates the tooltip message for Event Value column
-        $tooltipCallback = function ($hits, $min, $max, $avg) {
-            if (!$hits) {
-                return false;
-            }
-            $msgEventMinMax = Piwik::translate("Events_EventValueTooltip", array($hits, "<br />", $min, $max));
-            $msgEventAvg = Piwik::translate("Events_AvgEventValue", $avg);
-            return $msgEventMinMax . "<br/>" . $msgEventAvg;
-        };
-
-        // Add tooltip metadata column to the DataTable
-        $view->config->filters[] = array('ColumnCallbackAddMetadata',
-            array(
-                array(
-                    'nb_events',
-                    'min_event_value',
-                    'max_event_value',
-                    'avg_event_value'
-                ),
-                'sum_event_value_tooltip',
-                $tooltipCallback
-            )
-        );
-    }
 }
diff --git a/plugins/Goals/API.php b/plugins/Goals/API.php
index 43fff0bfdb9007f86cd5b95fea07e278127469a4..176cafad24d3400c8de0832a16d20e177da6e477 100644
--- a/plugins/Goals/API.php
+++ b/plugins/Goals/API.php
@@ -79,7 +79,7 @@ class API extends \Piwik\Plugin\API
      *
      * @param int $idSite
      * @param string $name
-     * @param string $matchAttribute 'url', 'title', 'file', 'external_website' or 'manually'
+     * @param string $matchAttribute 'url', 'title', 'file', 'external_website', 'manually', 'event_action', 'event_category' or 'event_name'
      * @param string $pattern eg. purchase-confirmation.htm
      * @param string $patternType 'regex', 'contains', 'exact'
      * @param bool $caseSensitive
@@ -91,7 +91,7 @@ class API extends \Piwik\Plugin\API
     public function addGoal($idSite, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false)
     {
         Piwik::checkUserHasAdminAccess($idSite);
-        $this->checkPatternIsValid($patternType, $pattern);
+        $this->checkPatternIsValid($patternType, $pattern, $matchAttribute);
         $name = $this->checkName($name);
         $pattern = $this->checkPattern($pattern);
 
@@ -141,7 +141,7 @@ class API extends \Piwik\Plugin\API
         Piwik::checkUserHasAdminAccess($idSite);
         $name = $this->checkName($name);
         $pattern = $this->checkPattern($pattern);
-        $this->checkPatternIsValid($patternType, $pattern);
+        $this->checkPatternIsValid($patternType, $pattern, $matchAttribute);
         Db::get()->update(Common::prefixTable('goal'),
             array(
                  'name'            => $name,
@@ -157,10 +157,11 @@ class API extends \Piwik\Plugin\API
         Cache::regenerateCacheWebsiteAttributes($idSite);
     }
 
-    private function checkPatternIsValid($patternType, $pattern)
+    private function checkPatternIsValid($patternType, $pattern, $matchAttribute)
     {
         if ($patternType == 'exact'
             && substr($pattern, 0, 4) != 'http'
+            && substr($matchAttribute, 0, 6) != 'event_'
         ) {
             throw new Exception(Piwik::translate('Goals_ExceptionInvalidMatchingString', array("http:// or https://", "http://www.yourwebsite.com/newsletter/subscribed.html")));
         }
diff --git a/plugins/Goals/javascripts/goalsForm.js b/plugins/Goals/javascripts/goalsForm.js
index 643a9ce3af4130d551d77dd041a9a81a37e58713..400f3813c273bf7053fef1239d96ec3542349262 100644
--- a/plugins/Goals/javascripts/goalsForm.js
+++ b/plugins/Goals/javascripts/goalsForm.js
@@ -34,6 +34,25 @@ function showCancel() {
     });
 }
 
+function onMatchAttributeChange(matchAttribute)
+{
+    if ('event' === matchAttribute) {
+        $('.entityAddContainer .whereEvent').show();
+        $('.entityAddContainer .whereUrl').hide();
+    } else {
+        $('.entityAddContainer .whereEvent').hide();
+        $('.entityAddContainer .whereUrl').show();
+    }
+
+    $('#match_attribute_name').html(mappingMatchTypeName[matchAttribute]);
+    $('#examples_pattern').html(mappingMatchTypeExamples[matchAttribute]);
+}
+
+function updateMatchAttribute () {
+    var matchTypeId = $(this).val();
+    onMatchAttributeChange(matchTypeId);
+}
+
 // init the goal form with existing goal value, if any
 function initGoalForm(goalMethodAPI, submitText, goalName, matchAttribute, pattern, patternType, caseSensitive, revenue, allowMultiple, goalId) {
     $('#goal_name').val(goalName);
@@ -46,6 +65,14 @@ function initGoalForm(goalMethodAPI, submitText, goalName, matchAttribute, patte
     } else {
         $('select[name=trigger_type] option[value=visitors]').prop('selected', true);
     }
+
+    if (0 === matchAttribute.indexOf('event')) {
+        $('select[name=event_type] option[value=' + matchAttribute + ']').prop('selected', true);
+        matchAttribute = 'event';
+    }
+
+    onMatchAttributeChange(matchAttribute);
+
     $('input[name=match_attribute][value=' + matchAttribute + ']').prop('checked', true);
     $('input[name=allow_multiple][value=' + allowMultiple + ']').prop('checked', true);
     $('#match_attribute_name').html(mappingMatchTypeName[matchAttribute]);
@@ -66,6 +93,7 @@ function initGoalForm(goalMethodAPI, submitText, goalName, matchAttribute, patte
 }
 
 function bindGoalForm() {
+
     $('select[name=trigger_type]').click(function () {
         var triggerTypeId = $(this).val();
         if (triggerTypeId == "manually") {
@@ -79,10 +107,9 @@ function bindGoalForm() {
         }
     });
 
-    $('input[name=match_attribute]').click(function () {
-        var matchTypeId = $(this).val();
-        $('#match_attribute_name').html(mappingMatchTypeName[matchTypeId]);
-        $('#examples_pattern').html(mappingMatchTypeExamples[matchTypeId]);
+    $(document).bind('Goals.edit', function () {
+        $('input[name=match_attribute]').off('change', updateMatchAttribute);
+        $('input[name=match_attribute]').change(updateMatchAttribute);
     });
 
     $('#goal_submit').click(function () {
@@ -126,6 +153,11 @@ function ajaxAddGoal() {
         parameters.caseSensitive = 0;
     } else {
         parameters.matchAttribute = $('input[name=match_attribute]:checked').val();
+
+        if (parameters.matchAttribute === 'event') {
+            parameters.matchAttribute = $('select[name=event_type]').val();
+        }
+
         parameters.patternType = $('[name=pattern_type]').val();
         parameters.pattern = encodeURIComponent($('input[name=pattern]').val());
         parameters.caseSensitive = $('#case_sensitive').prop('checked') == true ? 1 : 0;
diff --git a/plugins/Goals/templates/_addEditGoal.twig b/plugins/Goals/templates/_addEditGoal.twig
index c8feb87bc50a2e5c2e66e25b03ac542d32cd94be..f9449d8ce98abaa4728baffa52b3913189f5c617 100644
--- a/plugins/Goals/templates/_addEditGoal.twig
+++ b/plugins/Goals/templates/_addEditGoal.twig
@@ -55,7 +55,8 @@
         "url": "{{ 'Goals_URL'|translate }}",
         "title": "{{ 'Goals_PageTitle'|translate }}",
         "file": "{{ 'Goals_Filename'|translate }}",
-        "external_website": "{{ 'Goals_ExternalWebsiteUrl'|translate }}"
+        "external_website": "{{ 'Goals_ExternalWebsiteUrl'|translate }}",
+        "event": "{{ 'Events_Event'|translate }}"
     };
     var mappingMatchTypeExamples = {
         "url": "{{ 'General_ForExampleShort'|translate }} {{ 'Goals_Contains'|translate("'checkout/confirmation'") }} \
@@ -67,7 +68,10 @@
 		<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_MatchesExpression'|translate("'(.*)\\\.zip'") }}",
         "external_website": "{{ 'General_ForExampleShort'|translate }} {{ 'Goals_Contains'|translate("'amazon.com'") }} \
 		<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_IsExactly'|translate("'http://mypartner.com/landing.html'") }} \
-		<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_MatchesExpression'|translate("'http://www.amazon.com\\\/(.*)\\\/yourAffiliateId'") }}"
+		<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_MatchesExpression'|translate("'http://www.amazon.com\\\/(.*)\\\/yourAffiliateId'") }}",
+        "event": "{{ 'General_ForExampleShort'|translate }} {{ 'Goals_Contains'|translate("'video'") }} \
+		<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_IsExactly'|translate("'click'") }} \
+		<br />{{ 'General_ForExampleShort'|translate }} {{ 'Goals_MatchesExpression'|translate("'(.*)_banner'") }}"
     };
     bindGoalForm();
 
diff --git a/plugins/Goals/templates/_formAddGoal.twig b/plugins/Goals/templates/_formAddGoal.twig
index 002df4954b649676f34747cc3df13963cd553b20..7bff75f8cb45e6e7e928872c94984c387a327f21 100644
--- a/plugins/Goals/templates/_formAddGoal.twig
+++ b/plugins/Goals/templates/_formAddGoal.twig
@@ -25,6 +25,9 @@
                     <input type="radio" id="match_attribute_title" value="title" name="match_attribute"/>
                     <label for="match_attribute_title">{{ 'Goals_VisitPageTitle'|translate }}</label>
                     <br/>
+                    <input type="radio" id="match_attribute_event" value="event" name="match_attribute"/>
+                    <label for="match_attribute_event">{{ 'Goals_SendEvent'|translate }}</label>
+                    <br/>
                     <input type="radio" id="match_attribute_file" value="file" name="match_attribute"/>
                     <label for="match_attribute_file">{{ 'Goals_Download'|translate }}</label>
                     <br/>
@@ -35,7 +38,11 @@
             </tbody>
             <tbody id="match_attribute_section">
             <tr>
-                <td class="first">{{ 'Goals_WhereThe'|translate }} <span id="match_attribute_name"></span></td>
+                <td class="first">{{ 'Goals_WhereThe'|translate }} <span class="whereUrl" id="match_attribute_name"></span><select name="event_type" class="whereEvent inp">
+                        <option value="event_category">{{ 'Events_EventCategory'|translate }}</option>
+                        <option value="event_action">{{ 'Events_EventAction'|translate }}</option>
+                        <option value="event_name">{{ 'Events_EventName'|translate }}</option>
+                    </select></td>
                 <td>
                     <select name="pattern_type" class="inp">
                         <option value="contains">{{ 'Goals_Contains'|translate("") }}</option>
diff --git a/plugins/Goals/templates/_listGoalEdit.twig b/plugins/Goals/templates/_listGoalEdit.twig
index 11c1172f905bc81c0be36a77ff9cc1e7b37d2b2d..5118cfb72ac29033c096b1690b0c4e137c0332d8 100644
--- a/plugins/Goals/templates/_listGoalEdit.twig
+++ b/plugins/Goals/templates/_listGoalEdit.twig
@@ -50,7 +50,10 @@
         "file": "{{ 'Goals_Download'|translate }}",
         "url": "{{ 'Goals_VisitUrl'|translate }}",
         "title": "{{ 'Goals_VisitPageTitle'|translate }}",
-        "external_website": "{{ 'Goals_ClickOutlink'|translate }}"
+        "external_website": "{{ 'Goals_ClickOutlink'|translate }}",
+        "event_action": "{{ 'Goals_SendEvent'|translate }} ({{ 'Events_EventAction'|translate }})",
+        "event_category": "{{ 'Goals_SendEvent'|translate }} ({{ 'Events_EventCategory'|translate }})",
+        "event_name": "{{ 'Goals_SendEvent'|translate }} ({{ 'Events_EventName'|translate }})"
     };
 
     $(document).ready(function () {
diff --git a/plugins/Goals/tests/APITest.php b/plugins/Goals/tests/APITest.php
new file mode 100644
index 0000000000000000000000000000000000000000..010449e41ff5c29bfb0bf92ef2afd8817842e74e
--- /dev/null
+++ b/plugins/Goals/tests/APITest.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Goals\tests;
+use Piwik\Access;
+use Piwik\Plugins\Goals\API;
+use Piwik\Tests\Fixture;
+
+/**
+ * @group Goals
+ * @group Plugins
+ * @group APITest
+ * @group Database
+ */
+class APITest extends \DatabaseTestCase
+{
+    /**
+     * @var API
+     */
+    private $api;
+
+    private $idSite = 1;
+
+    public function setUp()
+    {
+        parent::setUp();
+        $this->api = API::getInstance();
+
+        Fixture::createWebsite('2014-01-01 00:00:00');
+        Fixture::createWebsite('2014-01-01 00:00:00');
+    }
+
+    public function test_addGoal_shouldReturnGoalId_IfCreationIsSuccessful()
+    {
+        $idGoal = $this->createAnyGoal();
+
+        $this->assertSame(1, $idGoal);
+    }
+
+    public function test_addGoal_shouldSucceed_IfOnlyMinimumFieldsGiven()
+    {
+        $idGoal = $this->api->addGoal($this->idSite, 'MyName', 'url', 'http://www.test.de', 'exact');
+
+        $this->assertGoal($idGoal, 'MyName', 'url', 'http://www.test.de', 'exact', 0, 0, 0);
+    }
+
+    public function test_addGoal_ShouldSucceed_IfAllFieldsGiven()
+    {
+        $idGoal = $this->api->addGoal($this->idSite, 'MyName', 'url', 'http://www.test.de', 'exact', true, 50, true);
+
+        $this->assertGoal($idGoal, 'MyName', 'url', 'http://www.test.de', 'exact', 1, 50, 1);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Goals_ExceptionInvalidMatchingString
+     */
+    public function test_addGoal_shouldThrowException_IfPatternTypeIsExactAndMatchAttributeNotEvent()
+    {
+        $this->api->addGoal($this->idSite, 'MyName', 'url', 'www.test.de', 'exact');
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Goals_ExceptionInvalidMatchingString
+     */
+    public function test_addGoal_shouldThrowException_IfPatternTypeIsExactAndMatchAttributeNotEvent2()
+    {
+        $this->api->addGoal($this->idSite, 'MyName', 'external_website', 'www.test.de', 'exact');
+    }
+
+    public function test_addGoal_shouldNotThrowException_IfPatternTypeIsExactAndMatchAttributeIsEvent()
+    {
+        $this->api->addGoal($this->idSite, 'MyName1', 'event_action', 'test', 'exact');
+        $this->api->addGoal($this->idSite, 'MyName2', 'event_name', 'test', 'exact');
+        $idGoal = $this->api->addGoal($this->idSite, 'MyName3', 'event_category', 'test', 'exact');
+
+        $this->assertSame('3', $idGoal);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage checkUserHasAdminAccess Fake exception
+     */
+    public function test_addGoal_shouldThrowException_IfNotEnoughPermission()
+    {
+        $this->setNonAdminUser();
+        $this->createAnyGoal();
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage checkUserHasAdminAccess Fake exception
+     */
+    public function test_updateGoal_shouldThrowException_IfNotEnoughPermission()
+    {
+        $idGoal = $this->createAnyGoal();
+        $this->assertSame(1, $idGoal); // make sure goal is created and does not already fail here
+        $this->setNonAdminUser();
+        $this->api->updateGoal($this->idSite, $idGoal, 'MyName', 'url', 'www.test.de', 'exact');
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Goals_ExceptionInvalidMatchingString
+     */
+    public function test_updateGoal_shouldThrowException_IfPatternTypeIsExactAndMatchAttributeNotEvent()
+    {
+        $idGoal = $this->createAnyGoal();
+        $this->api->updateGoal($this->idSite, $idGoal, 'MyName', 'url', 'www.test.de', 'exact');
+    }
+
+    public function test_updateGoal_shouldNotThrowException_IfPatternTypeIsExactAndMatchAttributeIsEvent()
+    {
+        $idGoal = $this->createAnyGoal();
+        $this->api->updateGoal($this->idSite, $idGoal, 'MyName', 'event_action', 'www.test.de', 'exact');
+        $this->api->updateGoal($this->idSite, $idGoal, 'MyName', 'event_category', 'www.test.de', 'exact');
+        $this->api->updateGoal($this->idSite, $idGoal, 'MyName', 'event_name', 'www.test.de', 'exact');
+
+        $this->assertSame(1, $idGoal);
+    }
+
+    public function test_updateGoal_shouldUpdateAllGivenFields()
+    {
+        $idGoal = $this->createAnyGoal();
+        $this->api->updateGoal($this->idSite, $idGoal, 'UpdatedName', 'file', 'http://www.updatetest.de', 'contains', true, 999, true);
+
+        $this->assertGoal($idGoal, 'UpdatedName', 'file', 'http://www.updatetest.de', 'contains', 1, 999, 1);
+    }
+
+    public function test_updateGoal_shouldUpdateMinimalFields_ShouldLeaveOtherFieldsUntouched()
+    {
+        $idGoal = $this->createAnyGoal();
+        $this->api->updateGoal($this->idSite, $idGoal, 'UpdatedName', 'file', 'http://www.updatetest.de', 'contains');
+
+        $this->assertGoal($idGoal, 'UpdatedName', 'file', 'http://www.updatetest.de', 'contains', 0, 0, 0);
+    }
+
+    public function test_deleteGoal_shouldNotDeleteAGoal_IfGoalIdDoesNotExist()
+    {
+        $this->assertHasNoGoals();
+
+        $this->createAnyGoal();
+        $this->assertHasGoals();
+
+        $this->api->deleteGoal($this->idSite, 999);
+        $this->assertHasGoals();
+    }
+
+    public function test_deleteGoal_shouldNotDeleteAGoal_IfSiteDoesNotMatchGoalId()
+    {
+        $this->assertHasNoGoals();
+
+        $idGoal = $this->createAnyGoal();
+        $this->assertHasGoals();
+
+        $this->api->deleteGoal($idSite = 2, $idGoal);
+        $this->assertHasGoals();
+    }
+
+    public function test_deleteGoal_shouldDeleteAGoal_IfGoalAndSiteMatches()
+    {
+        $this->assertHasNoGoals();
+
+        $idGoal = $this->createAnyGoal();
+        $this->assertHasGoals();
+
+        $this->api->deleteGoal($this->idSite, $idGoal);
+        $this->assertHasNoGoals();
+    }
+
+    private function assertHasGoals()
+    {
+        $goals = $this->getGoals();
+        $this->assertNotEmpty($goals);
+    }
+
+    private function assertHasNoGoals()
+    {
+        $goals = $this->getGoals();
+        $this->assertEmpty($goals);
+    }
+
+    private function assertGoal($idGoal, $name, $url, $pattern, $patternType, $caseSenstive = 0, $revenue = 0, $allowMultiple = 0)
+    {
+        $expected = array($idGoal => array(
+            'idsite' => $this->idSite,
+            'idgoal' => $idGoal,
+            'name' => $name,
+            'match_attribute' => $url,
+            'pattern' => $pattern,
+            'pattern_type' => $patternType,
+            'case_sensitive' => $caseSenstive,
+            'allow_multiple' => $allowMultiple,
+            'revenue' => $revenue,
+            'deleted' => 0
+        ));
+
+        $goals = $this->getGoals();
+
+        $this->assertEquals($expected, $goals);
+    }
+
+    private function getGoals()
+    {
+        return $this->api->getGoals($this->idSite);
+    }
+
+    private function createAnyGoal()
+    {
+        return $this->api->addGoal($this->idSite, 'MyName1', 'event_action', 'test', 'exact');
+    }
+
+    protected function setNonAdminUser()
+    {
+        $pseudoMockAccess = new \FakeAccess;
+        \FakeAccess::setSuperUserAccess(false);
+        \FakeAccess::$idSitesView = array(99);
+        \FakeAccess::$identity = 'aUser';
+        Access::setSingletonInstance($pseudoMockAccess);
+    }
+
+}
diff --git a/plugins/ImageGraph/StaticGraph.php b/plugins/ImageGraph/StaticGraph.php
index 0ece9a173d0d2388959b5322ad45e356b572a1cd..8adef6e41177b9fcbd0c1428fc33ed6ed34a001c 100644
--- a/plugins/ImageGraph/StaticGraph.php
+++ b/plugins/ImageGraph/StaticGraph.php
@@ -12,9 +12,9 @@ namespace Piwik\Plugins\ImageGraph;
 use Exception;
 use pData;
 use pImage;
-use Piwik\Loader;
 use Piwik\Piwik;
 use Piwik\SettingsPiwik;
+use Piwik\Factory;
 
 require_once PIWIK_INCLUDE_PATH . "/libs/pChart2.1.3/class/pDraw.class.php";
 require_once PIWIK_INCLUDE_PATH . "/libs/pChart2.1.3/class/pImage.class.php";
@@ -24,7 +24,7 @@ require_once PIWIK_INCLUDE_PATH . "/libs/pChart2.1.3/class/pData.class.php";
  * The StaticGraph abstract class is used as a base class for different types of static graphs.
  *
  */
-abstract class StaticGraph
+abstract class StaticGraph extends Factory
 {
     const GRAPH_TYPE_BASIC_LINE = "evolution";
     const GRAPH_TYPE_VERTICAL_BAR = "verticalBar";
@@ -73,29 +73,19 @@ abstract class StaticGraph
 
     abstract public function renderGraph();
 
-    /**
-     * Return the StaticGraph according to the static graph type $graphType
-     *
-     * @throws Exception If the static graph type is unknown
-     * @param string $graphType
-     * @return \Piwik\Plugins\ImageGraph\StaticGraph
-     */
-    public static function factory($graphType)
+    protected static function getClassNameFromClassId($graphType)
     {
-        if (isset(self::$availableStaticGraphTypes[$graphType])) {
+        $className = self::$availableStaticGraphTypes[$graphType];
+        $className = __NAMESPACE__ . "\\StaticGraph\\" . $className;
+        return new $className;
+    }
 
-            $className = self::$availableStaticGraphTypes[$graphType];
-            $className = __NAMESPACE__ . "\\StaticGraph\\" . $className;
-            Loader::loadClass($className);
-            return new $className;
-        } else {
-            throw new Exception(
-                Piwik::translate(
-                    'General_ExceptionInvalidStaticGraphType',
-                    array($graphType, implode(', ', self::getAvailableStaticGraphTypes()))
-                )
-            );
-        }
+    protected static function getInvalidClassIdExceptionMessage($graphType)
+    {
+        return Piwik::translate(
+            'General_ExceptionInvalidStaticGraphType',
+            array($graphType, implode(', ', self::getAvailableStaticGraphTypes()))
+        );
     }
 
     public static function getAvailableStaticGraphTypes()
diff --git a/plugins/MobileMessaging/SMSProvider.php b/plugins/MobileMessaging/SMSProvider.php
index 9bbf0758ed000fac3b4224dbfad10f9a4ecc78a5..0019a1ad582084e0a86cd52b2ff669bbfc251b52 100644
--- a/plugins/MobileMessaging/SMSProvider.php
+++ b/plugins/MobileMessaging/SMSProvider.php
@@ -9,14 +9,14 @@
 namespace Piwik\Plugins\MobileMessaging;
 
 use Exception;
-use Piwik\Loader;
 use Piwik\Piwik;
+use Piwik\Factory;
 
 /**
  * The SMSProvider abstract class is used as a base class for SMS provider implementations.
  *
  */
-abstract class SMSProvider
+abstract class SMSProvider extends Factory
 {
     const MAX_GSM_CHARS_IN_ONE_UNIQUE_SMS = 160;
     const MAX_GSM_CHARS_IN_ONE_CONCATENATED_SMS = 153;
@@ -38,6 +38,18 @@ abstract class SMSProvider
 			',
     );
 
+    protected static function getClassNameFromClassId($id)
+    {
+        return __NAMESPACE__ . '\\SMSProvider\\' . $id;
+    }
+
+    protected static function getInvalidClassIdExceptionMessage($id)
+    {
+        return Piwik::translate('MobileMessaging_Exception_UnknownProvider',
+            array($id, implode(', ', array_keys(self::$availableSMSProviders)))
+        );
+    }
+
     /**
      * Return the SMSProvider associated to the provider name $providerName
      *
@@ -49,10 +61,7 @@ abstract class SMSProvider
     {
         $className = __NAMESPACE__ . '\\SMSProvider\\' . $providerName;
 
-        try {
-            Loader::loadClass($className);
-            return new $className;
-        } catch (Exception $e) {
+        if (!class_exists($className)) {
             throw new Exception(
                 Piwik::translate(
                     'MobileMessaging_Exception_UnknownProvider',
@@ -60,6 +69,8 @@ abstract class SMSProvider
                 )
             );
         }
+
+        return new $className;
     }
 
     /**
diff --git a/plugins/Morpheus/stylesheets/general/_forms.less b/plugins/Morpheus/stylesheets/general/_forms.less
index bac73f074ada2851428e77e1ec631d859888fc81..ea75691a781a142e44da34bac0729b68c4d493e7 100644
--- a/plugins/Morpheus/stylesheets/general/_forms.less
+++ b/plugins/Morpheus/stylesheets/general/_forms.less
@@ -41,6 +41,12 @@ button[type="button"],
     }
 }
 
+.top_bar_sites_selector {
+  .sites_autocomplete .custom_select {
+    z-index: 139;
+  }
+}
+
 .sites_autocomplete {
     input {
       min-height: 0;
diff --git a/plugins/UserSettings/Columns/Language.php b/plugins/UserSettings/Columns/Language.php
index 9c0bcadfd37799efbd91706bd330fda85be0f550..f61154c7c42cddf66c751dbd7011f5d2b5e3894d 100644
--- a/plugins/UserSettings/Columns/Language.php
+++ b/plugins/UserSettings/Columns/Language.php
@@ -32,6 +32,12 @@ class Language extends VisitDimension
      */
     public function onNewVisit(Request $request, Visitor $visitor, $action)
     {
-        return substr($request->getBrowserLanguage(), 0, 20);
+        $language = $request->getBrowserLanguage();
+
+        if (empty($language)) {
+            return '';
+        }
+
+        return substr($language, 0, 20);
     }
 }
\ No newline at end of file
diff --git a/tests/PHPUnit/Core/FactoryTest.php b/tests/PHPUnit/Core/FactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..dfa6cbdb7a6c083abce3611b3b2a2e75910c7525
--- /dev/null
+++ b/tests/PHPUnit/Core/FactoryTest.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+use Piwik\Factory;
+
+/**
+ * @group Core
+ * @group Core_FactoryTest
+ */
+class FactoryTest extends PHPUnit_Framework_TestCase
+{
+    public function testCreatingExistingClassSucceeds()
+    {
+        $instance = Factory::factory("Piwik\\Timer");
+
+        $this->assertNotNull($instance);
+        $this->assertInstanceOf("Piwik\\Timer", $instance);
+    }
+
+    /**
+     * @expectedException Exception
+     * @expectedExceptionMessage Invalid class ID
+     */
+    public function testCreatingInvalidClassThrows()
+    {
+        Factory::factory("This\\Class\\Does\\Not\\Exist");
+    }
+}
\ No newline at end of file
diff --git a/tests/PHPUnit/Fixtures/SomeVisitsAllConversions.php b/tests/PHPUnit/Fixtures/SomeVisitsAllConversions.php
index 74254a586e9d9e03730817804e03a13299167523..185fc594a889be3fdb4d1a88831a188a12238719 100644
--- a/tests/PHPUnit/Fixtures/SomeVisitsAllConversions.php
+++ b/tests/PHPUnit/Fixtures/SomeVisitsAllConversions.php
@@ -53,6 +53,18 @@ class SomeVisitsAllConversions extends Fixture
                 $revenue = 10, $allowMultipleConversions = true
             );
         }
+
+        if (!self::goalExists($idSite = 1, $idGoal = 3)) {
+            API::getInstance()->addGoal($this->idSite, 'click event', 'event_action', 'click', 'contains');
+        }
+
+        if (!self::goalExists($idSite = 1, $idGoal = 4)) {
+            API::getInstance()->addGoal($this->idSite, 'category event', 'event_category', 'The_Category', 'exact', true);
+        }
+
+        if (!self::goalExists($idSite = 1, $idGoal = 5)) {
+            API::getInstance()->addGoal($this->idSite, 'name event', 'event_name', 'the_name', 'exact');
+        }
     }
 
     private function trackVisits()
@@ -93,5 +105,17 @@ class SomeVisitsAllConversions extends Fixture
         $t->setTokenAuth($this->getTokenAuth());
         $t->setForceNewVisit();
         $t->doTrackPageView('This is tracked in a new visit.');
+
+        // should trigger two goals at once (event_category, event_action)
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.3)->getDatetime());
+        self::checkResponse($t->doTrackEvent('The_Category', 'click_action', 'name'));
+
+        // should not trigger a goal (the_category is case senstive goal)
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.4)->getDatetime());
+        self::checkResponse($t->doTrackEvent('the_category', 'click_action', 'name'));
+
+        // should trigger a goal for event_name
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.4)->getDatetime());
+        self::checkResponse($t->doTrackEvent('other_category', 'other_action', 'the_name'));
     }
 }
\ No newline at end of file
diff --git a/tests/PHPUnit/Integration/TrackGoalsAllowMultipleConversionsPerVisitTest.php b/tests/PHPUnit/Integration/TrackGoalsAllowMultipleConversionsPerVisitTest.php
index 6ed0f41a4655bdd91fa735b6766bfd0230f58f1b..cac31449c2c6ca17bc12d03874191c010f150bfd 100755
--- a/tests/PHPUnit/Integration/TrackGoalsAllowMultipleConversionsPerVisitTest.php
+++ b/tests/PHPUnit/Integration/TrackGoalsAllowMultipleConversionsPerVisitTest.php
@@ -39,11 +39,11 @@ class TrackGoalsAllowMultipleConversionsPerVisitTest extends IntegrationTestCase
 
         // test delete is working as expected
         $goals = API::getInstance()->getGoals($idSite);
-        $this->assertTrue(2 == count($goals));
+        $this->assertTrue(5 == count($goals));
         API::getInstance()->deleteGoal($idSite, self::$fixture->idGoal_OneConversionPerVisit);
         API::getInstance()->deleteGoal($idSite, self::$fixture->idGoal_MultipleConversionPerVisit);
         $goals = API::getInstance()->getGoals($idSite);
-        $this->assertTrue(empty($goals));
+        $this->assertTrue(3 == count($goals));
     }
 
     public function getApiForTesting()
diff --git a/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitTime.getVisitInformationPerServerTime_day.xml b/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitTime.getVisitInformationPerServerTime_day.xml
index 38b1c4d24766c71323011f19995b93e77db119c0..9c6eba0c0cb5407a27e99218e9e0e8e270cc82a4 100644
--- a/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitTime.getVisitInformationPerServerTime_day.xml
+++ b/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitTime.getVisitInformationPerServerTime_day.xml
@@ -4,10 +4,10 @@
 		<label>0h</label>
 		<nb_uniq_visitors>1</nb_uniq_visitors>
 		<nb_visits>2</nb_visits>
-		<nb_actions>2</nb_actions>
-		<max_actions>1</max_actions>
-		<sum_visit_length>1120</sum_visit_length>
-		<bounce_count>2</bounce_count>
+		<nb_actions>5</nb_actions>
+		<max_actions>3</max_actions>
+		<sum_visit_length>363</sum_visit_length>
+		<bounce_count>0</bounce_count>
 		<goals>
 			<row idgoal='1'>
 				<nb_conversions>2</nb_conversions>
@@ -19,8 +19,23 @@
 				<nb_visits_converted>1</nb_visits_converted>
 				<revenue>666</revenue>
 			</row>
+			<row idgoal='3'>
+				<nb_conversions>2</nb_conversions>
+				<nb_visits_converted>2</nb_visits_converted>
+				<revenue>0</revenue>
+			</row>
+			<row idgoal='4'>
+				<nb_conversions>1</nb_conversions>
+				<nb_visits_converted>1</nb_visits_converted>
+				<revenue>0</revenue>
+			</row>
+			<row idgoal='5'>
+				<nb_conversions>1</nb_conversions>
+				<nb_visits_converted>1</nb_visits_converted>
+				<revenue>0</revenue>
+			</row>
 		</goals>
-		<nb_conversions>4</nb_conversions>
+		<nb_conversions>8</nb_conversions>
 		<revenue>1332</revenue>
 	</row>
 	<row>
diff --git a/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitsSummary.get_day.xml b/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitsSummary.get_day.xml
index 6a1390c2e5be8e2f3ec7a144922c41f97135d3f2..76b20063bff495e90d0ecedd64102f3be2647a6e 100644
--- a/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitsSummary.get_day.xml
+++ b/tests/PHPUnit/Integration/expected/test_trackGoals_allowMultipleConversionsPerVisit__VisitsSummary.get_day.xml
@@ -2,12 +2,12 @@
 <result>
 	<nb_uniq_visitors>1</nb_uniq_visitors>
 	<nb_visits>2</nb_visits>
-	<nb_actions>2</nb_actions>
-	<nb_visits_converted>1</nb_visits_converted>
-	<bounce_count>2</bounce_count>
-	<sum_visit_length>1120</sum_visit_length>
-	<max_actions>1</max_actions>
-	<bounce_rate>100%</bounce_rate>
-	<nb_actions_per_visit>1</nb_actions_per_visit>
-	<avg_time_on_site>560</avg_time_on_site>
+	<nb_actions>5</nb_actions>
+	<nb_visits_converted>2</nb_visits_converted>
+	<bounce_count>0</bounce_count>
+	<sum_visit_length>363</sum_visit_length>
+	<max_actions>3</max_actions>
+	<bounce_rate>0%</bounce_rate>
+	<nb_actions_per_visit>2.5</nb_actions_per_visit>
+	<avg_time_on_site>182</avg_time_on_site>
 </result>
\ No newline at end of file
diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php
index d32c604026d002fc203b56b0eb1cf2f6d7c4f9cf..6a80c72755d0b20d50fe6d2779578d80760629d5 100644
--- a/tests/PHPUnit/bootstrap.php
+++ b/tests/PHPUnit/bootstrap.php
@@ -33,7 +33,6 @@ require_once file_exists(PIWIK_INCLUDE_PATH . '/vendor/autoload.php')
 
 require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/upgrade.php';
 require_once PIWIK_INCLUDE_PATH . '/core/testMinimumPhpVersion.php';
-require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
 require_once PIWIK_INCLUDE_PATH . '/core/FrontController.php';
 require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/DatabaseTestCase.php';
 require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/IntegrationTestCase.php';
diff --git a/tests/PHPUnit/proxy/includes.php b/tests/PHPUnit/proxy/includes.php
index 6e3acc8af52ba8fedf5eb3adf650eeb40e7f2902..858ae485d20ae74fdca4e65dca9ad2581804fa85 100644
--- a/tests/PHPUnit/proxy/includes.php
+++ b/tests/PHPUnit/proxy/includes.php
@@ -13,7 +13,6 @@ require_once file_exists(PIWIK_INCLUDE_PATH . '/vendor/autoload.php')
     ? PIWIK_INCLUDE_PATH . '/vendor/autoload.php' // Piwik is the main project
     : PIWIK_INCLUDE_PATH . '/../../autoload.php'; // Piwik is installed as a dependency
 
-require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
 require_once PIWIK_INCLUDE_PATH . '/core/EventDispatcher.php';
 require_once PIWIK_INCLUDE_PATH . '/core/Piwik.php';
 require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/upgrade.php';
diff --git a/tests/resources/staticFileServer.php b/tests/resources/staticFileServer.php
index 237cf0af59adbc26cffa25847d4dbac44554747f..5907c169e3e61f188a17f4bf0a432a51895d4b19 100644
--- a/tests/resources/staticFileServer.php
+++ b/tests/resources/staticFileServer.php
@@ -39,7 +39,7 @@ require_once PIWIK_INCLUDE_PATH . '/core/testMinimumPhpVersion.php';
 
 session_cache_limiter('nocache');
 @date_default_timezone_set('UTC');
-require_once PIWIK_INCLUDE_PATH .'/core/Loader.php';
+require_once PIWIK_INCLUDE_PATH .'/vendor/autoload.php';
 
 // This is Piwik logo, the static file used in this test suit
 define("TEST_FILE_LOCATION", dirname(__FILE__) . "/lipsum.txt");
diff --git a/tests/travis/travis.sh b/tests/travis/travis.sh
index 2d1d9ebac1e9d74998a204901cae677c43c76398..fa783472dc7a708d0a637390f9397860c0be8560 100755
--- a/tests/travis/travis.sh
+++ b/tests/travis/travis.sh
@@ -12,7 +12,7 @@ if [ "$TEST_SUITE" != "UITests" ] && [ "$TEST_SUITE" != "AngularJSTests" ]
 then
     if [ `phpunit --group __nogroup__ | grep "No tests executed" | wc -l` -ne 1 ]
     then
-        echo "=====> There are some tests functions which do not have a @group set. "
+        echo "=====> There are some tests functions which do not have a @group set or have no tests. "
         echo "       Please add the @group phpdoc comment to the following tests: <====="
         phpunit --group __nogroup__ --testdox | grep "[x]"
         exit 1